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