SPB Git

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%
7.2 KB · 252 lines swift
Raw Blame History
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