Summary
Two Swift sites still orchestrate the mnemonic → seed → DIP-9 path → key derivation pipeline (and pull cleartext mnemonics across the FFI boundary) instead of persisting only Rust-returned key material. This violates the architectural rules in packages/swift-sdk/CLAUDE.md:
- "Do not build derivation paths in Swift."
- "Do not orchestrate multi-step derivation pipelines in Swift (mnemonic → seed → path → key → store)."
- "No pulling mnemonics or seeds across the FFI boundary so Swift can 'finish' an operation Rust already knows how to complete."
- "Identity key derivation + any action that uses a derived key belongs in
platform-wallet."
Surfaced as Major / Heavy-lift review findings on #3772; deferred from that PR because it is a cross-language refactor independent of that PR's network-scoping scope.
Site 1 — identity-key derivation in the persister callback
packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift — deriveAndStoreIdentityKey(...) (~L1803). The method resolves the wallet's network, fetches the mnemonic from Keychain, builds the DIP-9 identity-authentication path via KeyDerivation.getIdentityAuthenticationPath, and derives the private key in Swift, then writes it to Keychain.
Target shape: a platform-wallet FFI entry point that takes (wallet handle / wallet_id, identity_index, key_index, network) and returns (serialized_path, 32 private-key bytes) — derivation entirely Rust-side. Swift keeps only the sanctioned final step: writing the returned bytes to Keychain under PersistentPublicKey.privateKeyKeychainIdentifier.
Site 2 — enableNetwork re-feeds the mnemonic across FFI
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift — enableNetwork(_:) (~L744-759) calls WalletStorage().retrieveMnemonic(for: wallet.walletId) in a SwiftUI view and hands the cleartext mnemonic back to mgr.createWallet(mnemonic:network:name:). This is the exact anti-precedent named in CLAUDE.md and also widens the window where the cleartext phrase lives in a Swift String.
Target shape: a single FFI entry point, e.g. platform_wallet_enable_on_network(wallet_id, target_network), that resolves seed material entirely inside platform-wallet and registers the per-network sibling wallet. Swift only persists the resulting per-network Keychain entries (mnemonic + metadata blob) under the new scoped walletId.
Acceptance criteria
References
Summary
Two Swift sites still orchestrate the mnemonic → seed → DIP-9 path → key derivation pipeline (and pull cleartext mnemonics across the FFI boundary) instead of persisting only Rust-returned key material. This violates the architectural rules in
packages/swift-sdk/CLAUDE.md:Surfaced as Major / Heavy-lift review findings on #3772; deferred from that PR because it is a cross-language refactor independent of that PR's network-scoping scope.
Site 1 — identity-key derivation in the persister callback
packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift—deriveAndStoreIdentityKey(...)(~L1803). The method resolves the wallet's network, fetches the mnemonic from Keychain, builds the DIP-9 identity-authentication path viaKeyDerivation.getIdentityAuthenticationPath, and derives the private key in Swift, then writes it to Keychain.Target shape: a
platform-walletFFI entry point that takes(wallet handle / wallet_id, identity_index, key_index, network)and returns(serialized_path, 32 private-key bytes)— derivation entirely Rust-side. Swift keeps only the sanctioned final step: writing the returned bytes to Keychain underPersistentPublicKey.privateKeyKeychainIdentifier.Site 2 —
enableNetworkre-feeds the mnemonic across FFIpackages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift—enableNetwork(_:)(~L744-759) callsWalletStorage().retrieveMnemonic(for: wallet.walletId)in a SwiftUI view and hands the cleartext mnemonic back tomgr.createWallet(mnemonic:network:name:). This is the exact anti-precedent named inCLAUDE.mdand also widens the window where the cleartext phrase lives in a SwiftString.Target shape: a single FFI entry point, e.g.
platform_wallet_enable_on_network(wallet_id, target_network), that resolves seed material entirely insideplatform-walletand registers the per-network sibling wallet. Swift only persists the resulting per-network Keychain entries (mnemonic + metadata blob) under the new scoped walletId.Acceptance criteria
platform-wallet(Rust) entry point(s) performing the full derivation/registration internally, exposed throughrs-platform-wallet-ffi.deriveAndStoreIdentityKeyreduced to: call FFI → write returned key bytes to Keychain. NoMnemonic.toSeed/KeyDerivation.*/ path-building in Swift.enableNetworkno longer callsretrieveMnemonic; it calls the new enable-on-network FFI and persists the Rust-returned per-network entries.WalletStorage().retrieveMnemonic(...)calls in views/handlers that re-feed an FFI re-derivation.References
packages/swift-sdk/CLAUDE.md