Skip to content

Unified master seed#148

Open
theivess wants to merge 25 commits intomasterfrom
feat/unified-master-seed
Open

Unified master seed#148
theivess wants to merge 25 commits intomasterfrom
feat/unified-master-seed

Conversation

@theivess
Copy link
Copy Markdown

@theivess theivess commented Apr 4, 2026

As discussed..
Now we have a single master seed that is used across Liquid, Mostro and for Vault
Note: For vault it's enabled only when developer mode is true

Do Not Merge right now..
I am thoroughly testing the code right now in regtest setup

Summary by CodeRabbit

  • New Features

    • Passkey-based cube creation (web + macOS native) and passkey toggle in the UI.
    • New Master-Seed backup wizard in General Settings with PIN flow and verification.
    • Developer mode for the installer enabling advanced key generation options.
  • Improvements

    • UI shows a backup-warning banner when a cube isn’t backed up and not passkey-derived.
    • Migration preserves existing settings; cube-limit gating now applies only to Bitcoin.
    • Signer handling and recovery workflows improved for cloning/derivation.

theivess added 10 commits April 4, 2026 05:33
- Add MASTER_SEED_LABEL ("master_") and LEGACY_LIQUID_SEED_LABEL ("liquid_")
  constants alongside MNEMONICS_FOLDER_NAME for labelling mnemonic files.
- Extend vault-only filter in from_datadir_with_password_filtered to skip
  files containing "-master_" in addition to "-liquid_", so unified master
  seed files are excluded from vault-signer reads.
- Add HotSigner::nostr_identity_keys — derives NIP-06 key pair at
  m/44'/1237'/0'/0'/0' per the unified-master-seed design.
- Add HotSigner::try_clone — reconstructs an equivalent signer from the
  stored mnemonic; useful when the same key material is needed by a second
  consumer (e.g. installer re-using cube master seed as dev vault signer).
- Fix stale/duplicate doc comments on store_encrypted and
  from_datadir_with_password.
… try_clone

- Add MASTER_SEED_LABEL and LEGACY_LIQUID_SEED_LABEL constants for filename-based
  signer type identification (replaces raw string matching)
- from_datadir_with_password_filtered: rename skip_liquid -> vault_only; use
  constants in the filename filter instead of raw string literals
- HotSigner::try_clone(): infers network from master_xpriv instead of requiring
  caller to pass it (prevents wrong-network mistakes)
- Add test_try_clone_fingerprint_matches test
…ner_fingerprint

- Add serde alias for backward compat with existing settings.json files
- Add with_master_signer builder; keep with_liquid_signer as deprecated shim
- Update all call-sites across launcher.rs, breez/mod.rs, liquid/settings.rs, gui/tab.rs
- Add test_cube_settings_alias_backward_compat to guard serde alias
Regtest, Testnet, Testnet4 and Signet are dev/test environments;
enforcing the tier-based Cube limit there is counterproductive.
- Installer::new accepts developer_mode; uses master seed clone when true
- Signer::new (existing constructor) used to wrap cloned HotSigner
- SelectKeySource gates hot-signer option on developer_mode field
- Hot-signer button hidden in production mode (requires HW wallet)
- DefineDescriptor threads developer_mode through to SelectKeySource
- tab.rs: loader/app call-sites read developer_mode from GlobalSettings
@theivess theivess changed the title Unified master seed [WIP] Unified master seed Apr 4, 2026
@theivess theivess requested a review from satoshisound April 4, 2026 01:14
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 4, 2026

Warning

Rate limit exceeded

@satoshisound has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 34 minutes and 26 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 34 minutes and 26 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 639169c0-a71f-42a7-9c99-f7be87586d82

📥 Commits

Reviewing files that changed from the base of the PR and between d9973b9 and 508f5cf.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (25)
  • .env.example
  • coincube-core/src/signer.rs
  • coincube-gui/Cargo.toml
  • coincube-gui/src/app/breez/client.rs
  • coincube-gui/src/app/mod.rs
  • coincube-gui/src/app/settings/mod.rs
  • coincube-gui/src/app/state/liquid/overview.rs
  • coincube-gui/src/app/state/vault/overview.rs
  • coincube-gui/src/app/state/vault/psbt.rs
  • coincube-gui/src/app/view/message.rs
  • coincube-gui/src/app/view/mod.rs
  • coincube-gui/src/app/view/vault/overview.rs
  • coincube-gui/src/feature_flags.rs
  • coincube-gui/src/gui/tab.rs
  • coincube-gui/src/installer/message.rs
  • coincube-gui/src/installer/mod.rs
  • coincube-gui/src/installer/step/descriptor/editor/border_wallet_wizard.rs
  • coincube-gui/src/installer/step/descriptor/editor/mod.rs
  • coincube-gui/src/installer/step/share_xpubs.rs
  • coincube-gui/src/installer/view/mod.rs
  • coincube-gui/src/launcher.rs
  • coincube-gui/src/loader.rs
  • coincube-gui/src/services/mod.rs
  • coincube-gui/src/services/passkey/mod.rs
  • coincube-ui/src/component/hw.rs
📝 Walkthrough

Walkthrough

Renames and refactors "hot signer" → "master signer" across core and GUI, adds passkey-based master-key derivation (webview + macOS native), introduces installer developer_mode, implements PRF-derived MasterSigner construction and try_clone, and adds a master-seed backup wizard and related UI/state plumbing.

Changes

Cohort / File(s) Summary
Core signer & border-wallet
coincube-core/src/signer.rs, coincube-core/src/border_wallet/mod.rs
Renamed primary signer type to MasterSigner, added MASTER_SEED_LABEL and LEGACY_LIQUID_SEED_LABEL, added from_prf_output(...) and try_clone(), updated datadir filtering to vault_only, and added GridRecoveryPhrase::from_master_signer with tests.
Breez client & signer adapter
coincube-gui/src/app/breez/client.rs, coincube-gui/src/app/breez/mod.rs
Swapped GUI Breez signer adapter to use MasterSigner (type/adapter rename), updated connect/accessor signatures and breez client loading to accept master signer fingerprints and propagate missing-signer errors with fingerprint.
GUI signer wrapper & tooling
coincube-gui/src/signer.rs, coincube-ui/src/component/hw.rs, tests/tools/taproot_signer/*
GUI Signer now wraps MasterSigner; UI helpers renamed from hot→master; docs/tests updated to reflect master signer wording.
Settings, state & cache
coincube-gui/src/app/settings/mod.rs, coincube-gui/src/app/cache.rs, coincube-gui/src/app/mod.rs
Replaced persisted liquid_wallet_signer_fingerprint with master_signer_fingerprint (serde alias kept), added PasskeyMetadata, allow_random_grid_phrase, and cache fields current_cube_backed_up/current_cube_is_passkey.
Backup flow UI & state
coincube-gui/src/app/view/settings/backup.rs, coincube-gui/src/app/state/settings/general.rs, coincube-gui/src/app/view/settings/general.rs, coincube-gui/src/app/view/mod.rs
Added master-seed backup wizard UI, BackupSeedState state machine, PIN/unlock/verify/save flows, and backup_warning_banner. General settings now dispatch to the backup wizard when active.
Liquid settings removal/simplification
coincube-gui/src/app/state/liquid/settings.rs, coincube-gui/src/app/view/liquid/settings.rs, coincube-gui/src/app/state/mod.rs
Removed prior Liquid wallet backup flow/state and simplified the view to a short notice; re-export narrowed to LiquidSettings.
Installer & descriptor editor (developer mode)
coincube-gui/src/installer/mod.rs, coincube-gui/src/installer/step/descriptor/editor/*, coincube-gui/src/installer/descriptor.rs, coincube-gui/src/installer/message.rs
Added developer_mode: bool to Installer/DefineDescriptor, thread flag into key-source modal, renamed generate/fetch messages to master-signer variants, restricted generation button to developer_mode, and added options to derive GridRecoveryPhrase from master signer.
Launcher / cube creation / passkey flows
coincube-gui/src/launcher.rs, coincube-gui/src/loader.rs, coincube-gui/src/app/wallet.rs
Added passkey-mode UI and ceremony plumbing, changed cube-creation flows to support passkey registration (derive MasterSigner from PRF), switched restoration to MasterSigner::from_mnemonic, and tightened BTC-only cube-limit gating.
Passkey service & native macOS support
coincube-gui/src/services/passkey/mod.rs, coincube-gui/src/services/passkey/macos.rs
New passkey ceremony module (webview + IPC + PRF validation), public types for registration/authentication results, and macOS native implementation using AuthenticationServices exposing PRF output and credential id.
Views & other UI updates
coincube-gui/src/app/view/vault/overview.rs, coincube-gui/src/app/state/vault/overview.rs, coincube-gui/src/app/state/liquid/overview.rs, coincube-gui/src/app/view/vault/psbt.rs, coincube-gui/src/app/view/liquid/settings.rs, coincube-gui/src/app/view/message.rs
Inserted backup-warning banner wiring into vault/liquid overviews and PSBT signer UI, updated message enums (SelectHotSigner→SelectMasterSigner and new backup messages), and refactored liquid settings view to minimal notice.
Manifests, deps, tests & minor files
coincube-gui/Cargo.toml, .env.example, coincube-core/Cargo.toml, test assets, docs/*`
Added macOS target deps for objc2, added COINCUBE_ENABLE_PASSKEY example env var, updated commentary, and small test/json/doc updates.

Sequence Diagram(s)

sequenceDiagram
  participant UI as User UI
  participant App as App (Launcher)
  participant Web as Webview Ceremony
  participant Core as MasterSigner logic
  participant Store as Settings Storage

  UI->>App: User starts passkey registration
  App->>Web: create webview with CeremonyMode::Register
  Web->>UI: show webview (user completes OS/Web flow)
  Web->>App: IPC -> {"registered", credential_id, prf_output}
  App->>Core: MasterSigner::from_prf_output(network, prf_output)
  Core-->>App: MasterSigner instance
  App->>Store: create CubeSettings.with_master_signer(..).with_passkey(..)
  Store-->>App: persisted settings
  App->>UI: navigate to new Cube / show success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Feature/passkey #160: Overlaps signer rename (HotSigner→MasterSigner) and PRF-derived master-key changes — strong code-level overlap.
  • Add Border Wallets #126: Adds/changes border_wallet GridRecoveryPhrase and derivation logic — directly related to GridRecoveryPhrase::from_master_signer additions.
  • Active wallet send flow #66: Modifies Breez client signer adapter and datadir-loading behaviors that intersect with the Breez → MasterSigner adapter changes here.

Suggested reviewers

  • sokorototo

Poem

🐰 I hopped through code with tidy feet,
Master seeds and passkeys meet,
From PRF springs a seeded song,
Try_clone hums the same-keyed throng,
Backups bloom — the rabbit's neat! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Unified master seed' is clear and specific, directly capturing the main change: consolidating wallet seed usage to a single master seed across Liquid, Mostro, and Vault. This accurately reflects the primary objective of the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/unified-master-seed

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
coincube-gui/src/gui/tab.rs (1)

398-413: ⚠️ Potential issue | 🟡 Minor

Run cargo fmt on this file before merge.

CI is already red on Line 405, Line 544, and Line 585 for cargo fmt -- --check.

Also applies to: 537-552, 585-590

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@coincube-gui/src/gui/tab.rs` around lines 398 - 413, The code in the loader
view branch (loader::Message::View(loader::ViewMessage::SetupVault)) has
formatting violations around the Installer::new(...) call; run rustfmt (cargo
fmt) on coincube-gui/src/gui/tab.rs and reformat the block so argument
indentation and trailing commas match project style (including the multi-line
Installer::new call and nearby ranges around lines shown); then re-run cargo fmt
-- --check to ensure lines 405, 537-552, and 585-590 are fixed before merging.
🧹 Nitpick comments (1)
coincube-gui/src/installer/step/descriptor/editor/key.rs (1)

1198-1202: Consider removing unreachable non-dev subtitle branch

widget_generate_hot_key() is only included when self.developer_mode is true (Line 1067), so the non-dev subtitle branch is effectively dead code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@coincube-gui/src/installer/step/descriptor/editor/key.rs` around lines 1198 -
1202, The subtitle conditional contains a dead branch because
widget_generate_hot_key() is only compiled/used when self.developer_mode is
true; simplify by removing the unreachable else branch: set subtitle to the
dev-mode message unconditionally when developer_mode is true (or replace the
whole conditional with a single constant/dev message), and remove the unused
non-dev string so the code around subtitle, widget_generate_hot_key, and any
related UI text only contains the developer-mode message.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@coincube-core/src/signer.rs`:
- Around line 221-226: The code in signer.rs unconditionally skips files whose
filenames contain "-master_seed" (MASTER_SEED_LABEL) when vault_only is true,
preventing disk-based master-seed loading even in dev scenarios; modify the skip
logic to respect an explicit developer override (e.g., add a boolean parameter
like allow_dev_master_seed to/from_datadir_vault_only or a mode flag passed into
the file-iteration function) so that when the override is true you do not skip
files matching MASTER_SEED_LABEL or LEGACY_LIQUID_SEED_LABEL; update the caller
(from_datadir_vault_only in wallet.rs) to pass the appropriate override in dev
mode and document the new parameter so master-seed files can be loaded from disk
only when explicitly allowed.

In `@coincube-gui/src/app/breez/mod.rs`:
- Around line 67-73: The map_err closure currently only maps MnemonicStorage
NotFound to BreezError::SignerNotFound but misses the
coincube_core::signer::SignerError::SignerNotFound variant; update the match in
the map_err to include a branch for
coincube_core::signer::SignerError::SignerNotFound(fingerprint) and return
BreezError::SignerNotFound(fingerprint or master_signer_fingerprint as
appropriate), keeping the existing MnemonicStorage(io_err) =>
BreezError::SignerNotFound(master_signer_fingerprint) behavior and leaving other
errors mapped to BreezError::SignerError(e.to_string()).

In `@coincube-gui/src/installer/mod.rs`:
- Around line 143-165: When developer_mode is true but cloning the master signer
(the master_signer computed before assigning signer) fails, do not proceed with
developer_mode in downstream behavior; instead either return an error / abort
installation or override developer_mode to false for this Installer instance so
DefineDescriptor and UI logic don't treat the wallet as
unified-master-seed-derived. Locate the signer creation logic (the
developer_mode branch that builds master_signer and uses unwrap_or_else with
Signer::generate) and after detecting master_signer.is_none() either propagate
an error (fail closed) or set developer_mode = false (downgrade to normal mode)
before constructing Installer and before calling DefineDescriptor so the
hot-signer UI path is not offered when the master signer couldn't be cloned.
- Around line 145-165: The code block constructing `signer` is not
rustfmt-formatted; run `cargo fmt` to fix whitespace/line breaks so CI passes,
ensuring the `signer` initialization (the `developer_mode` conditional,
`master_signer` construction from `breez_client`/`liquid_signer()`, and the
`Signer::generate(network)` fallback) matches rustfmt style; reformat the hunk
and commit the formatted changes.

In `@coincube-gui/src/installer/step/descriptor/editor/key.rs`:
- Around line 1067-1069: CI reports a rustfmt failure in the let binding for
hot_signer in key.rs; run rustfmt (cargo fmt) to reformat the file and ensure
the expression assigning hot_signer (the let hot_signer = (self.developer_mode
&& !self.keys.contains_key(&hot_signer_fg) && safety_net_token.is_none())
binding) is formatted according to rustfmt rules (or manually adjust the line
breaks/parentheses to match rustfmt style) before pushing the change.

In `@coincube-gui/src/installer/step/descriptor/editor/mod.rs`:
- Around line 829-833: The test creates a Signet descriptor but generates a
Bitcoin signer; update the Signer generation to use Network::Signet so the
fixture uses a Signet signer. Locate the Sandbox<DefineDescriptor> construction
calling DefineDescriptor::new and change the Signer::generate(Network::Bitcoin)
call to Signer::generate(Network::Signet) (keeping the
Arc::new(Mutex::new(...)). This ensures the signer network matches the
descriptor network.

In `@coincube-gui/src/launcher.rs`:
- Around line 248-250: Formatting in coincube-gui/src/launcher.rs is failing
rustfmt; run rustfmt (cargo fmt) and reformat the file so expressions like the
at_limit computation and subsequent if block follow rustfmt style (ensure
spacing around && and alignment in the expression using cube_count, limit,
self.network, and Network::Bitcoin), and apply the same formatting fixes to the
similar blocks referenced around the later occurrences (the other uses of
at_limit-like checks). After formatting, re-run cargo fmt -- --check to confirm
the file is compliant.

---

Outside diff comments:
In `@coincube-gui/src/gui/tab.rs`:
- Around line 398-413: The code in the loader view branch
(loader::Message::View(loader::ViewMessage::SetupVault)) has formatting
violations around the Installer::new(...) call; run rustfmt (cargo fmt) on
coincube-gui/src/gui/tab.rs and reformat the block so argument indentation and
trailing commas match project style (including the multi-line Installer::new
call and nearby ranges around lines shown); then re-run cargo fmt -- --check to
ensure lines 405, 537-552, and 585-590 are fixed before merging.

---

Nitpick comments:
In `@coincube-gui/src/installer/step/descriptor/editor/key.rs`:
- Around line 1198-1202: The subtitle conditional contains a dead branch because
widget_generate_hot_key() is only compiled/used when self.developer_mode is
true; simplify by removing the unreachable else branch: set subtitle to the
dev-mode message unconditionally when developer_mode is true (or replace the
whole conditional with a single constant/dev message), and remove the unused
non-dev string so the code around subtitle, widget_generate_hot_key, and any
related UI text only contains the developer-mode message.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bad9b136-c982-4bb1-8a18-46b89b7b12ae

📥 Commits

Reviewing files that changed from the base of the PR and between fd79335 and d9386dc.

📒 Files selected for processing (9)
  • coincube-core/src/signer.rs
  • coincube-gui/src/app/breez/mod.rs
  • coincube-gui/src/app/settings/mod.rs
  • coincube-gui/src/app/state/liquid/settings.rs
  • coincube-gui/src/gui/tab.rs
  • coincube-gui/src/installer/mod.rs
  • coincube-gui/src/installer/step/descriptor/editor/key.rs
  • coincube-gui/src/installer/step/descriptor/editor/mod.rs
  • coincube-gui/src/launcher.rs

Comment on lines +221 to +226
// Skip Liquid and master-seed mnemonics when in vault-only mode.
if vault_only {
if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
if filename.contains("-liquid_") {
if filename.contains(&format!("-{}", LEGACY_LIQUID_SEED_LABEL))
|| filename.contains(&format!("-{}", MASTER_SEED_LABEL))
{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Call sites of from_datadir_vault_only =="
rg -nP --type=rust -C3 '\bfrom_datadir_vault_only\s*\('

echo
echo "== Call sites of from_datadir_with_password_filtered =="
rg -nP --type=rust -C3 '\bfrom_datadir_with_password_filtered\s*\('

echo
echo "== Developer mode checks near mnemonic-loading paths =="
rg -nP --type=rust -C4 'developer_mode|vault_only'

Repository: coincubetech/coincube

Length of output: 35676


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Checking ALL imports/usages of from_datadir methods ==="
rg -nP --type=rust '\bfrom_datadir\b' --context 2

echo
echo "=== Checking definitions and doc comments for from_datadir methods ==="
rg -nP --type=rust 'pub fn from_datadir' -A 3

Repository: coincubetech/coincube

Length of output: 2261


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Reading signer.rs around line 304 for method definitions ==="
sed -n '295,320p' coincube-core/src/signer.rs

echo
echo "=== Reading signer.rs around line 221 for the vault_only logic ==="
sed -n '215,235p' coincube-core/src/signer.rs

echo
echo "=== Searching for any comments about master-seed loading intent ==="
rg -nP --type=rust -B 2 -A 2 'master.*seed|MASTER_SEED' coincube-core/src/signer.rs | head -50

echo
echo "=== Check if developer_mode is ever passed to signer loading methods ==="
rg -nP --type=rust 'developer_mode.*from_datadir|from_datadir.*developer_mode'

Repository: coincubetech/coincube

Length of output: 4451


vault_only flag unconditionally excludes master-seed files from disk, with no developer-mode override

Lines 221–226 skip files containing -master_seed whenever vault_only == true. Since the only production call site is from_datadir_vault_only() (wallet.rs:154), master-seed files cannot be loaded from disk in any context—including developer mode.

This conflicts with the stated intent in the code comment at signer.rs:376–377 ("the installer re-using the cube's master seed as the vault hot-signer in dev mode"). While dev mode in the installer does load a master signer, it sources it from breez_client.liquid_signer() rather than from disk. If disk-based master-seed loading is needed in dev mode, a new code path or parameter is required to bypass the unconditional skip.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@coincube-core/src/signer.rs` around lines 221 - 226, The code in signer.rs
unconditionally skips files whose filenames contain "-master_seed"
(MASTER_SEED_LABEL) when vault_only is true, preventing disk-based master-seed
loading even in dev scenarios; modify the skip logic to respect an explicit
developer override (e.g., add a boolean parameter like allow_dev_master_seed
to/from_datadir_vault_only or a mode flag passed into the file-iteration
function) so that when the override is true you do not skip files matching
MASTER_SEED_LABEL or LEGACY_LIQUID_SEED_LABEL; update the caller
(from_datadir_vault_only in wallet.rs) to pass the appropriate override in dev
mode and document the new parameter so master-seed files can be loaded from disk
only when explicitly allowed.

@theivess theivess changed the title [WIP] Unified master seed Unified master seed Apr 6, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (6)
coincube-gui/src/services/passkey/macos.rs (3)

52-55: Document the origin and purpose of the PRF salt.

The salt "NYOASTRTSAOYN" appears to be a specific constant from the Breez passkey-login spec. Consider adding a reference URL or more context about why this specific salt value is used, as it's a security-critical parameter.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@coincube-gui/src/services/passkey/macos.rs` around lines 52 - 55, Update the
PRF_SALT constant's documentation to state its origin and purpose: add a short
comment above PRF_SALT explaining that the bytes correspond to the ASCII string
"NYOASTRTSAOYN", that this value is mandated by the Breez passkey-login spec,
and include a reference URL (or issue/spec section) to the official Breez spec
for future auditing; ensure the comment also notes that this is a
security-critical parameter and should not be changed without consulting the
referenced spec.

136-161: Fallback hidden window creation may leak if retain/release is mismanaged.

The fallback window creation calls retain explicitly but relies on Retained::from_raw to take ownership. If from_raw fails (returns None), the manual retain isn't balanced. However, the expect call makes this a panic scenario, so in practice the leak won't occur. Consider adding a comment explaining the retain/ownership transfer.

📝 Add clarifying comment
                // Retain the window and return as Retained<RuntimeNSObject>
                // (which is what ASPresentationAnchor aliases to).
+               // The explicit retain call is balanced by Retained::from_raw
+               // taking ownership of the +1 reference count.
                let _: () = msg_send![window, retain];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@coincube-gui/src/services/passkey/macos.rs` around lines 136 - 161, The
fallback NSWindow allocation calls msg_send![window, retain] then immediately
hands the pointer to Retained::from_raw(window as *mut NSObject) with an expect,
which implicitly transfers ownership; add a brief clarifying comment around the
retain + Retained::from_raw sequence (referencing window, alloc, msg_send!,
retain, Retained::from_raw and the expect) that explains the manual retain is
required to transfer ownership into Retained and that the subsequent expect will
panic on failure (preventing an unbalanced retain from silently leaking);
optionally mention that if you change from_raw to a fallible path in future
you'll need to balance the retain/release.

319-324: Authentication flow not yet implemented — add tracking issue or TODO context.

The authenticate method currently returns an error. Consider adding a GitHub issue reference or more context about when this will be implemented, as passkey-based Cubes will need re-authentication support.

Do you want me to open a GitHub issue to track the implementation of native macOS passkey authentication?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@coincube-gui/src/services/passkey/macos.rs` around lines 319 - 324, The
authenticate function currently returns a hardcoded Err; add a clear TODO
comment above or inside pub fn authenticate(rp_id: &str, _credential_id: &[u8])
stating the planned work and linking to a tracking GitHub issue (e.g., "TODO:
Implement native macOS passkey authentication — tracking: GH-<number> or
https://..."), include a short description of expected behavior (assertion flow,
re-authentication for passkey-based Cubes) and update the error message to
reference the tracking issue so callers see context; ensure the comment
references the function name authenticate and the parameters
rp_id/_credential_id for discoverability.
coincube-gui/src/launcher.rs (1)

344-445: Consider zeroizing the PIN after use in the async task.

The pin variable is moved into the async block and used for mnemonic encryption. After store_encrypted completes, the PIN remains in memory until the async block completes. Consider wrapping pin in Zeroizing<String> for defense-in-depth.

♻️ Optional: Wrap PIN in Zeroizing
-                let pin = if passkey_mode {
-                    String::new()
-                } else {
-                    self.create_cube_pin.value()
-                };
+                let pin = Zeroizing::new(if passkey_mode {
+                    String::new()
+                } else {
+                    self.create_cube_pin.value()
+                });

Then use &*pin where a &str is needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@coincube-gui/src/launcher.rs` around lines 344 - 445, The async closure
captures and moves the plain `pin` into the Task, leaving it resident until the
future completes; wrap the PIN in a zeroizing container (e.g.,
zeroize::Zeroizing<String>) before it is moved into the async block and pass a
string slice via &*pin where a &str is required, then let the Zeroizing wrapper
go out of scope (or explicitly drop it) immediately after the call to
MasterSigner::store_encrypted to ensure the memory is zeroed; update references
in this block (the async move closure, uses of `pin`, and the call to
MasterSigner::store_encrypted) to use the Zeroizing wrapper and deref to &str as
needed.
coincube-gui/src/services/passkey/mod.rs (2)

186-188: Arc<PasskeyCeremonyChannel> with non-Send/Sync type triggers Clippy lint.

The #[allow(clippy::arc_with_non_send_sync)] suppresses the lint, but mpsc::Sender and mpsc::Receiver are indeed not Sync. Since the channel is only used within the same thread (main thread for UI), this is safe, but consider using std::sync::mpsc alternatives or documenting why Arc is needed here (likely for the IPC closure capture).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@coincube-gui/src/services/passkey/mod.rs` around lines 186 - 188, The field
channel is wrapped in Arc and suppresses clippy for non-Send/Sync because
PasskeyCeremonyChannel contains non-Sync mpsc endpoints; since it's only used on
the UI main thread, replace Arc with a single-threaded pointer (e.g., use Rc or
Rc<RefCell<PasskeyCeremonyChannel>>) instead of Arc::new(...) to avoid the lint
and make intent explicit, updating imports and any closure captures that assume
Arc, or alternatively add a short doc comment above the channel field explaining
why Arc is required and that the usage is single-threaded so the clippy allow is
safe (referencing PasskeyCeremonyChannel and the channel field).

264-271: Error classification via substring matching may be fragile.

Matching error messages with contains("cancelled") or contains("NotAllowedError") depends on the exact wording from the ceremony page. If the page's error messages change, these classifications could break.

Consider defining error codes in the IPC protocol for more robust classification.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@coincube-gui/src/services/passkey/mod.rs` around lines 264 - 271, The current
error handling in CeremonyIpcMessage::Error classifies failures by substring
matching (e.g., checking message.contains("cancelled") or "NotAllowedError")
which is fragile; update the IPC protocol and the CeremonyIpcMessage::Error
variant to include a discrete error code/enum (e.g., ErrorCode) or numeric code
alongside the message, then change the match in the handler to switch on that
error code and map to PasskeyError::Cancelled, PasskeyError::PrfNotSupported, or
PasskeyError::CeremonyFailed(message) accordingly so classification no longer
depends on brittle substrings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@coincube-core/src/signer.rs`:
- Around line 390-400: The try_clone implementation incorrectly maps
master_xpriv.network via NetworkKind to only Bitcoin vs Testnet, which
misclassifies Signet/Regtest as Testnet; update the Signer struct to persist the
exact bitcoin::Network used when created (e.g., add a field like
original_network or network: bitcoin::Network), set that field wherever Signer
is constructed, and then change try_clone to call
Self::from_mnemonic(self.network, self.mnemonic.clone()) instead of inferring
from master_xpriv.network so the cloned signer preserves the exact network
(retain references to try_clone, master_xpriv.network, NetworkKind, and
from_mnemonic to locate edits).

In `@coincube-gui/src/app/state/vault/psbt.rs`:
- Around line 927-935: The current error path returns WalletError::MasterSigner
with the message "Master signer not loaded" even for signing failures; change
the error to be signing-specific so load vs sign are distinguishable. Update the
branch that returns Err(WalletError::MasterSigner("Master signer not
loaded".to_string()).into()) (and the earlier map_err wrapping signing failures)
to either: 1) introduce a new variant like WalletError::MasterSignerSign(String)
and use it for signing errors, or 2) keep WalletError::MasterSigner but use
distinct messages (e.g. "Master signer failed to sign PSBT: …" vs "Master signer
not loaded") so callers (e.g. code in wallet.rs) can render correct text; adjust
the map_err and the tuple-returning branch accordingly to reference the new
variant/message.

In `@coincube-gui/src/feature_flags.rs`:
- Around line 93-105: The test passkey_disabled_by_default asserts that the
compile-time constant PASSKEY_ENABLED is false, which breaks builds that
intentionally enable passkeys; remove the failing unit test function named
passkey_disabled_by_default from feature_flags.rs (or alternatively move this
"default is off" assertion into CI/build scripts instead of the test suite) so
that PASSKEY_ENABLED can be true in enabled builds without causing cargo test to
fail.

In `@coincube-gui/src/installer/step/descriptor/editor/border_wallet_wizard.rs`:
- Around line 238-243: The code panics on a poisoned Mutex via
expect("poisoned") when accessing self.signer; replace the expect with handling
that maps the lock error into a user-facing error (assign to self.error) and
then return the Err result so the UI stays alive. Concretely, change the signer
lock path used around self.signer so that a poisoned lock produces a formatted
error string assigned to self.error (or returned as Err) instead of calling
expect, and then proceed to call derive_grid_recovery_phrase() only on a
successfully-locked signer; keep references to self.signer,
derive_grid_recovery_phrase(), and self.error when implementing this flow so the
Generate Phrase button shows the error rather than crashing the app.

In `@coincube-gui/src/services/passkey/mod.rs`:
- Around line 15-19: The hardcoded ceremony URL CEREMONY_BASE_URL =
"https://coincube.io/passkey" (and RP_ID = "coincube.io") points to a
non-existent endpoint and will break the passkey flow; make the ceremony
endpoint and RP_ID configurable (e.g., read from env/config) and default to a
safe local/dev URL for non-production, replace all direct uses of
CEREMONY_BASE_URL and RP_ID in this module with the config-backed values (load
during init in the passkey module), and ensure any production deploy sets RP_ID
to the actual domain that will host the ceremony page.

---

Nitpick comments:
In `@coincube-gui/src/launcher.rs`:
- Around line 344-445: The async closure captures and moves the plain `pin` into
the Task, leaving it resident until the future completes; wrap the PIN in a
zeroizing container (e.g., zeroize::Zeroizing<String>) before it is moved into
the async block and pass a string slice via &*pin where a &str is required, then
let the Zeroizing wrapper go out of scope (or explicitly drop it) immediately
after the call to MasterSigner::store_encrypted to ensure the memory is zeroed;
update references in this block (the async move closure, uses of `pin`, and the
call to MasterSigner::store_encrypted) to use the Zeroizing wrapper and deref to
&str as needed.

In `@coincube-gui/src/services/passkey/macos.rs`:
- Around line 52-55: Update the PRF_SALT constant's documentation to state its
origin and purpose: add a short comment above PRF_SALT explaining that the bytes
correspond to the ASCII string "NYOASTRTSAOYN", that this value is mandated by
the Breez passkey-login spec, and include a reference URL (or issue/spec
section) to the official Breez spec for future auditing; ensure the comment also
notes that this is a security-critical parameter and should not be changed
without consulting the referenced spec.
- Around line 136-161: The fallback NSWindow allocation calls msg_send![window,
retain] then immediately hands the pointer to Retained::from_raw(window as *mut
NSObject) with an expect, which implicitly transfers ownership; add a brief
clarifying comment around the retain + Retained::from_raw sequence (referencing
window, alloc, msg_send!, retain, Retained::from_raw and the expect) that
explains the manual retain is required to transfer ownership into Retained and
that the subsequent expect will panic on failure (preventing an unbalanced
retain from silently leaking); optionally mention that if you change from_raw to
a fallible path in future you'll need to balance the retain/release.
- Around line 319-324: The authenticate function currently returns a hardcoded
Err; add a clear TODO comment above or inside pub fn authenticate(rp_id: &str,
_credential_id: &[u8]) stating the planned work and linking to a tracking GitHub
issue (e.g., "TODO: Implement native macOS passkey authentication — tracking:
GH-<number> or https://..."), include a short description of expected behavior
(assertion flow, re-authentication for passkey-based Cubes) and update the error
message to reference the tracking issue so callers see context; ensure the
comment references the function name authenticate and the parameters
rp_id/_credential_id for discoverability.

In `@coincube-gui/src/services/passkey/mod.rs`:
- Around line 186-188: The field channel is wrapped in Arc and suppresses clippy
for non-Send/Sync because PasskeyCeremonyChannel contains non-Sync mpsc
endpoints; since it's only used on the UI main thread, replace Arc with a
single-threaded pointer (e.g., use Rc or Rc<RefCell<PasskeyCeremonyChannel>>)
instead of Arc::new(...) to avoid the lint and make intent explicit, updating
imports and any closure captures that assume Arc, or alternatively add a short
doc comment above the channel field explaining why Arc is required and that the
usage is single-threaded so the clippy allow is safe (referencing
PasskeyCeremonyChannel and the channel field).
- Around line 264-271: The current error handling in CeremonyIpcMessage::Error
classifies failures by substring matching (e.g., checking
message.contains("cancelled") or "NotAllowedError") which is fragile; update the
IPC protocol and the CeremonyIpcMessage::Error variant to include a discrete
error code/enum (e.g., ErrorCode) or numeric code alongside the message, then
change the match in the handler to switch on that error code and map to
PasskeyError::Cancelled, PasskeyError::PrfNotSupported, or
PasskeyError::CeremonyFailed(message) accordingly so classification no longer
depends on brittle substrings.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b9bada8c-d52f-4d84-9871-578fd251cdc9

📥 Commits

Reviewing files that changed from the base of the PR and between d9386dc and d9973b9.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (50)
  • .env.example
  • coincube-core/Cargo.toml
  • coincube-core/src/border_wallet/mod.rs
  • coincube-core/src/descriptors/mod.rs
  • coincube-core/src/signer.rs
  • coincube-gui/Cargo.toml
  • coincube-gui/src/app/breez/client.rs
  • coincube-gui/src/app/breez/mod.rs
  • coincube-gui/src/app/cache.rs
  • coincube-gui/src/app/mod.rs
  • coincube-gui/src/app/settings/mod.rs
  • coincube-gui/src/app/state/liquid/overview.rs
  • coincube-gui/src/app/state/liquid/settings.rs
  • coincube-gui/src/app/state/mod.rs
  • coincube-gui/src/app/state/settings/general.rs
  • coincube-gui/src/app/state/settings/mod.rs
  • coincube-gui/src/app/state/vault/overview.rs
  • coincube-gui/src/app/state/vault/psbt.rs
  • coincube-gui/src/app/view/liquid/settings.rs
  • coincube-gui/src/app/view/message.rs
  • coincube-gui/src/app/view/mod.rs
  • coincube-gui/src/app/view/settings/backup.rs
  • coincube-gui/src/app/view/settings/general.rs
  • coincube-gui/src/app/view/settings/mod.rs
  • coincube-gui/src/app/view/vault/overview.rs
  • coincube-gui/src/app/view/vault/psbt.rs
  • coincube-gui/src/app/wallet.rs
  • coincube-gui/src/feature_flags.rs
  • coincube-gui/src/gui/tab.rs
  • coincube-gui/src/installer/descriptor.rs
  • coincube-gui/src/installer/message.rs
  • coincube-gui/src/installer/mod.rs
  • coincube-gui/src/installer/step/descriptor/editor/border_wallet_wizard.rs
  • coincube-gui/src/installer/step/descriptor/editor/key.rs
  • coincube-gui/src/installer/step/descriptor/editor/mod.rs
  • coincube-gui/src/installer/step/mnemonic.rs
  • coincube-gui/src/installer/step/share_xpubs.rs
  • coincube-gui/src/installer/view/mod.rs
  • coincube-gui/src/launcher.rs
  • coincube-gui/src/lib.rs
  • coincube-gui/src/loader.rs
  • coincube-gui/src/services/mod.rs
  • coincube-gui/src/services/passkey/macos.rs
  • coincube-gui/src/services/passkey/mod.rs
  • coincube-gui/src/signer.rs
  • coincube-gui/test_assets/global_settings.json
  • coincube-ui/src/component/hw.rs
  • docs/TRY.md
  • tests/tools/taproot_signer/README.md
  • tests/tools/taproot_signer/src/main.rs
✅ Files skipped from review due to trivial changes (9)
  • coincube-core/Cargo.toml
  • tests/tools/taproot_signer/src/main.rs
  • tests/tools/taproot_signer/README.md
  • docs/TRY.md
  • coincube-gui/src/lib.rs
  • .env.example
  • coincube-gui/src/app/state/settings/mod.rs
  • coincube-gui/test_assets/global_settings.json
  • coincube-gui/src/services/mod.rs
🚧 Files skipped from review as they are similar to previous changes (6)
  • coincube-gui/src/app/breez/mod.rs
  • coincube-gui/src/installer/step/descriptor/editor/mod.rs
  • coincube-gui/src/installer/mod.rs
  • coincube-gui/src/gui/tab.rs
  • coincube-gui/src/app/state/liquid/settings.rs
  • coincube-gui/src/installer/step/descriptor/editor/key.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants