feat(sdk)!: optional BIP-39 passphrase through the mnemonic resolver and wallet creation - #4615
Draft
PastaPastaPasta wants to merge 2 commits into
Draft
Conversation
… resolver and wallet creation
Every seed derivation used to hardcode the empty passphrase: key-wallet's Mnemonic wallet variant calls to_seed(""), and the Swift-to-Rust mnemonic resolver vtable could only return the mnemonic. A host that wanted a BIP-39 passphrase ("25th word") wallet had no way to create one that would sign after relaunch.
The resolver callback now also returns the wallet's stored passphrase (length 0 = none). A single resolve_seed helper in rs-sdk-ffi is the only consumer of the vtable, so the passphrase cannot be honoured on one derivation path and dropped on another; the core signer, the resolver-driven sign FFI, identity derive-and-persist, identity discovery/preview/loading, and the at-slot preview all route through it. create_wallet_from_mnemonic takes a passphrase and builds a Seed-typed key-wallet (same network-scoped id as the mnemonic variant for the empty passphrase, verified by test). New FFI exports: platform_wallet_manager_create_wallet_from_mnemonic_with_passphrase_and_birth_height and an any-language platform_wallet_mnemonic_to_seed.
Swift SDK: WalletStorage stores the passphrase as its own per-wallet Keychain item next to the mnemonic and folds it into the seed-binding stamp; MnemonicResolver fills the passphrase buffer; PlatformWalletManager.createWallet(mnemonic:seedPassphrase:) on both overloads; deleteWallet removes the passphrase item; Mnemonic.toSeed uses the any-language FFI. The throwing passphrase parameter on WalletManager.addWalletAndSerialize is removed.
BREAKING: MnemonicResolveCallback gains three trailing parameters; PlatformWalletManager::create_wallet_from_mnemonic gains a passphrase argument. The Android JNI trampoline reports no passphrase, so Kotlin behaviour is unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
|
Warning Review limit reachedNext included review available in 48 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (26)
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. Comment |
create_wallet_from_mnemonic now calls seed_from_mnemonic instead of repeating it; ResolveSeedError derives thiserror; the resolver sign FFI maps errors to tags in one table; WalletStorage's per-wallet accessors share one set of Keychain primitives; MaskedMnemonicUTF8 is renamed MaskedSecretUTF8 now that it also masks the passphrase; Mnemonic.toSeed reuses withOptionalPassphraseCString. No behaviour change; public Swift surface and FFI symbols are identical. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.
Issue being fixed or feature implemented
The iOS Dash Wallet wants to offer an optional BIP-39 passphrase ("25th word") as an advanced, hidden-by-default feature. The SDK could not support it: every seed derivation hardcoded the empty passphrase. key-wallet's
Mnemonicwallet variant callsto_seed("")(rust-dashcore #747 removedMnemonicWithPassphrase), and the Swift→RustMnemonicResolveCallbackcould only hand back the mnemonic, so every resolver consumer inrs-sdk-ffi/rs-platform-wallet-ffiderived withto_seed("")too. A wallet created from a raw seed viacreateWallet(seed:)would have had the right keys but no Keychain-backed resolver path, so it could not sign after a relaunch.This is phase 0 (the SDK half) of the app feature; the app-side work follows in dashwallet-ios.
What was done?
Rust
rs-sdk-ffi/src/mnemonic_resolver.rs:MnemonicResolveCallbackgainsout_passphrase_utf8/out_passphrase_capacity/out_passphrase_len(length0= no passphrase). NewPASSPHRASE_RESOLVER_BUFFER_CAPACITY. Newresolve_seed(handle, wallet_id) -> Zeroizing<[u8; 64]>is now the only consumer of the vtable: result-code mapping, length/UTF-8 checks, any-language parse,to_seed(passphrase), zeroization, in one place.ResolveSeedErrorcarries the failure kind.to_seed("")block routes through it:MnemonicResolverCoreSigner,dash_sdk_sign_with_mnemonic_resolver_and_path,dash_sdk_derive_and_persist_identity_keys,resolve_seed_from_resolver[_classified](which feeds identity discovery / preview / loading / provider keys / shielded / address private key), anddash_sdk_derive_identity_key_at_slot_with_resolver.platform-wallet:PlatformWalletManager::create_wallet_from_mnemonictakespassphrase: &strand builds aSeed-typed key-wallet fromto_seed(passphrase). The network-scoped wallet id is computed from the root xpub for both variants, so existing (empty-passphrase) wallets keep their ids — pinned by a new test. New free fnplatform_wallet::seed_from_mnemonic.platform-wallet-ffi: new exportsplatform_wallet_manager_create_wallet_from_mnemonic_with_passphrase_and_birth_height(NULL /""passphrase ≡ existing export) andplatform_wallet_mnemonic_to_seed(any-language; key-wallet-ffi'smnemonic_to_seedis English-only). Existing mnemonic create exports are unchanged wrappers.rs-unified-sdk-jni: trampoline reports passphrase length 0 (TODO(seed-passphrase)), so Android behaviour is unchanged.Swift SDK
WalletStorage: per-walletwallet.passphrase.<walletIdHex>Keychain item (storePassphrase/retrievePassphrase[UTF8Bytes]/passphraseAvailability/hasPassphrase/deletePassphrase), same accessibility class as the mnemonic. An empty passphrase is rejected; absence of the item is the discriminator.mnemonicKeychainStampfolds the passphrase item in so a changed passphrase invalidates the cached seed-binding marker.MnemonicResolver: fills the passphrase buffer (masked while idle, same as the mnemonic); an unreadable passphrase item fails closed.PlatformWalletManager.createWallet(mnemonic:seedPassphrase:network:…)on the sync and async overloads;deleteWalletdeletes the passphrase item before the mnemonic.Mnemonic.toSeed(mnemonic:passphrase:)now calls the any-language FFI.passphrase:parameter onWalletManager.addWalletAndSerializeand the stale README examples that documented a passphrase API that did not exist.How Has This Been Tested?
cargo test --libforrs-sdk-ffi(330 pass),platform-wallet-ffi(329 pass),rs-unified-sdk-jni(37 pass); targetedplatform-walletlifecycle tests pass.cargo clippy/cargo fmt --checkclean on the four touched crates.abandon…aboutwith""andTREZOR) throughresolve_seedandplatform_wallet_mnemonic_to_seed; a passphrase resolver signs with the passphrase-derived key and the empty-passphrase key is refused asPUBKEY_MISMATCH; mnemonic-create id == seed-create id, passphrase changes the id,""keeps today's id.build_ios.sh --target mac --profile dev), thenswift build --build-testsand the full hermetic suite: 429 tests, 0 failures. NewSeedPassphraseResolverTestscoverstoSeedvectors + Japanese phrase, resolver signing with/without a stored passphrase, passphrase deletion, and the empty-passphrase rejection;PlatformWalletCreateWalletTestsasserts the passphrase reaches the native create verbatim.Breaking Changes
MnemonicResolveCallback(C ABI) has three new trailing parameters. Every in-tree implementor is updated (Swift trampoline, JNI trampoline, all test callbacks); out-of-tree hosts must add them and set*out_passphrase_len = 0when they have no passphrase.PlatformWalletManager::create_wallet_from_mnemonic(Rust) takes apassphrase: &strargument.WalletManager.addWalletAndSerializeno longer has apassphrase:parameter (it only ever threw on a non-empty value).Checklist:
For repository code-owners and collaborators only
🤖 Generated with Claude Code