From bda72f0139cbc4b32d757ff1f2d3c2c4d0ee092e Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Thu, 3 Sep 2026 17:12:45 -0400 Subject: [PATCH 1/2] feat(derive): route key derivation through SharedCoreKit Derive.seedUsingBIP39 and keyPairUsingBIP39 now call SharedDerivation.seed/derivedKey instead of the Swift-only BIP-39 and SLIP-10 implementation, so derivation logic is shared with Android via :libs:encryption:mnemonic in KMP commonMain. FlipcashCore/Package.swift and FlipcashCoreVectors/Package.swift needed a macOS platform declaration to actually exercise this: neither had ever built for macOS before (FlipcashCore declared iOS only), so FlipcashCoreVectors' Slip10DerivationVectorTests gate had never run. Declaring macOS surfaced that ChatNotificationClient's GRPCClient usage needs macOS 15, not the 13.3/14.0 floor BigDecimal and grpc/nio need, so both manifests are pinned to macOS 15. Verified locally against a macOS-slice build of SharedCore.xcframework via FLIPCASH_SHARED_CORE_LOCAL (kmp/shared-core's macOS targets aren't in a published flipcash-shared-core-spm release yet); the full FlipcashCoreVectors suite passes. --- FlipcashCore/Package.swift | 1 + .../FlipcashCore/Solana/Keys/Derive.swift | 82 ++----------------- FlipcashCoreVectors/Package.swift | 2 +- 3 files changed, 10 insertions(+), 75 deletions(-) diff --git a/FlipcashCore/Package.swift b/FlipcashCore/Package.swift index 28dbab872..fa590d46c 100644 --- a/FlipcashCore/Package.swift +++ b/FlipcashCore/Package.swift @@ -7,6 +7,7 @@ let package = Package( name: "FlipcashCore", platforms: [ .iOS(.v18), + .macOS(.v15), ], products: [ .library( diff --git a/FlipcashCore/Sources/FlipcashCore/Solana/Keys/Derive.swift b/FlipcashCore/Sources/FlipcashCore/Solana/Keys/Derive.swift index 9a96c019d..cc964f11c 100644 --- a/FlipcashCore/Sources/FlipcashCore/Solana/Keys/Derive.swift +++ b/FlipcashCore/Sources/FlipcashCore/Solana/Keys/Derive.swift @@ -7,85 +7,19 @@ // import Foundation +import SharedCoreKit -public typealias KeyDescriptor = (key: Data, chain: Data) - -/// Deterministic wallet generation for ED25519 curve using SLIP-0010 spec -/// Reference: https://github.com/satoshilabs/slips/blob/master/slip-0010.md -/// public enum Derive { - - private static let curve: Data = Data("ed25519 seed".utf8) - private static let hardenedOffset: UInt32 = 0x80000000 - - static func masterKey(seed: Data) -> KeyDescriptor { - seed.hmac(using: curve).split32() - } - - static func path(path: Path, seed: Data) -> (keyPair: KeyPair, chaincode: Data) { - var descriptor = masterKey(seed: seed) - - path.indexes.forEach { index in - descriptor = CKDPriv(keyDescriptor: descriptor, index: hardenedOffset + index.value) - } - - return ( - KeyPair(seed: try! Seed32(descriptor.key)), - descriptor.chain - ) - } - - private static func CKDPriv(keyDescriptor: KeyDescriptor, index: UInt32) -> KeyDescriptor { - var entropy = Data() - entropy.append(0x00) - entropy.append(keyDescriptor.key) - entropy.append(contentsOf: index.bigEndian.bytes) - - return entropy.hmac(using: keyDescriptor.chain).split32() - } -} + private static let hardenedOffset: Int64 = 0x8000_0000 -/// Deterministic key derivation using BIP39 -/// Reference: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki -/// -extension Derive { - static func seedUsingBIP39(phrase: [String], password: String = "") -> Key64 { - let phrase = phrase.joined(separator: " ") - let salt = "mnemonic\(password)" - - let bytes = PBKDF.deriveKey( - algorithm: .sha512, - password: phrase, - salt: salt - ) - - return try! Key64(bytes) + try! Key64(SharedDerivation.seed(mnemonic: phrase, passphrase: password)) } - - public static func keyPairUsingBIP39(path: Path, phrase: [String], password: String = "") -> KeyPair { - let key64 = seedUsingBIP39( - phrase: phrase, - password: password - ) - - return Derive.path(path: path, seed: key64.data).keyPair - } -} - -// MARK: - Data - -private extension Data { - - func split32() -> (Data, Data) { - let lhs = Data(self[0..<32]) - let rhs = Data(self[32..<64]) - return (lhs, rhs) - } - - func hmac(using key: Data) -> Data { - var hmac = HMAC(algorithm: .sha512, key: key) - hmac.update(self) - return hmac.digestData() + public static func keyPairUsingBIP39(path: Path, phrase: [String], password: String = "") -> KeyPair { + let seed = seedUsingBIP39(phrase: phrase, password: password) + let hardenedIndexes = path.indexes.map { hardenedOffset + Int64($0.value) } + let derivedKey = SharedDerivation.derivedKey(seed: seed.data, hardenedIndexes: hardenedIndexes) + return KeyPair(seed: try! Seed32(derivedKey)) } } diff --git a/FlipcashCoreVectors/Package.swift b/FlipcashCoreVectors/Package.swift index ef2796b8d..8026c97f5 100644 --- a/FlipcashCoreVectors/Package.swift +++ b/FlipcashCoreVectors/Package.swift @@ -13,7 +13,7 @@ import PackageDescription let package = Package( name: "FlipcashCoreVectors", platforms: [ - .macOS("14.0"), // FlipcashCore's transitive deps (BigDecimal 13.3, grpc/nio) need >= 13.3 + .macOS("15.0"), // FlipcashCore's transitive deps (BigDecimal 13.3, grpc/nio) need >= 13.3 .iOS("18.0"), ], dependencies: [ From 8d3aff8f3d345b18f1fc8c0312cec23f0d262db7 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Sat, 5 Sep 2026 18:25:12 -0400 Subject: [PATCH 2/2] chore(deps): bump SharedCore floor to 0.5.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FlipcashCore, FlipcashUI, and CrossPlatformVectors were still pinned to .upToNextMinor(from: "0.4.0"), which predates the SharedCore release that ships SLIP-10 derivation. Moving the floor to 0.5.0 lets SwiftPM resolve 0.5.1, the release with a macOS-safe XCFramework zip (0.5.0's macOS slice had its framework-bundle symlinks flattened into duplicated files by KMMBridge's zip task, which broke codesign for anything linking the macOS build of SharedCore). FlipcashCoreVectors, the new macOS-only regression gate for this derivation work, needs that fix to codesign at all — it now builds and passes against the real published 0.5.1 package. --- Code.xcodeproj/project.pbxproj | 2 +- CrossPlatformVectors/Package.swift | 9 +++++---- FlipcashCore/Package.swift | 2 +- FlipcashUI/Package.swift | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Code.xcodeproj/project.pbxproj b/Code.xcodeproj/project.pbxproj index f5809faaa..59195821b 100644 --- a/Code.xcodeproj/project.pbxproj +++ b/Code.xcodeproj/project.pbxproj @@ -1766,7 +1766,7 @@ repositoryURL = "https://github.com/code-payments/flipcash-shared-core-spm"; requirement = { kind = upToNextMinorVersion; - minimumVersion = 0.4.0; + minimumVersion = 0.5.0; }; }; 9ACE172927E6287C00ACA047 /* XCRemoteSwiftPackageReference "mixpanel-swift" */ = { diff --git a/CrossPlatformVectors/Package.swift b/CrossPlatformVectors/Package.swift index 1193d1e7e..f58c59f64 100644 --- a/CrossPlatformVectors/Package.swift +++ b/CrossPlatformVectors/Package.swift @@ -11,16 +11,17 @@ import PackageDescription // // iOS only, and run on a simulator rather than `swift test` on the host: the implementation under // test is no longer a host-buildable C package but the shared Kotlin reached through -// `SharedCoreKit`, whose XCFramework ships iOS slices only. That is the point of the gate now — -// it exercises the framework exactly as FlipcashCore consumes it, which the Kotlin-side -// `Ed25519VectorTest` on the same fixtures does not. +// `SharedCoreKit`. Exercising the framework exactly as FlipcashCore consumes it on iOS is the +// point of the gate — the Kotlin-side `Ed25519VectorTest` on the same fixtures does not do that. +// (The XCFramework also ships a macOS slice now, used by `FlipcashCoreVectors`; this target stays +// iOS-only by design, not for lack of one.) let package = Package( name: "CrossPlatformVectors", platforms: [ .iOS(.v15), ], dependencies: [ - .package(url: "https://github.com/code-payments/flipcash-shared-core-spm", .upToNextMinor(from: "0.4.0")), + .package(url: "https://github.com/code-payments/flipcash-shared-core-spm", .upToNextMinor(from: "0.5.0")), ], targets: [ .testTarget( diff --git a/FlipcashCore/Package.swift b/FlipcashCore/Package.swift index fa590d46c..a983cc036 100644 --- a/FlipcashCore/Package.swift +++ b/FlipcashCore/Package.swift @@ -23,7 +23,7 @@ let package = Package( .package(url: "https://github.com/grpc/grpc-swift-nio-transport.git", from: "2.0.0"), .package(url: "https://github.com/apple/swift-nio.git", from: "2.81.0"), .package(path: "../FlipcashAPI"), - .package(url: "https://github.com/code-payments/flipcash-shared-core-spm", .upToNextMinor(from: "0.4.0")), + .package(url: "https://github.com/code-payments/flipcash-shared-core-spm", .upToNextMinor(from: "0.5.0")), ], targets: [ .target( diff --git a/FlipcashUI/Package.swift b/FlipcashUI/Package.swift index 8305aa3dd..7d8abed7d 100644 --- a/FlipcashUI/Package.swift +++ b/FlipcashUI/Package.swift @@ -19,7 +19,7 @@ let package = Package( .package(url: "https://github.com/ekazaev/ChatLayout", from: "2.4.2"), .package(url: "https://github.com/ra1028/DifferenceKit", from: "1.3.0"), .package(url: "https://github.com/onevcat/Kingfisher", from: "8.3.0"), - .package(url: "https://github.com/code-payments/flipcash-shared-core-spm", .upToNextMinor(from: "0.4.0")), + .package(url: "https://github.com/code-payments/flipcash-shared-core-spm", .upToNextMinor(from: "0.5.0")), ], targets: [ .target(