Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion .platform-ref
Original file line number Diff line number Diff line change
@@ -1 +1 @@
e72022ab45fc0f30a286104714ae6241bc540901
1721ca7a8bc962c5c1e1559f65ff433454f57619
8 changes: 8 additions & 0 deletions DashWallet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
E012FFCC6C3142B4B5869B72 /* ShieldedRecoveryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2ADF29244E649D8BE05FD93 /* ShieldedRecoveryController.swift */; };
4C9AC6D2EC13450DAC68FC90 /* ShieldedBalanceControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 930BFA9B069540D4A5CAD79A /* ShieldedBalanceControllerTests.swift */; };
525045ECC4BF30BBCB09E13C /* PlatformBalanceReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17921340EDF4455B62461AF8 /* PlatformBalanceReaderTests.swift */; };
7A34000230A5000000000002 /* DashModelContainer+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A34000130A5000000000001 /* DashModelContainer+Testing.swift */; };
C693C63758228F630D39F00E /* HomeBalancePresentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F9884169298187E321C8804 /* HomeBalancePresentationTests.swift */; };
BCC98012E278206FC08FA8E3 /* PlatformBalanceControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71029F5975636F1DCFFFF85 /* PlatformBalanceControllerTests.swift */; };
FA87A306BF0E476385DA1C66 /* ShieldedBalanceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5A428A4E24408592C674A0 /* ShieldedBalanceController.swift */; };
Expand Down Expand Up @@ -1024,6 +1025,7 @@
7A32000230A3000000000002 /* PooledSendableBalanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A32000130A3000000000001 /* PooledSendableBalanceTests.swift */; };
7A33000230A3000000000002 /* ContactPaymentReviewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A33000130A3000000000001 /* ContactPaymentReviewTests.swift */; };
7A30002230A2000000000022 /* CrowdNodeOwnershipTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A30002130A2000000000021 /* CrowdNodeOwnershipTests.swift */; };
7A33000230A4000000000002 /* KeyToolsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A33000130A4000000000001 /* KeyToolsTests.swift */; };
7A31000230A2000000000002 /* CoinJoinMoveDestinationPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A31000130A2000000000001 /* CoinJoinMoveDestinationPolicyTests.swift */; };
7A30001230A1000000000012 /* StuckAssetLockRetryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A30001130A1000000000011 /* StuckAssetLockRetryTests.swift */; };
BABA1000000000000000001F /* AssetLockDiagnosticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BABA1000000000000000001E /* AssetLockDiagnosticsTests.swift */; };
Expand Down Expand Up @@ -2253,6 +2255,7 @@
F2ADF29244E649D8BE05FD93 /* ShieldedRecoveryController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShieldedRecoveryController.swift; sourceTree = "<group>"; };
930BFA9B069540D4A5CAD79A /* ShieldedBalanceControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShieldedBalanceControllerTests.swift; sourceTree = "<group>"; };
17921340EDF4455B62461AF8 /* PlatformBalanceReaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformBalanceReaderTests.swift; sourceTree = "<group>"; };
7A34000130A5000000000001 /* DashModelContainer+Testing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DashModelContainer+Testing.swift"; sourceTree = "<group>"; };
6F9884169298187E321C8804 /* HomeBalancePresentationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeBalancePresentationTests.swift; sourceTree = "<group>"; };
D71029F5975636F1DCFFFF85 /* PlatformBalanceControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformBalanceControllerTests.swift; sourceTree = "<group>"; };
9F5A428A4E24408592C674A0 /* ShieldedBalanceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShieldedBalanceController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3334,6 +3337,7 @@
7A32000130A3000000000001 /* PooledSendableBalanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PooledSendableBalanceTests.swift; sourceTree = "<group>"; };
7A33000130A3000000000001 /* ContactPaymentReviewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPaymentReviewTests.swift; sourceTree = "<group>"; };
7A30002130A2000000000021 /* CrowdNodeOwnershipTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrowdNodeOwnershipTests.swift; sourceTree = "<group>"; };
7A33000130A4000000000001 /* KeyToolsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyToolsTests.swift; sourceTree = "<group>"; };
7A31000130A2000000000001 /* CoinJoinMoveDestinationPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinJoinMoveDestinationPolicyTests.swift; sourceTree = "<group>"; };
7A30001130A1000000000011 /* StuckAssetLockRetryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StuckAssetLockRetryTests.swift; sourceTree = "<group>"; };
BABA1000000000000000001E /* AssetLockDiagnosticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetLockDiagnosticsTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -7682,6 +7686,7 @@
B7C072AA26C7000000000002 /* DWContestedNameStatusServiceTests.swift */,
930BFA9B069540D4A5CAD79A /* ShieldedBalanceControllerTests.swift */,
17921340EDF4455B62461AF8 /* PlatformBalanceReaderTests.swift */,
7A34000130A5000000000001 /* DashModelContainer+Testing.swift */,
6F9884169298187E321C8804 /* HomeBalancePresentationTests.swift */,
D71029F5975636F1DCFFFF85 /* PlatformBalanceControllerTests.swift */,
5798C63088F44013AEC5D85B /* ShieldedRecoveryControllerTests.swift */,
Expand All @@ -7697,6 +7702,7 @@
7A32000130A3000000000001 /* PooledSendableBalanceTests.swift */,
7A33000130A3000000000001 /* ContactPaymentReviewTests.swift */,
7A30002130A2000000000021 /* CrowdNodeOwnershipTests.swift */,
7A33000130A4000000000001 /* KeyToolsTests.swift */,
7A31000130A2000000000001 /* CoinJoinMoveDestinationPolicyTests.swift */,
7A30001130A1000000000011 /* StuckAssetLockRetryTests.swift */,
BABA1000000000000000001E /* AssetLockDiagnosticsTests.swift */,
Expand Down Expand Up @@ -11056,6 +11062,7 @@
B7C072AA26C7000000000001 /* DWContestedNameStatusServiceTests.swift in Sources */,
4C9AC6D2EC13450DAC68FC90 /* ShieldedBalanceControllerTests.swift in Sources */,
525045ECC4BF30BBCB09E13C /* PlatformBalanceReaderTests.swift in Sources */,
7A34000230A5000000000002 /* DashModelContainer+Testing.swift in Sources */,
C693C63758228F630D39F00E /* HomeBalancePresentationTests.swift in Sources */,
BCC98012E278206FC08FA8E3 /* PlatformBalanceControllerTests.swift in Sources */,
F0CAB696BC154E7EB3EC221D /* ShieldedRecoveryControllerTests.swift in Sources */,
Expand All @@ -11071,6 +11078,7 @@
7A32000230A3000000000002 /* PooledSendableBalanceTests.swift in Sources */,
7A33000230A3000000000002 /* ContactPaymentReviewTests.swift in Sources */,
7A30002230A2000000000022 /* CrowdNodeOwnershipTests.swift in Sources */,
7A33000230A4000000000002 /* KeyToolsTests.swift in Sources */,
7A31000230A2000000000002 /* CoinJoinMoveDestinationPolicyTests.swift in Sources */,
7A30001230A1000000000012 /* StuckAssetLockRetryTests.swift in Sources */,
BABA1000000000000000001F /* AssetLockDiagnosticsTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -593,31 +593,6 @@ final class SwiftDashSDKHost {
return persistedMnemonics().contains { Mnemonic.normalizePhrase($0.mnemonic) == typed }
}

/// Throwaway key-wallet stack (manager + wallet) for path-based key
/// derivation, built from the persisted mnemonic of the host's active
/// wallet. Separate from the running `PlatformWalletManager` — key
/// derivation lives in the `WalletManager`/`Wallet` FFI surface, which
/// the runtime handles don't expose. The instances derive keys locally
/// (no networking, no persistence); drop them when done.
///
/// Consumers: the masternode Owner/Voting key deriver and CrowdNode
/// message signing. Returns nil when no wallet is bound/running or the
/// mnemonic can't be read (logged).
func derivationWallet() -> (manager: WalletManager, wallet: Wallet, walletId: Data)? {
guard let hostWalletId = wallet?.walletId, let network = runningNetwork else {
Self.logger.warning("🪺 HOST :: derivationWallet — no bound wallet/network")
return nil
}
guard let mnemonic = try? WalletStorage().retrieveMnemonic(for: hostWalletId),
let manager = try? WalletManager(network: network),
let walletId = try? manager.addWallet(mnemonic: mnemonic),
let derivationWallet = (try? manager.getWallet(id: walletId)) ?? nil else {
Self.logger.error("🪺 HOST :: derivationWallet — key-wallet bootstrap failed")
return nil
}
return (manager: manager, wallet: derivationWallet, walletId: walletId)
}

// MARK: - Lifecycle

enum HostError: LocalizedError {
Expand Down Expand Up @@ -709,14 +684,7 @@ final class SwiftDashSDKHost {
case WalletStorageError.mnemonicNotFound:
return (.notFound, nil)
case SwiftDashSDKWalletDeletionError.invalidMnemonic,
SwiftDashSDKWalletDeletionError.unrecognizedWalletNetwork,
KeyWalletError.invalidMnemonic,
KeyWalletError.invalidInput:
// The resolver derives ids by constructing a wallet from the
// stored phrase; these two `KeyWalletError` cases are that
// construction refusing material that passed word validation.
// Its other cases (allocation, network, state) say nothing about
// the keys and stay a store failure.
SwiftDashSDKWalletDeletionError.unrecognizedWalletNetwork:
return (.unclassifiable, nil)
case HostError.mnemonicRoundTripMismatch:
return (.idMismatch, nil)
Expand Down Expand Up @@ -1114,7 +1082,7 @@ final class SwiftDashSDKHost {
do {
// This is the same deterministic id contract used by addWallet's
// duplicate guard and PlatformWalletManager.createWallet.
walletId = try Wallet(mnemonic: mnemonic, network: network).id
walletId = try Mnemonic.walletId(for: mnemonic, network: network)
} catch {
Self.logger.error("🪺 HOST :: walletId derivation failed: \(String(describing: error), privacy: .public)")
throw HostError.walletCreationFailed(error)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,13 +273,6 @@ final class SwiftDashSDKKeyMigrator: NSObject {
}

do {
let seed = try Mnemonic.toSeed(mnemonic: mnemonic)
guard seed.count == 64, try Mnemonic.toSeed(mnemonic: mnemonic) == seed else {
logger.error("🔑 KEYMIG :: \(walletID, privacy: .public) seed sanity check failed")
failed += 1
hadFailure = true
continue
}
let sdkWalletId = try createWalletOnHost(
mnemonic: mnemonic,
network: network,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
// `findLastPotentialWordsOfMnemonicForPassphrase:…`,
// DSBIP39Mnemonic.m:436–589), composed from SwiftDashSDK primitives:
// `Mnemonic.wordList` (candidate enumeration), `Mnemonic.validate`
// (BIP-39 checksum), `Mnemonic.toSeed` + `key_wallet_derive_address_from_seed`
// (candidate → first BIP-44 mainnet address), plus `InsightClient`
// (BIP-39 checksum), `Mnemonic.firstReceiveAddress` (candidate → first
// BIP-44 mainnet address), plus `InsightClient`
// (on-chain address-existence) and `DamerauLevenshtein` (typo backup).
//
// Algorithm (single missing/incorrect word of a 12-word phrase):
Expand All @@ -30,10 +30,8 @@
// DashSync's per-language decode; extra candidates are Insight-gated.
// 4. Two-missing concurrency is 4 (DashSync: CPU−1) — gentler on Insight.
// 5. Distance backup ranks ALL checksum-valid words, not only those whose
// seed derivation succeeded. Identical for English; for non-English
// phrases the current key-wallet FFI (`mnemonic_to_seed`) is
// English-hardcoded, so derivation fails and on-chain confirmation is
// unavailable — the distance backup still works there.
// address derivation succeeded. Derivation accepts every SDK language,
// so this differs from DashSync only if a derivation fails.
// 6. A stopped scan still completes (with whatever was accumulated):
// keeps the stop-flag box's lifetime airtight; the VC has already
// dismissed itself on cancel, so the extra completion is invisible.
Expand Down Expand Up @@ -159,13 +157,12 @@ enum PhraseRepairEngine {
typealias ProgressTick = (Float) async -> Bool

/// Seam for unit tests; `.default` wires the real Insight client and
/// the SwiftDashSDK/FFI derivation chain.
/// the SwiftDashSDK address derivation.
struct Dependencies {
var insight: InsightAddressQuerying
/// Checksum-valid candidate phrase → first BIP-44 external address
/// (account 0, mainnet). `nil` when derivation fails — today that
/// means a non-English phrase (the FFI's `mnemonic_to_seed` is
/// English-hardcoded); such candidates skip on-chain confirmation.
/// (account 0, mainnet). `nil` when derivation fails. Such candidates
/// skip on-chain confirmation.
var deriveFirstAddress: (String) -> String?

static let `default` = Dependencies(insight: InsightClient(),
Expand Down Expand Up @@ -442,39 +439,12 @@ enum PhraseRepairEngine {
return found
}

// MARK: Address derivation (SDK + FFI)
// MARK: Address derivation (SDK)

/// m/44'/5'/0'/0/0 — first external address of account 0, mainnet
/// Checksum-valid phrase → its first receive address on mainnet
/// (hardcoded, parity with `[DSChain mainnet]` in DSBIP39Mnemonic.m:542).
private static let firstAddressPath: String? =
try? KeyDerivation.getBIP44PaymentPath(network: .mainnet,
accountIndex: 0,
isChange: false,
addressIndex: 0)

/// Checksum-valid phrase → BIP-39 seed (nil passphrase) → address via
/// the `key_wallet_derive_address_from_seed` C FFI (re-exported through
/// SwiftDashSDK; result freed with `string_free`, its documented pair).
/// Returns nil on any failure — notably non-English phrases, which the
/// FFI's English-hardcoded `mnemonic_to_seed` rejects.
/// Returns nil on any failure.
static func defaultDeriveFirstAddress(phrase: String) -> String? {
guard let path = firstAddressPath,
let seed = try? Mnemonic.toSeed(mnemonic: phrase),
seed.count == 64 else {
return nil
}

var address: String?
seed.withUnsafeBytes { (rawBuffer: UnsafeRawBufferPointer) in
guard let base = rawBuffer.bindMemory(to: UInt8.self).baseAddress else { return }
path.withCString { cPath in
guard let cAddress = key_wallet_derive_address_from_seed(base,
Network.mainnet.ffiValue,
cPath) else { return }
address = String(cString: cAddress)
string_free(cAddress)
}
}
return address
try? Mnemonic.firstReceiveAddress(for: phrase, network: .mainnet)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,6 @@ final class SwiftDashSDKWalletCreator: NSObject {
}

do {
// Determinism + length sanity check (matches migrator).
let seed = try Mnemonic.toSeed(mnemonic: mnemonic)
guard seed.count == 64 else {
logger.error("\(label, privacy: .public): seed length invalid: \(seed.count, privacy: .public)")
return
}

let walletId = try createWalletOnHost(
mnemonic: mnemonic,
network: appNetwork,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,7 @@ import SwiftDashSDK

// MARK: - WalletBalance

/// Wallet balance snapshot. Defined locally rather than re-exporting
/// SwiftDashSDK's `KeyWalletTypes.Balance` because that struct references
/// an internal `FFIBalance` type, which makes it unusable as a `public`
/// property type from this module. The four fields map directly to
/// Wallet balance snapshot. The four fields map directly to
/// `SPVWalletEventsHandler.onBalanceUpdated`'s callback parameters.
public struct WalletBalance: Equatable, Sendable {
public let confirmed: UInt64
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,11 @@ enum SwiftDashSDKStoredWalletNetworkResolver {
}
var ids: [Network: Data] = [:]
for network in storableNetworks {
ids[network] = try SwiftDashSDK.Wallet(
mnemonic: mnemonic,
network: network
).id
do {
ids[network] = try Mnemonic.walletId(for: mnemonic, network: network)
} catch {
throw SwiftDashSDKWalletDeletionError.invalidMnemonic
}
}
return ids
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ final class MasternodeVoterRegistry {
/// Outcome of a votable-node resolution.
///
/// `mayBeIncomplete` says the answer cannot be trusted as exhaustive: the
/// voting address pool was read from the degraded fallback (gap-limit
/// deep) *and* at least one active registration went unmatched, so a node
/// this wallet really can vote with may be missing from `nodes`. The UI
/// says so rather than presenting a short list as the full picture.
/// running wallet has no provider voting account *and* at least one active
/// registration went unmatched, so a node this wallet really can vote with
/// may be missing from `nodes`. The UI says so rather than presenting a
/// short list as the full picture.
struct Resolution {
let nodes: [VoterNode]
let mayBeIncomplete: Bool
Expand Down Expand Up @@ -163,11 +163,11 @@ final class MasternodeVoterRegistry {
keySource: .walletIndex(index))
}

// Only a degraded pool read can hide a node we own; if every active
// registration matched, depth did not matter.
let mayBeIncomplete = !resolution.poolIsLive && nodes.count < eligible.count
// Only a missing provider voting account can hide a node we own. If
// every active registration matched, the account did not matter.
let mayBeIncomplete = !resolution.hasProviderPool && nodes.count < eligible.count
Self.logger.info(
"🗳️ VOTING :: votable nodes=\(nodes.count, privacy: .public) of \(eligible.count, privacy: .public) active registrations livePool=\(resolution.poolIsLive, privacy: .public)")
"🗳️ VOTING :: votable nodes=\(nodes.count, privacy: .public) of \(eligible.count, privacy: .public) active registrations providerPool=\(resolution.hasProviderPool, privacy: .public)")
// Exclusion is by the wallet's ELIGIBLE set (not just the votable
// subset): a wallet-registered node stays out of the tracked list
// even when its derived key didn't resolve.
Expand Down
Loading
Loading