Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,14 @@ public final class KeychainSigner: Signer, @unchecked Sendable {
/// - network: forwarded to `dash_sdk_signer_create_from_private_key`
/// for WIF address derivation; does not affect signature output.
/// - keychain: defaults to `KeychainManager.shared`.
/// - storage: mnemonic source for the resolver-based signing
/// paths. Defaults to a fresh `WalletStorage()` — overridable
/// for tests.
public init(
modelContainer: ModelContainer,
network: Network = .testnet,
keychain: KeychainManager = .shared
keychain: KeychainManager = .shared,
storage: WalletStorage = WalletStorage()
Comment thread
coderabbitai[bot] marked this conversation as resolved.
) {
self.modelContainer = modelContainer
self.network = network
Expand All @@ -206,7 +210,7 @@ public final class KeychainSigner: Signer, @unchecked Sendable {
// One resolver per signer instance. Cheap to keep around —
// it's just an opaque handle + a Swift-side `WalletStorage`
// reference. Used by the platform-address signing branch.
self.mnemonicResolver = MnemonicResolver()
self.mnemonicResolver = MnemonicResolver(storage: storage)

// Hand Rust an opaque NON-owning pointer to self. The
// Swift owner is responsible for keeping `self` alive
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,38 @@ final class IdentityResolverSignIntegrationTests: XCTestCase {
private let mnemonic =
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"

/// In-memory `WalletStorage` so the resolver path under test never
/// touches the real macOS Keychain — CI runners often have a locked
/// or permission-denied keychain session (`keychainError(-60008)` /
/// `(-61)`), which is an environment failure, not what these tests
/// cover. The lock mirrors the real storage's thread-safety: the
/// resolver trampoline reads from a Rust worker thread.
private final class InMemoryWalletStorage: WalletStorage {
private var mnemonics: [Data: Data] = [:]
private let lock = NSLock()

override func storeMnemonic(_ mnemonic: String, for walletId: Data) throws {
lock.lock()
defer { lock.unlock() }
mnemonics[walletId] = Data(mnemonic.utf8)
}

override func retrieveMnemonicUTF8Bytes(for walletId: Data) throws -> Data {
lock.lock()
defer { lock.unlock() }
guard let data = mnemonics[walletId], !data.isEmpty else {
throw WalletStorageError.mnemonicNotFound
}
return data
}

override func deleteMnemonic(for walletId: Data) throws {
lock.lock()
defer { lock.unlock() }
mnemonics[walletId] = nil
}
}

/// A consistent breadcrumb (the path derives to the row's pubkey) signs via
/// the resolver: the seed is read from the Keychain, the key is derived on
/// demand, the binding accepts it, and a 65-byte ECDSA signature comes back —
Expand All @@ -34,7 +66,7 @@ final class IdentityResolverSignIntegrationTests: XCTestCase {
XCTAssertEqual(pubkey.count, 33, "compressed secp256k1 pubkey")

// Seed the resolver's source: the mnemonic in WalletStorage, keyed by walletId.
let storage = WalletStorage()
let storage = InMemoryWalletStorage()
try storage.storeMnemonic(mnemonic, for: walletId)
defer { try? storage.deleteMnemonic(for: walletId) }

Expand All @@ -48,7 +80,7 @@ final class IdentityResolverSignIntegrationTests: XCTestCase {
ctx.insert(row)
try ctx.save()

let signer = KeychainSigner(modelContainer: container, network: .testnet)
let signer = KeychainSigner(modelContainer: container, network: .testnet, storage: storage)
let message = Data("identity state transition".utf8)
let result = signer.signIdentityKeyOnDemand(publicKey: pubkey, keyType: 0, data: message)

Expand All @@ -75,7 +107,7 @@ final class IdentityResolverSignIntegrationTests: XCTestCase {
let wrongPath = try KeyDerivation.getIdentityAuthenticationPath(
network: .testnet, identityIndex: 9, keyIndex: 9)

let storage = WalletStorage()
let storage = InMemoryWalletStorage()
try storage.storeMnemonic(mnemonic, for: walletId)
defer { try? storage.deleteMnemonic(for: walletId) }

Expand All @@ -88,7 +120,7 @@ final class IdentityResolverSignIntegrationTests: XCTestCase {
ctx.insert(row)
try ctx.save()

let signer = KeychainSigner(modelContainer: container, network: .testnet)
let signer = KeychainSigner(modelContainer: container, network: .testnet, storage: storage)
let result = signer.signIdentityKeyOnDemand(
publicKey: pubkey, keyType: 0, data: Data("x".utf8))

Expand Down
Loading