|
1 | 1 | import Foundation |
2 | | -import CryptoKit |
3 | 2 |
|
4 | 3 | public enum Ed25519 { |
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 | | - } |
| 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 | + } |
106 | 17 | } |
0 commit comments