import Foundation import CryptoKit /// spec, раздел "Мнемоника и seed → Algorithm M-1 mnemonic_to_master_seed". /// Реализует BIP-39-совместимое представление 24 слов = 256 бит entropy + 8 бит checksum. enum Mnemonic { static let wordCount = 24 static let entropyBits = 256 static let checksumBits = 8 static let kdfIterations: UInt32 = 1_048_576 // 2^20 static let masterSeedLength = 64 static let kdfSalt = "mt-seed".data(using: .ascii)! enum MnemonicError: Error, Equatable { case wordCount(Int) case unknownWord(position: Int) case checksumMismatch case invalidEntropyLength } /// Генерация мнемоники из 32 байт entropy. static func encode(entropy: Data) throws -> String { guard entropy.count == 32 else { throw MnemonicError.invalidEntropyLength } let checksum = Array(SHA256.hash(data: entropy))[0] var packed = [UInt8](entropy) packed.append(checksum) // 33 байта let indices = bitsToIndices(packed) return indices.map { Wordlist.canonical[Int($0)] }.joined(separator: " ") } /// Парсинг мнемоники + PBKDF2 → 64-байт master_seed. static func decodeAndDerive(mnemonic: String) throws -> Data { let words = mnemonic.split(whereSeparator: { $0.isWhitespace }).map { String($0) } guard words.count == wordCount else { throw MnemonicError.wordCount(words.count) } var indices: [UInt16] = [] indices.reserveCapacity(wordCount) for (i, w) in words.enumerated() { guard let idx = Wordlist.index(of: w) else { throw MnemonicError.unknownWord(position: i) } indices.append(idx) } let packed = indicesToBits(indices) let entropy = Data(packed.prefix(32)) let providedChecksum = packed[32] let computedChecksum = Array(SHA256.hash(data: entropy))[0] guard providedChecksum == computedChecksum else { throw MnemonicError.checksumMismatch } return pbkdf2_hmac_sha256(password: entropy, salt: kdfSalt, iterations: kdfIterations, keyLength: masterSeedLength) } /// Случайная мнемоника через системный CSPRNG (24 слова). static func generate() -> String { var entropy = Data(count: 32) let result = entropy.withUnsafeMutableBytes { buf in SecRandomCopyBytes(kSecRandomDefault, 32, buf.baseAddress!) } precondition(result == errSecSuccess, "SecRandomCopyBytes failed") return try! encode(entropy: entropy) } // MARK: - Bit packing (24 × 11 = 264 bits → 33 bytes, MSB-first) private static func bitsToIndices(_ bytes: [UInt8]) -> [UInt16] { var indices = [UInt16](repeating: 0, count: wordCount) var bitPos = 0 for i in 0..> bitInByte) & 1) indices[i] |= bit << (10 - b) bitPos += 1 } } return indices } private static func indicesToBits(_ indices: [UInt16]) -> [UInt8] { var bytes = [UInt8](repeating: 0, count: 33) var bitPos = 0 for i in 0..> (10 - b)) & 1) let byteIdx = bitPos / 8 let bitInByte = 7 - (bitPos % 8) bytes[byteIdx] |= bit << bitInByte bitPos += 1 } } return bytes } // MARK: - PBKDF2-HMAC-SHA-256 через CommonCrypto bridge private static func pbkdf2_hmac_sha256(password: Data, salt: Data, iterations: UInt32, keyLength: Int) -> Data { var derived = Data(count: keyLength) let result = derived.withUnsafeMutableBytes { (derivedBytes: UnsafeMutableRawBufferPointer) -> Int32 in password.withUnsafeBytes { (passwordBytes: UnsafeRawBufferPointer) -> Int32 in salt.withUnsafeBytes { (saltBytes: UnsafeRawBufferPointer) -> Int32 in CCKeyDerivationPBKDF( CCPBKDFAlgorithm(kCCPBKDF2), passwordBytes.baseAddress, password.count, saltBytes.baseAddress, salt.count, CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), iterations, derivedBytes.baseAddress, keyLength ) } } } precondition(result == kCCSuccess, "PBKDF2 failed") return derived } }