fix(sdk): late-init BLE signer must use the identity AK, not a random keypair (fixes bilateral σ verification) - #629
Merged
Conversation
…andom keypair
The bilateral σ_A/σ_B signatures are signed by, and verified against, the device
genesis-v2 identity AK — the same key the QR/BLE pairing pins as the contact AK
(derive_device_ak_keypair(wallet_seed, genesis, slot=0, policy); spec TRACEABILITY.md,
ADR 0002 row 9). There is no separate bilateral/session signing key.
But `ensure_bluetooth_manager_and_sync_contact` minted a RANDOM `SignatureKeyPair::new()`
for the BilateralTransactionManager it late-registers. When a QR contact is added before
`init_dsm_sdk` registers the canonical AK-keyed manager, this fallback wins the global
signer `OnceLock` race (first-writer-wins) and the canonical manager is discarded
("already registered"). σ is then signed with a random key the peer never pinned, so
every bilateral transfer fails σ verification
("invalid counterparty signature (σ_B) — response lost").
Fix: derive the signing keypair from the cached wallet seed exactly as the canonical path
(init.rs), and REFUSE to register (Ok(false)) when the seed is locked — rather than
minting a non-AK key that could hijack the signer slot. Both managers then hold the
byte-identical identity AK, so whichever wins the race signs σ with the pinned AK.
Exposed by the rows 6/7 fix (#627): once σ is correctly verified against the pinned AK
instead of the overwritten wire key, the random-signer mismatch became fatal. The old
overwrite masked it by promoting the random wire key into the trust-root slot.
Hardware-proven (8XK<->9FF, release .so): before the fix σ_B failed
("invalid counterparty signature (σ_B) — response lost"); after, σ_B verifies and the
flow advances through confirm + §11.1 per-step-EK verification (PASS). Full offline-bearer
settlement is unrelated and still needs the Pico anchor appliance (fails closed at
MissingRelease; balances unchanged).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
The bilateral σ_A/σ_B signatures must be signed by — and verified against — the device's genesis-v2 identity AK, the same key the QR/BLE pairing pins as the contact AK (
derive_device_ak_keypair(wallet_seed, genesis, slot=0, policy); specTRACEABILITY.md, ADR 0002 row 9). There is no separate bilateral/session signing key and no cert layer on the σ path.But
ensure_bluetooth_manager_and_sync_contact(dsm_sdk/src/bluetooth/mod.rs) minted a randomSignatureKeyPair::new()for theBilateralTransactionManagerit late-registers. When a QR contact is added beforeinit_dsm_sdkregisters the canonical AK-keyed manager, this fallback wins the global-signerOnceLockrace (first-writer-wins), and the canonical manager is discarded ("already registered"). σ is then signed with a random key the peer never pinned → every bilateral transfer fails σ verification:invalid counterparty signature (σ_B) — response lost.Why it surfaced now
The rows 6/7 fix (#627) made the receiver verify σ against the pinned AK instead of the wire-overwritten key. The old behavior — overwriting
contact.public_keywith the wire signing key on every prepare — masked this bug by promoting the random wire key into the trust-root slot so σ verified tautologically. With rows 6/7 correct, the random-signer mismatch became fatal.This was found by a full source provenance trace of all three key roles (σ-signer, wire key, QR-pinned AK), which showed they are all meant to be the byte-identical identity AK.
The fix
Derive the signing keypair from the cached wallet seed exactly as the canonical path (
init.rs), and refuse to register (Ok(false)) when the seed is locked — rather than minting a non-AK key that can hijack the signer slot. Both managers then hold the byte-identical identity AK, so whichever wins theOnceLockrace signs σ with the pinned AK.Hardware evidence (8XK ↔ 9FF, release
.so)Direct before/after on the rig:
Invalid operation: invalid counterparty signature (σ_B) — response lost(transfer dies at step 2).Session moved to Accepted phase→send_bilateral_confirm ... ConfirmPending→ receiver§11.1 per-step EK A-side verification PASS(reaches step 3, post-crypto). No"signing AK differs"warning — the wire signing key now equals the pinned AK.Scope / evidence boundary
main.MissingReleasebecause no anchor is connected; balances stay unchanged.