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%
1//2// ASKKeychain.swift3// AskCoin-HD4//5// Created by 仇弘扬 on 2017/8/14.6// Copyright © 2017年 askcoin. All rights reserved.7//8import CommonCrypto9import Foundation1011let BTCKeychainMainnetPrivateVersion: UInt32 = 0x0488_ADE412let BTCKeychainMainnetPublicVersion: UInt32 = 0x0488_B21E1314let BTCKeychainTestnetPrivateVersion: UInt32 = 0x0435_839415let BTCKeychainTestnetPublicVersion: UInt32 = 0x0435_87CF1617let BTCMasterKeychainPath = "m"18let BTCKeychainHardenedSymbol = "'"19let BTCKeychainPathSeparator = "/"2021public class Keychain: NSObject {22 public var privateKey: Data?23 private var chainCode: Data?2425 fileprivate var isMasterKey = false26 var isTestnet = false2728 var depth: UInt8 = 029 var hardened = false30 var index: UInt32 = 03132 override init() {}3334 public convenience init?(seedString: String, network: String) throws {35 guard let seedData = try? Mnemonic(phrase: seedString.components(separatedBy: " ")) else {36 return nil37 }38 guard let keyData = "Bitcoin seed".data(using: .utf8) else {39 return nil40 }41 guard let hmac = hmacSha512(message: Data(seedData.seed), key: keyData) else {42 return nil43 }44 self.init(hmac: hmac.bytes)45 isMasterKey = true46 isTestnet = network == "devnet" || network == "testnet"47 }4849 public convenience init?(seed: String, salt: String, network: String) throws {50 let password = (seed.components(separatedBy: " ").joined(separator: " ") as NSString)51 .decomposedStringWithCompatibilityMapping52 let salt = (salt as NSString).decomposedStringWithCompatibilityMapping53 guard let seedBytes = pbkdf2(54 hash: CCPBKDFAlgorithm(kCCPRFHmacAlgSHA512),55 password: password,56 salt: Data(salt.bytes),57 keyByteCount: 64,58 rounds: 204859 )?.bytes else {60 return nil61 }6263 guard let keyData = "Bitcoin seed".data(using: .utf8) else {64 return nil65 }66 guard let hmac = hmacSha512(message: Data(seedBytes), key: keyData) else {67 return nil68 }69 self.init(hmac: hmac.bytes)70 isMasterKey = true71 isTestnet = network == "devnet" || network == "testnet"72 }7374 public init(hmac: [UInt8]) {75 privateKey = Data(hmac[0 ..< 32])76 chainCode = Data(hmac[32 ..< 64])77 }7879 public lazy var identifier: Data? = {80 if let pubKey = self.publicKey {81 return pubKey.ask_BTCHash160()82 }83 return nil84 }()8586 public var parentFingerprint: UInt32 = 08788 public lazy var fingerprint: UInt32 = {89 if let id = self.identifier {90 return UInt32(bytes: id.bytes[0 ..< 4])91 }92 return 093 }()9495 private lazy var publicKey: Data? = {96 guard let prvKey = self.privateKey else {97 return nil98 }99 return CKSecp256k1.generatePublicKey(withPrivateKey: prvKey, compression: true)100 }()101102 // MARK: - Extended private key103104 public lazy var extendedPrivateKey: String = self.extendedPrivateKeyData.ask_base58Check()105106 public lazy var extendedPrivateKeyData: Data = {107 guard self.privateKey != nil else {108 return Data()109 }110111 var toReturn = Data()112113 let version = !isTestnet ? BTCKeychainMainnetPrivateVersion : BTCKeychainTestnetPrivateVersion114 toReturn += self.extendedKeyPrefix(with: version)115116 toReturn += UInt8(0).ask_hexToData()117118 if let prikey = self.privateKey {119 toReturn += prikey120 }121122 return toReturn123 }()124125 // MARK: - Extended public key126127 public lazy var extendedPublicKey: String = self.extendedPublicKeyData.ask_base58Check()128129 public lazy var extendedPublicKeyData: Data = {130 guard self.publicKey != nil else {131 return Data()132 }133134 var toReturn = Data()135136 let version = !isTestnet ? BTCKeychainMainnetPublicVersion : BTCKeychainTestnetPublicVersion137 toReturn += self.extendedKeyPrefix(with: version)138139 if let pubkey = self.publicKey {140 toReturn += pubkey141 }142143 return toReturn144 }()145146 func extendedKeyPrefix(with version: UInt32) -> Data {147 var toReturn = Data()148149 let versionData = version.ask_hexToData()150 toReturn += versionData151152 let depthData = depth.ask_hexToData()153 toReturn += depthData154155 let parentFPData = parentFingerprint.ask_hexToData()156 toReturn += parentFPData157158 let childIndex = hardened ? (0x8000_0000 | index) : index159 let childIndexData = childIndex.ask_hexToData()160 toReturn += childIndexData161162 if let cCode = chainCode {163 toReturn += cCode164 } else {165 print(KeyDerivationError.chainCodeNil)166 }167168 return toReturn169 }170171 public func derivedKeychain(at path: String) throws -> Keychain {172 if path == BTCMasterKeychainPath || path == BTCKeychainPathSeparator || path == "" {173 return self174 }175176 var paths = path.components(separatedBy: BTCKeychainPathSeparator)177 if path.hasPrefix(BTCMasterKeychainPath) {178 paths.removeFirst()179 }180181 var kc = self182183 for indexString in paths {184 var isHardened = false185 var temp = indexString186 if indexString.hasSuffix(BTCKeychainHardenedSymbol) {187 isHardened = true188 temp = String(temp[..<temp.index(temp.endIndex, offsetBy: -1)])189 }190 if let index = UInt32(temp) {191 kc = try kc.derivedKeychain(at: index, hardened: isHardened)192 continue193 }194 throw KeyDerivationError.pathInvalid195 }196197 return kc198 }199200 public func derivedKeychain(at index: UInt32, hardened: Bool = true) throws -> Keychain {201 let edge: UInt32 = 0x8000_0000202203 guard (edge & UInt32(index)) == 0 else {204 throw KeyDerivationError.indexInvalid205 }206207 guard let prvKey = privateKey else {208 throw KeyDerivationError.privateKeyNil209 }210211 guard let pubKey = publicKey else {212 throw KeyDerivationError.publicKeyNil213 }214215 guard let chCode = chainCode else {216 throw KeyDerivationError.chainCodeNil217 }218219 var data = Data()220221 if hardened {222 let padding: UInt8 = 0223 data += padding.ask_hexToData()224 data += prvKey225 } else {226 data += pubKey227 }228229 let indexBE = hardened ? (edge + index) : index230 data += indexBE.ask_hexToData()231232 let digestArray = hmacSha512(message: data, key: chCode)!.bytes233234 let factor = BInt(data: Data(digestArray[0 ..< 32]))235 let curveOrder = BInt(hex: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141")236237 let derivedKeychain = Keychain(hmac: digestArray)238239 let pkNum = BInt(data: Data(prvKey))240241 let pkData = ((pkNum + factor) % curveOrder).data242243 derivedKeychain.privateKey = pkData244 derivedKeychain.depth = depth + 1245 derivedKeychain.parentFingerprint = fingerprint246 derivedKeychain.index = index247 derivedKeychain.hardened = hardened248249 return derivedKeychain250 }251}252