-
Notifications
You must be signed in to change notification settings - Fork 55
fix(platform-wallet): snapshot identity cross-check + typed persister-load errors (#3980 follow-ups) #3984
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(platform-wallet): snapshot identity cross-check + typed persister-load errors (#3980 follow-ups) #3984
Changes from all commits
e66fbf5
51cd9c9
9879a9e
8999eea
161d8b8
40806d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,6 +42,14 @@ pub enum CorruptKind { | |
| /// One or more manifest `account_xpub` bytes failed to parse as a | ||
| /// well-formed extended public key. | ||
| MalformedXpub, | ||
| /// The carried [`ManagedWalletInfo`] snapshot does not describe the | ||
| /// persisted row it is attached to: its `wallet_id`/`network` differ | ||
| /// from the row, or its account set diverges from the row's account | ||
| /// manifest. This is a wrong-row/structurally-inconsistent snapshot — | ||
| /// distinct from unreadable bytes ([`Self::DecodeError`]). | ||
| /// | ||
| /// [`ManagedWalletInfo`]: key_wallet::wallet::managed_wallet_info::ManagedWalletInfo | ||
| SnapshotIdentityMismatch, | ||
| /// Any other structural decode / projection failure surfaced by the | ||
| /// persister. The string is a structural projection — never a raw | ||
| /// row byte slice or a hex-encoded key. | ||
|
|
@@ -53,6 +61,9 @@ impl std::fmt::Display for CorruptKind { | |
| match self { | ||
| Self::MissingManifest => f.write_str("missing account manifest"), | ||
| Self::MalformedXpub => f.write_str("malformed account xpub"), | ||
| Self::SnapshotIdentityMismatch => { | ||
| f.write_str("snapshot does not match its persisted row") | ||
| } | ||
|
Comment on lines
44
to
+66
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Suggestion: SnapshotIdentityMismatch collapses three distinct failure modes into one undifferentiated variant The new source: ['claude']
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still present at
Comment on lines
44
to
+66
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Suggestion: SnapshotIdentityMismatch collapses three failure modes and loses diagnostic granularity Carried forward from the prior review (161d8b8) — verified byte-identical at HEAD. The enum-over-string design is otherwise the right call (match-able and does not leak snapshot bytes) — carrying a small discriminant sub-enum ( source: ['claude'] |
||
| Self::DecodeError(s) => write!(f, "decode error: {s}"), | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Suggestion: Snapshot/manifest cross-check has defense-in-depth gaps in filtered families and empty-set case
snapshot_accounts_match_manifestcompares only the set ofAccountTypevariants after filtering outProviderOperatorKeys,ProviderPlatformKeys, andPlatformPayment { .. }(line 345-352). The filter is justified — those are the known enumeration asymmetries betweenWallet::all_accountsandManagedWalletInfo::all_managed_accounts— but three residual gaps remain in the very scenario this function was added to defend against (a wrong-row / tampered snapshot):info.wallet_idis a persisted field, not a re-derivation. A row with matchingwallet_id/network/account-type set but attacker-controlled per-account xpubs will pass, andwallet_info = *infothen consumes those pools into the SPV watch set whilebuild_watch_only_walletuses the manifest's xpubs — divergent detection/attribution surfaces without any signing-side failure.PlatformPaymentis filtered on the manifest side (where it can appear) andProviderOperatorKeys/ProviderPlatformKeyson the snapshot side (where they can appear). A snapshot with an extraProviderOperatorKeysthe manifest wouldn't attest to, or a manifest with an extraPlatformPaymentthe snapshot wouldn't attest to, is invisible because each is filtered on exactly the side that would have caught it.PlatformPaymententry —build_watch_only_walletonly requires non-empty) and the snapshot's comparable set is also empty,∅ == ∅returnstrueand any wrong-row snapshot withwallet_id/networkset to match sails through. Unlikely on real wallets (which always carry a Standard account), but silently defeats the guard when it occurs.All three sit behind the documented trust boundary (the persister is trusted as-is, tracked follow-up for cryptographic manifest binding to
wallet_id), so none is a new hole opened by this PR — but each undermines the specific narrowing this PR claims to add. Consider requiring at least one comparable account on both sides, comparing per-account xpubs (or a hash) for the shared families, and rejecting snapshot-only families that aren't in the manifest.source: ['claude']
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still present at
40806d6.