spb/os-vault Public
Self-custody, multi-chain crypto wallet for macOS. One recovery phrase, six chain families, zero API keys — nothing leaves your Mac.
Swift 96%
Shell 3.4%
Makefile 0.6%
1import CommonCrypto2import Foundation3import TweetNacl45@available(*, deprecated, renamed: "KeyPair")6public typealias Account = KeyPair78public extension KeyPair {9 @available(*, deprecated, renamed: "AccountMeta")10 typealias Meta = AccountMeta11}1213public enum KeyPairError: Error, Equatable {14 case couldNotDerivatePrivateKey15}1617public struct KeyPair: Codable, Hashable {18 public let phrase: [String]19 public let publicKey: PublicKey20 public let secretKey: Data2122 public init() throws {23 let keys = try NaclSign.KeyPair.keyPair()24 publicKey = try PublicKey(data: keys.publicKey)25 secretKey = keys.secretKey26 let phrase = try Mnemonic.toMnemonic(secretKey.bytes)27 self.phrase = phrase28 }2930 public init(phrase: [String], publicKey: PublicKey, secretKey: Data) {31 self.phrase = phrase32 self.publicKey = publicKey33 self.secretKey = secretKey34 }3536 public init(secretKey: Data) throws {37 let keys = try NaclSign.KeyPair.keyPair(fromSecretKey: secretKey)38 publicKey = try PublicKey(data: keys.publicKey)39 self.secretKey = keys.secretKey40 let phrase = try Mnemonic.toMnemonic(secretKey.bytes)41 self.phrase = phrase42 }4344 public init(45 seed: String,46 salt: String,47 passphrase _: String,48 network: Network,49 derivablePath: DerivablePath50 ) async throws {51 self = try await Task {52 let publicKey: PublicKey53 let secretKey: Data5455 switch derivablePath.type {56 case .deprecated:57 let keychain = try Keychain(seed: seed, salt: salt, network: network.cluster)58 guard let seed = try keychain?.derivedKeychain(at: derivablePath.rawValue).privateKey else {59 throw KeyPairError.couldNotDerivatePrivateKey60 }6162 let keys = try NaclSign.KeyPair.keyPair(fromSeed: seed)6364 publicKey = try .init(data: keys.publicKey)65 secretKey = keys.secretKey66 default:67 let password = (seed as NSString).decomposedStringWithCompatibilityMapping68 let salt = (salt as NSString).decomposedStringWithCompatibilityMapping69 guard let seedBytes = pbkdf2(70 hash: CCPBKDFAlgorithm(kCCPRFHmacAlgSHA512),71 password: password,72 salt: Data(salt.bytes),73 keyByteCount: 64,74 rounds: 204875 )?.bytes else {76 throw KeyPairError.couldNotDerivatePrivateKey77 }7879 let keys = try Ed25519HDKey.derivePath(derivablePath.rawValue, seed: seedBytes.toHexString()).get()80 let keyPair = try NaclSign.KeyPair.keyPair(fromSeed: keys.key)81 let newKey = try PublicKey(data: keyPair.publicKey)8283 publicKey = newKey84 secretKey = keyPair.secretKey85 }8687 return .init(phrase: [seed], publicKey: publicKey, secretKey: secretKey)88 }.value89 }9091 public init(mnemonic: Mnemonic, network: Network, derivablePath: DerivablePath) async throws {92 self = try await Task {93 let phrase = mnemonic.phrase9495 let publicKey: PublicKey96 let secretKey: Data9798 switch derivablePath.type {99 case .deprecated:100 let keychain = try Keychain(seedString: phrase.joined(separator: " "), network: network.cluster)101 guard let seed = try keychain?.derivedKeychain(at: derivablePath.rawValue).privateKey else {102 throw KeyPairError.couldNotDerivatePrivateKey103 }104105 let keys = try NaclSign.KeyPair.keyPair(fromSeed: seed)106107 publicKey = try .init(data: keys.publicKey)108 secretKey = keys.secretKey109 default:110 let keys = try Ed25519HDKey.derivePath(derivablePath.rawValue, seed: mnemonic.seed.toHexString()).get()111 let keyPair = try NaclSign.KeyPair.keyPair(fromSeed: keys.key)112 let newKey = try PublicKey(data: keyPair.publicKey)113114 publicKey = newKey115 secretKey = keyPair.secretKey116 }117118 return .init(phrase: phrase, publicKey: publicKey, secretKey: secretKey)119 }.value120 }121122 /// Create account with seed phrase123 /// - Parameters:124 /// - phrase: secret phrase for an account, leave it empty for new account125 /// - network: network in which account should be created126 /// - Throws: Error if the derivation is not successful127 public init(phrase: [String] = [], network: Network, derivablePath: DerivablePath? = nil) async throws {128 let mnemonic: Mnemonic129 var phrase = phrase.filter { !$0.isEmpty }130 if !phrase.isEmpty {131 mnemonic = try Mnemonic(phrase: phrase)132 } else {133 // change from 12-words to 24-words (128 to 256)134 mnemonic = Mnemonic()135 phrase = mnemonic.phrase136 }137138 var derivablePath = derivablePath139 if derivablePath == nil {140 if phrase.count == 12 {141 derivablePath = .init(type: .deprecated, walletIndex: 0, accountIndex: 0)142 } else {143 derivablePath = .default144 }145 }146147 let publicKey: PublicKey148 let secretKey: Data149150 switch derivablePath!.type {151 case .deprecated:152 let keychain = try Keychain(seedString: phrase.joined(separator: " "), network: network.cluster)153 guard let seed = try keychain?.derivedKeychain(at: derivablePath!.rawValue).privateKey else {154 throw KeyPairError.couldNotDerivatePrivateKey155 }156157 let keys = try NaclSign.KeyPair.keyPair(fromSeed: seed)158159 publicKey = try .init(data: keys.publicKey)160 secretKey = keys.secretKey161 default:162 let keys = try Ed25519HDKey.derivePath(derivablePath!.rawValue, seed: mnemonic.seed.toHexString()).get()163 let keyPair = try NaclSign.KeyPair.keyPair(fromSeed: keys.key)164 let newKey = try PublicKey(data: keyPair.publicKey)165166 publicKey = newKey167 secretKey = keyPair.secretKey168 }169170 self.phrase = phrase171 self.publicKey = publicKey172 self.secretKey = secretKey173 }174}175