mirror of
https://github.com/luxfi/safe-ios.git
synced 2026-10-30 15:19:20 +00:00
378 lines
17 KiB
Swift
378 lines
17 KiB
Swift
//
|
|
// Copyright © 2020 Gnosis Ltd. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import secp256k1
|
|
|
|
// https://github.com/matter-labs/web3swift
|
|
|
|
public struct SECP256K1 {
|
|
public struct UnmarshaledSignature{
|
|
public var v: UInt8 = 0
|
|
public var r = Data(repeating: 0, count: 32)
|
|
public var s = Data(repeating: 0, count: 32)
|
|
|
|
public init(v: UInt8, r: Data, s: Data) {
|
|
self.v = v
|
|
self.r = r
|
|
self.s = s
|
|
}
|
|
}
|
|
}
|
|
|
|
extension SECP256K1 {
|
|
static let context = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_SIGN|SECP256K1_CONTEXT_VERIFY))
|
|
|
|
public static func signForRecovery(hash: Data, privateKey: Data, useExtraEntropy: Bool = false) -> (serializedSignature:Data?, rawSignature: Data?) {
|
|
if (hash.count != 32 || privateKey.count != 32) {return (nil, nil)}
|
|
if !SECP256K1.verifyPrivateKey(privateKey: privateKey) {
|
|
return (nil, nil)
|
|
}
|
|
for _ in 0...1024 {
|
|
guard var recoverableSignature = SECP256K1.recoverableSign(hash: hash, privateKey: privateKey, useExtraEntropy: useExtraEntropy) else {
|
|
continue
|
|
}
|
|
guard let truePublicKey = SECP256K1.privateKeyToPublicKey(privateKey: privateKey) else {continue}
|
|
guard let recoveredPublicKey = SECP256K1.recoverPublicKey(hash: hash, recoverableSignature: &recoverableSignature) else {continue}
|
|
if !SECP256K1.constantTimeComparison(Data(toByteArray(truePublicKey.data)), Data(toByteArray(recoveredPublicKey.data))) {
|
|
continue
|
|
}
|
|
guard let serializedSignature = SECP256K1.serializeSignature(recoverableSignature: &recoverableSignature) else {continue}
|
|
let rawSignature = Data(toByteArray(recoverableSignature))
|
|
return (serializedSignature, rawSignature)
|
|
}
|
|
return (nil, nil)
|
|
}
|
|
|
|
public static func privateToPublic(privateKey: Data, compressed: Bool = false) -> Data? {
|
|
if (privateKey.count != 32) {return nil}
|
|
guard var publicKey = SECP256K1.privateKeyToPublicKey(privateKey: privateKey) else {return nil}
|
|
guard let serializedKey = serializePublicKey(publicKey: &publicKey, compressed: compressed) else {return nil}
|
|
return serializedKey
|
|
}
|
|
|
|
public static func combineSerializedPublicKeys(keys: [Data], outputCompressed: Bool = false) -> Data? {
|
|
let numToCombine = keys.count
|
|
guard numToCombine >= 1 else { return nil}
|
|
var storage = ContiguousArray<secp256k1_pubkey>()
|
|
let arrayOfPointers = UnsafeMutablePointer< UnsafePointer<secp256k1_pubkey>? >.allocate(capacity: numToCombine)
|
|
defer {
|
|
arrayOfPointers.deinitialize(count: numToCombine)
|
|
arrayOfPointers.deallocate()
|
|
}
|
|
for i in 0 ..< numToCombine {
|
|
let key = keys[i]
|
|
guard let pubkey = SECP256K1.parsePublicKey(serializedKey: key) else {return nil}
|
|
storage.append(pubkey)
|
|
}
|
|
for i in 0 ..< numToCombine {
|
|
withUnsafePointer(to: &storage[i]) { (ptr) -> Void in
|
|
arrayOfPointers.advanced(by: i).pointee = ptr
|
|
}
|
|
}
|
|
let immutablePointer = UnsafePointer(arrayOfPointers)
|
|
var publicKey: secp256k1_pubkey = secp256k1_pubkey()
|
|
let result = withUnsafeMutablePointer(to: &publicKey) { (pubKeyPtr: UnsafeMutablePointer<secp256k1_pubkey>) -> Int32 in
|
|
let res = secp256k1_ec_pubkey_combine(context!, pubKeyPtr, immutablePointer, numToCombine)
|
|
return res
|
|
}
|
|
if result == 0 {
|
|
return nil
|
|
}
|
|
let serializedKey = SECP256K1.serializePublicKey(publicKey: &publicKey, compressed: outputCompressed)
|
|
return serializedKey
|
|
}
|
|
|
|
|
|
internal static func recoverPublicKey(hash: Data, recoverableSignature: inout secp256k1_ecdsa_recoverable_signature) -> secp256k1_pubkey? {
|
|
guard hash.count == 32 else {return nil}
|
|
var publicKey: secp256k1_pubkey = secp256k1_pubkey()
|
|
let result = hash.withUnsafeBytes({ (hashRawBufferPointer: UnsafeRawBufferPointer) -> Int32? in
|
|
if let hashRawPointer = hashRawBufferPointer.baseAddress, hashRawBufferPointer.count > 0 {
|
|
let hashPointer = hashRawPointer.assumingMemoryBound(to: UInt8.self)
|
|
return withUnsafePointer(to: &recoverableSignature, { (signaturePointer:UnsafePointer<secp256k1_ecdsa_recoverable_signature>) -> Int32 in
|
|
withUnsafeMutablePointer(to: &publicKey, { (pubKeyPtr: UnsafeMutablePointer<secp256k1_pubkey>) -> Int32 in
|
|
let res = secp256k1_ecdsa_recover(context!, pubKeyPtr,
|
|
signaturePointer, hashPointer)
|
|
return res
|
|
})
|
|
})
|
|
} else {
|
|
return nil
|
|
}
|
|
})
|
|
guard let res = result, res != 0 else {
|
|
return nil
|
|
}
|
|
return publicKey
|
|
}
|
|
|
|
internal static func privateKeyToPublicKey(privateKey: Data) -> secp256k1_pubkey? {
|
|
if (privateKey.count != 32) {return nil}
|
|
var publicKey = secp256k1_pubkey()
|
|
let result = privateKey.withUnsafeBytes { (pkRawBufferPointer: UnsafeRawBufferPointer) -> Int32? in
|
|
if let pkRawPointer = pkRawBufferPointer.baseAddress, pkRawBufferPointer.count > 0 {
|
|
let privateKeyPointer = pkRawPointer.assumingMemoryBound(to: UInt8.self)
|
|
let res = secp256k1_ec_pubkey_create(context!, &publicKey, privateKeyPointer)
|
|
return res
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
guard let res = result, res != 0 else {
|
|
return nil
|
|
}
|
|
return publicKey
|
|
}
|
|
|
|
public static func serializePublicKey(publicKey: inout secp256k1_pubkey, compressed: Bool = false) -> Data? {
|
|
var keyLength = compressed ? 33 : 65
|
|
var serializedPubkey = Data(repeating: 0x00, count: keyLength)
|
|
let result = serializedPubkey.withUnsafeMutableBytes { (serializedPubkeyRawBuffPointer) -> Int32? in
|
|
if let serializedPkRawPointer = serializedPubkeyRawBuffPointer.baseAddress, serializedPubkeyRawBuffPointer.count > 0 {
|
|
let serializedPubkeyPointer = serializedPkRawPointer.assumingMemoryBound(to: UInt8.self)
|
|
return withUnsafeMutablePointer(to: &keyLength, { (keyPtr:UnsafeMutablePointer<Int>) -> Int32 in
|
|
withUnsafeMutablePointer(to: &publicKey, { (pubKeyPtr:UnsafeMutablePointer<secp256k1_pubkey>) -> Int32 in
|
|
let res = secp256k1_ec_pubkey_serialize(context!,
|
|
serializedPubkeyPointer,
|
|
keyPtr,
|
|
pubKeyPtr,
|
|
UInt32(compressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED))
|
|
return res
|
|
})
|
|
})
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
guard let res = result, res != 0 else {
|
|
return nil
|
|
}
|
|
return serializedPubkey
|
|
}
|
|
|
|
internal static func parsePublicKey(serializedKey: Data) -> secp256k1_pubkey? {
|
|
guard serializedKey.count == 33 || serializedKey.count == 65 else {
|
|
return nil
|
|
}
|
|
let keyLen: Int = Int(serializedKey.count)
|
|
var publicKey = secp256k1_pubkey()
|
|
let result = serializedKey.withUnsafeBytes { (serializedKeyRawBufferPointer: UnsafeRawBufferPointer) -> Int32? in
|
|
if let serializedKeyRawPointer = serializedKeyRawBufferPointer.baseAddress, serializedKeyRawBufferPointer.count > 0 {
|
|
let serializedKeyPointer = serializedKeyRawPointer.assumingMemoryBound(to: UInt8.self)
|
|
let res = secp256k1_ec_pubkey_parse(context!, &publicKey, serializedKeyPointer, keyLen)
|
|
return res
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
guard let res = result, res != 0 else {
|
|
return nil
|
|
}
|
|
return publicKey
|
|
}
|
|
|
|
public static func parseSignature(signature: Data) -> secp256k1_ecdsa_recoverable_signature? {
|
|
guard signature.count == 65 else {return nil}
|
|
var recoverableSignature: secp256k1_ecdsa_recoverable_signature = secp256k1_ecdsa_recoverable_signature()
|
|
let serializedSignature = signature[0..<64]
|
|
var v = Int32(signature[64])
|
|
if v >= 27 && v <= 30 {
|
|
v -= 27
|
|
} else if v >= 31 && v <= 34 {
|
|
v -= 31
|
|
} else if v >= 35 && v <= 38 {
|
|
v -= 35
|
|
}
|
|
let result = serializedSignature.withUnsafeBytes { (serRawBufferPtr: UnsafeRawBufferPointer) -> Int32? in
|
|
if let serRawPtr = serRawBufferPtr.baseAddress, serRawBufferPtr.count > 0 {
|
|
let serPtr = serRawPtr.assumingMemoryBound(to: UInt8.self)
|
|
return withUnsafeMutablePointer(to: &recoverableSignature, { (signaturePointer:UnsafeMutablePointer<secp256k1_ecdsa_recoverable_signature>) -> Int32 in
|
|
let res = secp256k1_ecdsa_recoverable_signature_parse_compact(context!, signaturePointer, serPtr, v)
|
|
return res
|
|
})
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
guard let res = result, res != 0 else {
|
|
return nil
|
|
}
|
|
return recoverableSignature
|
|
}
|
|
|
|
internal static func serializeSignature(recoverableSignature: inout secp256k1_ecdsa_recoverable_signature) -> Data? {
|
|
var serializedSignature = Data(repeating: 0x00, count: 64)
|
|
var v: Int32 = 0
|
|
let result = serializedSignature.withUnsafeMutableBytes { (serSignatureRawBufferPointer: UnsafeMutableRawBufferPointer) -> Int32? in
|
|
if let serSignatureRawPointer = serSignatureRawBufferPointer.baseAddress, serSignatureRawBufferPointer.count > 0 {
|
|
let serSignaturePointer = serSignatureRawPointer.assumingMemoryBound(to: UInt8.self)
|
|
return withUnsafePointer(to: &recoverableSignature) { (signaturePointer:UnsafePointer<secp256k1_ecdsa_recoverable_signature>) -> Int32 in
|
|
withUnsafeMutablePointer(to: &v, { (vPtr: UnsafeMutablePointer<Int32>) -> Int32 in
|
|
let res = secp256k1_ecdsa_recoverable_signature_serialize_compact(context!, serSignaturePointer, vPtr, signaturePointer)
|
|
return res
|
|
})
|
|
}
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
guard let res = result, res != 0 else {
|
|
return nil
|
|
}
|
|
if (v == 0 || v == 27 || v == 31 || v == 35) {
|
|
serializedSignature.append(0x1b)
|
|
} else if (v == 1 || v == 28 || v == 32 || v == 36) {
|
|
serializedSignature.append(0x1c)
|
|
} else {
|
|
return nil
|
|
}
|
|
return serializedSignature
|
|
}
|
|
|
|
internal static func recoverableSign(hash: Data, privateKey: Data, useExtraEntropy: Bool = false) -> secp256k1_ecdsa_recoverable_signature? {
|
|
if (hash.count != 32 || privateKey.count != 32) {
|
|
return nil
|
|
}
|
|
if !SECP256K1.verifyPrivateKey(privateKey: privateKey) {
|
|
return nil
|
|
}
|
|
var recoverableSignature: secp256k1_ecdsa_recoverable_signature = secp256k1_ecdsa_recoverable_signature();
|
|
guard let extraEntropy = SECP256K1.randomBytes(length: 32) else {return nil}
|
|
let result = hash.withUnsafeBytes { (hashRBPointer) -> Int32? in
|
|
if let hashRPointer = hashRBPointer.baseAddress, hashRBPointer.count > 0 {
|
|
let hashPointer = hashRPointer.assumingMemoryBound(to: UInt8.self)
|
|
return privateKey.withUnsafeBytes({ (privateKeyRBPointer) -> Int32? in
|
|
if let privateKeyRPointer = privateKeyRBPointer.baseAddress, privateKeyRBPointer.count > 0 {
|
|
let privateKeyPointer = privateKeyRPointer.assumingMemoryBound(to: UInt8.self)
|
|
return extraEntropy.withUnsafeBytes({ (extraEntropyRBPointer) -> Int32? in
|
|
if let extraEntropyRPointer = extraEntropyRBPointer.baseAddress, extraEntropyRBPointer.count > 0 {
|
|
let extraEntropyPointer = extraEntropyRPointer.assumingMemoryBound(to: UInt8.self)
|
|
return withUnsafeMutablePointer(to: &recoverableSignature, { (recSignaturePtr: UnsafeMutablePointer<secp256k1_ecdsa_recoverable_signature>) -> Int32 in
|
|
let res = secp256k1_ecdsa_sign_recoverable(context!, recSignaturePtr, hashPointer, privateKeyPointer, nil, useExtraEntropy ? extraEntropyPointer : nil)
|
|
return res
|
|
})
|
|
} else {
|
|
return nil
|
|
}
|
|
})
|
|
} else {
|
|
return nil
|
|
}
|
|
})
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
guard let res = result, res != 0 else {
|
|
print("Failed to sign!")
|
|
return nil
|
|
}
|
|
return recoverableSignature
|
|
}
|
|
|
|
public static func recoverPublicKey(hash: Data, signature: Data, compressed: Bool = false) -> Data? {
|
|
guard hash.count == 32, signature.count == 65 else {return nil}
|
|
guard var recoverableSignature = parseSignature(signature: signature) else {return nil}
|
|
guard var publicKey = SECP256K1.recoverPublicKey(hash: hash, recoverableSignature: &recoverableSignature) else {return nil}
|
|
guard let serializedKey = SECP256K1.serializePublicKey(publicKey: &publicKey, compressed: compressed) else {return nil}
|
|
return serializedKey
|
|
}
|
|
|
|
|
|
public static func verifyPrivateKey(privateKey: Data) -> Bool {
|
|
if (privateKey.count != 32) {return false}
|
|
let result = privateKey.withUnsafeBytes { (privateKeyRBPointer) -> Int32? in
|
|
if let privateKeyRPointer = privateKeyRBPointer.baseAddress, privateKeyRBPointer.count > 0 {
|
|
let privateKeyPointer = privateKeyRPointer.assumingMemoryBound(to: UInt8.self)
|
|
let res = secp256k1_ec_seckey_verify(context!, privateKeyPointer)
|
|
return res
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
guard let res = result, res == 1 else {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
public static func generatePrivateKey() -> Data? {
|
|
for _ in 0...1024 {
|
|
guard let keyData = SECP256K1.randomBytes(length: 32) else {
|
|
continue
|
|
}
|
|
guard SECP256K1.verifyPrivateKey(privateKey: keyData) else {
|
|
continue
|
|
}
|
|
return keyData
|
|
}
|
|
return nil
|
|
}
|
|
|
|
public static func unmarshalSignature(signatureData:Data) -> UnmarshaledSignature? {
|
|
if (signatureData.count != 65) {return nil}
|
|
let v = signatureData[64]
|
|
let r = signatureData[0..<32]
|
|
let s = signatureData[32..<64]
|
|
return UnmarshaledSignature(v: v, r: r, s: s)
|
|
}
|
|
|
|
public static func marshalSignature(v: UInt8, r: [UInt8], s: [UInt8]) -> Data? {
|
|
guard r.count == 32, s.count == 32 else {return nil}
|
|
var completeSignature = Data(r)
|
|
completeSignature.append(Data(s))
|
|
completeSignature.append(Data([v]))
|
|
return completeSignature
|
|
}
|
|
|
|
public static func marshalSignature(v: Data, r: Data, s: Data) -> Data? {
|
|
guard r.count == 32, s.count == 32 else {return nil}
|
|
var completeSignature = r
|
|
completeSignature.append(s)
|
|
completeSignature.append(v)
|
|
return completeSignature
|
|
}
|
|
|
|
internal static func randomBytes(length: Int) -> Data? {
|
|
for _ in 0...1024 {
|
|
var data = Data(repeating: 0, count: length)
|
|
let result = data.withUnsafeMutableBytes { (mutableRBBytes) -> Int32? in
|
|
if let mutableRBytes = mutableRBBytes.baseAddress, mutableRBBytes.count > 0 {
|
|
let mutableBytes = mutableRBytes.assumingMemoryBound(to: UInt8.self)
|
|
return SecRandomCopyBytes(kSecRandomDefault, 32, mutableBytes)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
if let res = result, res == errSecSuccess {
|
|
return data
|
|
} else {
|
|
continue
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
internal static func toByteArray<T>(_ value: T) -> [UInt8] {
|
|
var value = value
|
|
return withUnsafeBytes(of: &value) { Array($0) }
|
|
}
|
|
|
|
internal static func fromByteArray<T>(_ value: [UInt8], _: T.Type) -> T {
|
|
return value.withUnsafeBytes {
|
|
$0.baseAddress!.load(as: T.self)
|
|
}
|
|
}
|
|
|
|
internal static func constantTimeComparison(_ lhs: Data, _ rhs:Data) -> Bool {
|
|
guard lhs.count == rhs.count else {return false}
|
|
var difference = UInt8(0x00)
|
|
for i in 0..<lhs.count { // compare full length
|
|
difference |= lhs[i] ^ rhs[i] //constant time
|
|
}
|
|
return difference == UInt8(0x00)
|
|
}
|
|
}
|