|
1 | 1 | import Foundation |
| 2 | +import CryptoKit |
2 | 3 |
|
3 | 4 | public enum Ed25519 { |
4 | | - public enum Error: Swift.Error { |
5 | | - case sharedSecretError(Swift.Error) |
6 | | - } |
7 | | - |
8 | | - public static func getSharedSecret(privateKey: PrivateKey, publicKey: PublicKey) throws -> Data { |
9 | | - do { |
10 | | - let xPrivateKey = try privateKey.toX25519 |
11 | | - let xPublicKey = try publicKey.toX25519 |
12 | | - return try X25519.getSharedSecret(privateKey: xPrivateKey, publicKey: xPublicKey) |
13 | | - } catch { |
14 | | - throw Error.sharedSecretError(error) |
15 | | - } |
16 | | - } |
| 5 | + |
| 6 | + public enum Error: Swift.Error { |
| 7 | + case sharedSecretError(Swift.Error) |
| 8 | + case derivePathError(String) |
| 9 | + } |
| 10 | + |
| 11 | + static let ED25519_CURVE = "ed25519 seed" |
| 12 | + public static let HARDENED_OFFSET: UInt32 = 0x80000000 |
| 13 | + |
| 14 | + public struct Keys { |
| 15 | + var key: Data |
| 16 | + var chainCode: Data |
| 17 | + } |
| 18 | + |
| 19 | + public static func getSharedSecret(privateKey: PrivateKey, publicKey: PublicKey) throws -> Data { |
| 20 | + do { |
| 21 | + let xPrivateKey = try privateKey.toX25519 |
| 22 | + let xPublicKey = try publicKey.toX25519 |
| 23 | + return try X25519.getSharedSecret(privateKey: xPrivateKey, publicKey: xPublicKey) |
| 24 | + } catch { |
| 25 | + throw Error.sharedSecretError(error) |
| 26 | + |
| 27 | + } |
| 28 | + } |
| 29 | + |
| 30 | + public static func getMasterKeyFromSeed(seed: String) throws -> Keys { |
| 31 | + guard let seedData = Data(hexString: seed) else { |
| 32 | + throw Error.derivePathError("Invalid seed hex string") |
| 33 | + } |
| 34 | + |
| 35 | + let hmac = HMAC<SHA512>.authenticationCode(for: seedData, using: SymmetricKey(data: ED25519_CURVE.data(using: .utf8)!)) |
| 36 | + let I = Data(hmac) |
| 37 | + let IL = I.prefix(32) |
| 38 | + let IR = I.suffix(from: 32) |
| 39 | + |
| 40 | + return Keys(key: IL, chainCode: IR) |
| 41 | + } |
| 42 | + |
| 43 | + public static func CKDPriv(keys: Keys, index: UInt32) -> Keys { |
| 44 | + var indexData = Data(count: 4) |
| 45 | + indexData.withUnsafeMutableBytes { $0.bindMemory(to: UInt8.self).baseAddress?.withMemoryRebound(to: UInt32.self, capacity: 1) { |
| 46 | + $0.pointee = index.bigEndian |
| 47 | + }} |
| 48 | + |
| 49 | + let data = Data([0]) + keys.key + indexData |
| 50 | + let hmacValue = HMAC<SHA512>.authenticationCode(for: data, using: SymmetricKey(data: keys.chainCode)) |
| 51 | + let I = Data(hmacValue) |
| 52 | + let IL = I.prefix(32) |
| 53 | + let IR = I.suffix(from: 32) |
| 54 | + |
| 55 | + return Keys(key: IL, chainCode: IR) |
| 56 | + } |
| 57 | + |
| 58 | + public static func isValidPath(path: String) -> Bool { |
| 59 | + let pathRegex = #"^m(\/[0-9]+')+$"# |
| 60 | + let regex = try? NSRegularExpression(pattern: pathRegex) |
| 61 | + |
| 62 | + let range = NSRange(location: 0, length: path.utf16.count) |
| 63 | + guard regex?.firstMatch(in: path, options: [], range: range) != nil else { |
| 64 | + return false |
| 65 | + } |
| 66 | + |
| 67 | + return !path.split(separator: "/").dropFirst().map { $0.replacingOccurrences(of: "'", with: "") }.contains { Int($0) == nil } |
| 68 | + } |
| 69 | + |
| 70 | + public static func derivePath(path: String, seed: String, offset: UInt32 = HARDENED_OFFSET) throws -> Keys { |
| 71 | + guard isValidPath(path: path) else { |
| 72 | + throw Error.derivePathError("Invalid derivation path") |
| 73 | + } |
| 74 | + |
| 75 | + var keys = try getMasterKeyFromSeed(seed: seed) |
| 76 | + |
| 77 | + let segments = path |
| 78 | + .split(separator: "/") |
| 79 | + .dropFirst() |
| 80 | + .map { $0.replacingOccurrences(of: "'", with: "") } |
| 81 | + .compactMap { UInt32($0) } |
| 82 | + |
| 83 | + for segment in segments { |
| 84 | + keys = CKDPriv(keys: keys, index: segment + offset) |
| 85 | + } |
| 86 | + |
| 87 | + return keys |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +extension Data { |
| 92 | + init?(hexString: String) { |
| 93 | + let length = hexString.count / 2 |
| 94 | + var data = Data(capacity: length) |
| 95 | + var index = hexString.startIndex |
| 96 | + for _ in 0..<length { |
| 97 | + let nextIndex = hexString.index(index, offsetBy: 2) |
| 98 | + guard let byte = UInt8(hexString[index..<nextIndex], radix: 16) else { |
| 99 | + return nil |
| 100 | + } |
| 101 | + data.append(byte) |
| 102 | + index = nextIndex |
| 103 | + } |
| 104 | + self = data |
| 105 | + } |
17 | 106 | } |
0 commit comments