92 lines
3.1 KiB
Swift
92 lines
3.1 KiB
Swift
import CryptoKit
|
|
import Foundation
|
|
|
|
/// BIP-32 key derivation for the XRP account path `m/44'/144'/0'/0/0`. The
|
|
/// first three steps are hardened, the last two are not, so both cases are
|
|
/// supported (hardened steps sign with the parent private key, normal steps
|
|
/// with the parent public key).
|
|
enum BIP32 {
|
|
static let hardenedBit: UInt32 = 0x8000_0000
|
|
|
|
/// Standard XRP derivation path: 44' / 144' / 0' / 0 / 0.
|
|
static let xrpPath: [UInt32] = [
|
|
hardenedBit | 44,
|
|
hardenedBit | 144,
|
|
hardenedBit | 0,
|
|
0,
|
|
0,
|
|
]
|
|
|
|
struct Key {
|
|
var privateKey: [UInt8]
|
|
var chainCode: [UInt8]
|
|
var publicKey: [UInt8]
|
|
}
|
|
|
|
static func masterKey(fromSeed seed: [UInt8]) -> Key? {
|
|
let derived = hmac(key: Array("Bitcoin seed".utf8), data: seed)
|
|
guard derived.count == 64 else { return nil }
|
|
let privateKey = Array(derived[0..<32])
|
|
guard isValidScalar(privateKey),
|
|
let publicKey = Secp256k1Backend.publicKey?(Data(privateKey)) else { return nil }
|
|
return Key(privateKey: privateKey,
|
|
chainCode: Array(derived[32..<64]),
|
|
publicKey: Array(publicKey))
|
|
}
|
|
|
|
static func derive(_ path: [UInt32], from key: Key) -> Key? {
|
|
var current = key
|
|
for index in path {
|
|
guard let next = deriveChild(current, index: index) else { return nil }
|
|
current = next
|
|
}
|
|
return current
|
|
}
|
|
|
|
private static func deriveChild(_ parent: Key, index: UInt32) -> Key? {
|
|
let isHardened = index & hardenedBit != 0
|
|
|
|
var data: [UInt8] = []
|
|
if isHardened {
|
|
data.append(0x00)
|
|
data += parent.privateKey
|
|
} else {
|
|
data += parent.publicKey
|
|
}
|
|
data += [
|
|
UInt8((index >> 24) & 0xff),
|
|
UInt8((index >> 16) & 0xff),
|
|
UInt8((index >> 8) & 0xff),
|
|
UInt8(index & 0xff),
|
|
]
|
|
|
|
let derived = hmac(key: parent.chainCode, data: data)
|
|
guard derived.count == 64 else { return nil }
|
|
let left = Array(derived[0..<32])
|
|
guard isValidScalar(left),
|
|
let sum = Secp256k1Backend.addScalar?(Data(left), Data(parent.privateKey)) else { return nil }
|
|
let privateKey = Array(sum)
|
|
// The child public key is the same whether it is computed from the child
|
|
// private key or by point addition, so derive it directly.
|
|
guard isValidScalar(privateKey),
|
|
let publicKey = Secp256k1Backend.publicKey?(Data(privateKey)) else { return nil }
|
|
|
|
return Key(privateKey: privateKey,
|
|
chainCode: Array(derived[32..<64]),
|
|
publicKey: Array(publicKey))
|
|
}
|
|
|
|
/// A key is usable when 0 < key < the secp256k1 group order.
|
|
private static func isValidScalar(_ bytes: [UInt8]) -> Bool {
|
|
guard bytes.count == 32 else { return false }
|
|
let orderHex = "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"
|
|
guard let order = Hex.decode(orderHex) else { return false }
|
|
if bytes.allSatisfy({ $0 == 0 }) { return false }
|
|
for (left, right) in zip(bytes, order) where left != right { return left < right }
|
|
return false
|
|
}
|
|
|
|
private static func hmac(key: [UInt8], data: [UInt8]) -> [UInt8] {
|
|
Array(HMAC<SHA512>.authenticationCode(for: Data(data), using: SymmetricKey(data: Data(key))))
|
|
}
|
|
} |