mirror of
https://github.com/luxfi/safe-ios.git
synced 2026-10-30 15:19:20 +00:00
307 lines
13 KiB
Swift
307 lines
13 KiB
Swift
//
|
|
// Copyright © 2020 Gnosis Ltd. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import BigInt
|
|
import CryptoSwift
|
|
|
|
// https://github.com/matter-labs/web3swift
|
|
|
|
extension UInt32 {
|
|
public func serialize32() -> Data {
|
|
var bigEndian = self.bigEndian
|
|
let count = MemoryLayout<UInt32>.size
|
|
let bytePtr = withUnsafePointer(to: &bigEndian) {
|
|
$0.withMemoryRebound(to: UInt8.self, capacity: count) {
|
|
UnsafeBufferPointer(start: $0, count: count)
|
|
}
|
|
}
|
|
let byteArray = Array(bytePtr)
|
|
return Data(byteArray)
|
|
}
|
|
}
|
|
|
|
public class HDNode {
|
|
public struct HDversion {
|
|
public var privatePrefix: Data = Data(hex: "0x0488ADE4")
|
|
public var publicPrefix: Data = Data(hex: "0x0488B21E")
|
|
public init() {}
|
|
}
|
|
|
|
public var path: String? = "m"
|
|
public var privateKey: Data?
|
|
public var publicKey: Data
|
|
public var chaincode: Data
|
|
public var depth: UInt8
|
|
public var parentFingerprint: Data = Data(repeating: 0, count: 4)
|
|
public var childNumber: UInt32 = UInt32(0)
|
|
public var isHardened:Bool {
|
|
get {
|
|
return self.childNumber >= (UInt32(1) << 31)
|
|
}
|
|
}
|
|
public var index: UInt32 {
|
|
get {
|
|
if self.isHardened {
|
|
return self.childNumber - (UInt32(1) << 31)
|
|
} else {
|
|
return self.childNumber
|
|
}
|
|
}
|
|
}
|
|
public var hasPrivate:Bool {
|
|
get {
|
|
return privateKey != nil
|
|
}
|
|
}
|
|
|
|
init() {
|
|
publicKey = Data()
|
|
chaincode = Data()
|
|
depth = UInt8(0)
|
|
}
|
|
|
|
public convenience init?(_ serializedString: String) {
|
|
let data = Data(Base58.bytesFromBase58(serializedString))
|
|
self.init(data)
|
|
}
|
|
|
|
public init?(_ data: Data) {
|
|
guard data.count == 82 else { return nil }
|
|
let header = data[0..<4]
|
|
var serializePrivate = false
|
|
if header == HDNode.HDversion().privatePrefix {
|
|
serializePrivate = true
|
|
}
|
|
depth = data[4..<5].bytes[0]
|
|
parentFingerprint = data[5..<9]
|
|
let cNum = data[9..<13].bytes
|
|
childNumber = UInt32(cNum)
|
|
chaincode = data[13..<45]
|
|
if serializePrivate {
|
|
privateKey = data[46..<78]
|
|
guard let pubKey = HDNode.privateToPublic(privateKey!, compressed: true) else { return nil }
|
|
if pubKey[0] != 0x02 && pubKey[0] != 0x03 { return nil }
|
|
publicKey = pubKey
|
|
} else {
|
|
publicKey = data[45..<78]
|
|
}
|
|
let hashedData = data[0..<78].sha256().sha256()
|
|
let checksum = hashedData[0..<4]
|
|
if checksum != data[78..<82] { return nil }
|
|
}
|
|
|
|
public static func privateToPublic(_ privateKey: Data, compressed: Bool = false) -> Data? {
|
|
guard let publicKey = SECP256K1.privateToPublic(privateKey: privateKey,
|
|
compressed: compressed) else { return nil }
|
|
return publicKey
|
|
}
|
|
|
|
public init?(seed: Data) {
|
|
guard seed.count >= 16 else { return nil }
|
|
let hmacKey = "Bitcoin seed".data(using: .ascii)!
|
|
let hmac:Authenticator = HMAC(key: hmacKey.bytes, variant: HMAC.Variant.sha512)
|
|
guard let entropy = try? hmac.authenticate(seed.bytes) else { return nil }
|
|
guard entropy.count == 64 else { return nil}
|
|
let I_L = entropy[0..<32]
|
|
let I_R = entropy[32..<64]
|
|
chaincode = Data(I_R)
|
|
let privKeyCandidate = Data(I_L)
|
|
guard SECP256K1.verifyPrivateKey(privateKey: privKeyCandidate) else { return nil }
|
|
guard let pubKeyCandidate = SECP256K1.privateToPublic(privateKey: privKeyCandidate,
|
|
compressed: true) else { return nil }
|
|
guard pubKeyCandidate.bytes[0] == 0x02 || pubKeyCandidate.bytes[0] == 0x03 else { return nil }
|
|
publicKey = pubKeyCandidate
|
|
privateKey = privKeyCandidate
|
|
depth = 0x00
|
|
childNumber = UInt32(0)
|
|
}
|
|
|
|
private static var curveOrder = BigUInt("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", radix: 16)!
|
|
public static var defaultPath: String = "m/44'/60'/0'/0"
|
|
public static var defaultPathPrefix: String = "m/44'/60'/0'"
|
|
public static var defaultPathMetamask: String = "m/44'/60'/0'/0/0"
|
|
public static var defaultPathMetamaskPrefix: String = "m/44'/60'/0'/0"
|
|
public static var hardenedIndexPrefix: UInt32 = (UInt32(1) << 31)
|
|
}
|
|
|
|
extension HDNode {
|
|
public func derive (index: UInt32, derivePrivateKey:Bool, hardened: Bool = false) -> HDNode? {
|
|
if derivePrivateKey {
|
|
if self.hasPrivate { // derive private key when is itself extended private key
|
|
var entropy:Array<UInt8>
|
|
var trueIndex: UInt32
|
|
if index >= (UInt32(1) << 31) || hardened {
|
|
trueIndex = index;
|
|
if trueIndex < (UInt32(1) << 31) {
|
|
trueIndex = trueIndex + (UInt32(1) << 31)
|
|
}
|
|
let hmac:Authenticator = HMAC(key: self.chaincode.bytes, variant: .sha512)
|
|
var inputForHMAC = Data()
|
|
inputForHMAC.append(Data([UInt8(0x00)]))
|
|
inputForHMAC.append(self.privateKey!)
|
|
inputForHMAC.append(trueIndex.serialize32())
|
|
guard let ent = try? hmac.authenticate(inputForHMAC.bytes) else {return nil }
|
|
guard ent.count == 64 else { return nil }
|
|
entropy = ent
|
|
} else {
|
|
trueIndex = index
|
|
let hmac:Authenticator = HMAC(key: self.chaincode.bytes, variant: .sha512)
|
|
var inputForHMAC = Data()
|
|
inputForHMAC.append(self.publicKey)
|
|
inputForHMAC.append(trueIndex.serialize32())
|
|
guard let ent = try? hmac.authenticate(inputForHMAC.bytes) else {return nil }
|
|
guard ent.count == 64 else { return nil }
|
|
entropy = ent
|
|
}
|
|
let I_L = entropy[0..<32]
|
|
let I_R = entropy[32..<64]
|
|
let cc = Data(I_R)
|
|
let bn = BigUInt(Data(I_L))
|
|
if bn > HDNode.curveOrder {
|
|
if trueIndex < UInt32.max {
|
|
return self.derive(index:index+1, derivePrivateKey: derivePrivateKey, hardened:hardened)
|
|
}
|
|
return nil
|
|
}
|
|
let newPK = (bn + BigUInt(self.privateKey!)) % HDNode.curveOrder
|
|
if newPK == BigUInt(0) {
|
|
if trueIndex < UInt32.max {
|
|
return self.derive(index:index+1, derivePrivateKey: derivePrivateKey, hardened:hardened)
|
|
}
|
|
return nil
|
|
}
|
|
guard let privKeyCandidate = newPK.serialize().setLengthLeft(32) else { return nil }
|
|
guard SECP256K1.verifyPrivateKey(privateKey: privKeyCandidate) else { return nil }
|
|
guard let pubKeyCandidate = SECP256K1.privateToPublic(privateKey: privKeyCandidate,
|
|
compressed: true) else { return nil }
|
|
guard pubKeyCandidate.bytes[0] == 0x02 || pubKeyCandidate.bytes[0] == 0x03 else { return nil }
|
|
guard self.depth < UInt8.max else { return nil }
|
|
let newNode = HDNode()
|
|
newNode.chaincode = cc
|
|
newNode.depth = self.depth + 1
|
|
newNode.publicKey = pubKeyCandidate
|
|
newNode.privateKey = privKeyCandidate
|
|
newNode.childNumber = trueIndex
|
|
guard let fprint = try? RIPEMD160.hash(message: self.publicKey.sha256())[0..<4] else {
|
|
return nil
|
|
}
|
|
newNode.parentFingerprint = fprint
|
|
var newPath = String()
|
|
if newNode.isHardened {
|
|
newPath = self.path! + "/"
|
|
newPath += try! String(newNode.index % HDNode.hardenedIndexPrefix) + "'"
|
|
} else {
|
|
newPath = try! (self.path! + "/" + String(newNode.index))
|
|
}
|
|
newNode.path = newPath
|
|
return newNode
|
|
} else {
|
|
return nil // derive private key when is itself extended public key (impossible)
|
|
}
|
|
}
|
|
else { // deriving only the public key
|
|
var entropy:Array<UInt8> // derive public key when is itself public key
|
|
if index >= (UInt32(1) << 31) || hardened {
|
|
return nil // no derivation of hardened public key from extended public key
|
|
} else {
|
|
let hmac:Authenticator = HMAC(key: self.chaincode.bytes, variant: .sha512)
|
|
var inputForHMAC = Data()
|
|
inputForHMAC.append(self.publicKey)
|
|
inputForHMAC.append(index.serialize32())
|
|
guard let ent = try? hmac.authenticate(inputForHMAC.bytes) else { return nil }
|
|
guard ent.count == 64 else { return nil }
|
|
entropy = ent
|
|
}
|
|
let I_L = entropy[0..<32]
|
|
let I_R = entropy[32..<64]
|
|
let cc = Data(I_R)
|
|
let bn = BigUInt(Data(I_L))
|
|
if bn > HDNode.curveOrder {
|
|
if index < UInt32.max {
|
|
return self.derive(index:index+1, derivePrivateKey: derivePrivateKey, hardened:hardened)
|
|
}
|
|
return nil
|
|
}
|
|
guard let tempKey = bn.serialize().setLengthLeft(32) else { return nil }
|
|
guard SECP256K1.verifyPrivateKey(privateKey: tempKey) else {return nil }
|
|
guard let pubKeyCandidate = SECP256K1.privateToPublic(privateKey: tempKey, compressed: true) else { return nil }
|
|
guard pubKeyCandidate.bytes[0] == 0x02 || pubKeyCandidate.bytes[0] == 0x03 else { return nil }
|
|
guard let newPublicKey = SECP256K1.combineSerializedPublicKeys(keys: [self.publicKey, pubKeyCandidate],
|
|
outputCompressed: true) else { return nil }
|
|
guard newPublicKey.bytes[0] == 0x02 || newPublicKey.bytes[0] == 0x03 else { return nil }
|
|
guard self.depth < UInt8.max else { return nil }
|
|
let newNode = HDNode()
|
|
newNode.chaincode = cc
|
|
newNode.depth = self.depth + 1
|
|
newNode.publicKey = newPublicKey
|
|
newNode.childNumber = index
|
|
guard let fprint = try? RIPEMD160.hash(message: self.publicKey.sha256())[0..<4] else {
|
|
return nil
|
|
}
|
|
newNode.parentFingerprint = fprint
|
|
var newPath = String()
|
|
if newNode.isHardened {
|
|
newPath = self.path! + "/"
|
|
newPath += try! String(newNode.index % HDNode.hardenedIndexPrefix) + "'"
|
|
} else {
|
|
newPath = try! (self.path! + "/" + String(newNode.index))
|
|
}
|
|
newNode.path = newPath
|
|
return newNode
|
|
}
|
|
}
|
|
|
|
public func derive (path: String, derivePrivateKey: Bool = true) -> HDNode? {
|
|
let components = path.components(separatedBy: "/")
|
|
var currentNode:HDNode = self
|
|
var firstComponent = 0
|
|
if path.hasPrefix("m") {
|
|
firstComponent = 1
|
|
}
|
|
for component in components[firstComponent ..< components.count] {
|
|
var hardened = false
|
|
if component.hasSuffix("'") {
|
|
hardened = true
|
|
}
|
|
guard let index = UInt32(component.trimmingCharacters(in: CharacterSet(charactersIn: "'"))) else { return nil }
|
|
guard let newNode = currentNode.derive(index: index,
|
|
derivePrivateKey: derivePrivateKey,
|
|
hardened: hardened) else { return nil }
|
|
currentNode = newNode
|
|
}
|
|
return currentNode
|
|
}
|
|
|
|
public func serializeToString(serializePublic: Bool = true, version: HDversion = HDversion()) -> String? {
|
|
guard let data = self.serialize(serializePublic: serializePublic, version: version) else { return nil }
|
|
let encoded = Base58.base58FromBytes(data.bytes)
|
|
return encoded
|
|
}
|
|
|
|
public func serialize(serializePublic: Bool = true, version: HDversion = HDversion()) -> Data? {
|
|
var data = Data()
|
|
if (!serializePublic && !self.hasPrivate) { return nil }
|
|
if serializePublic {
|
|
data.append(version.publicPrefix)
|
|
} else {
|
|
data.append(version.privatePrefix)
|
|
}
|
|
data.append(contentsOf: [self.depth])
|
|
data.append(self.parentFingerprint)
|
|
data.append(self.childNumber.serialize32())
|
|
data.append(self.chaincode)
|
|
if serializePublic {
|
|
data.append(self.publicKey)
|
|
} else {
|
|
data.append(contentsOf: [0x00])
|
|
data.append(self.privateKey!)
|
|
}
|
|
let hashedData = data.sha256().sha256()
|
|
let checksum = hashedData[0..<4]
|
|
data.append(checksum)
|
|
return data
|
|
}
|
|
}
|