Skip to content

Commit 40207a3

Browse files
committed
IOS-6784 Add public key recovery interface
1 parent bf339b4 commit 40207a3

File tree

5 files changed

+45
-5
lines changed

5 files changed

+45
-5
lines changed

TangemSdk/TangemSdk/Common/Extensions/HexConvertible.swift

+1
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ public extension HexConvertible where Self: FixedWidthInteger {
2525

2626
extension Int: HexConvertible {}
2727
extension UInt64: HexConvertible {}
28+
extension Int32: HexConvertible {}

TangemSdk/TangemSdk/Crypto/Secp256k1Key.swift

+9-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,15 @@ public struct Secp256k1Key {
1717
public init(with data: Data) throws {
1818
secp256k1PubKey = try secp256k1.parsePublicKey(data)
1919
}
20-
20+
21+
public init(with signature: Secp256k1Signature.Extended, hash: Data) throws {
22+
secp256k1PubKey = try secp256k1.recoverPublicKey(signatureCoponents: signature.components, hash: hash)
23+
}
24+
25+
public init(with signature: Secp256k1Signature.Extended, message: Data) throws {
26+
try self.init(with: signature, hash: message.getSha256())
27+
}
28+
2129
public func compress() throws -> Data {
2230
var pubkey = secp256k1PubKey
2331
return try secp256k1.serializePublicKey(&pubkey, compressed: true)

TangemSdk/TangemSdk/Crypto/Secp256k1Signature.swift

+6-2
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ public struct Secp256k1Signature {
4040

4141
public func unmarshal(with publicKey: Data, hash: Data) throws -> Extended {
4242
var sig = rawSig
43-
let (r, s, v) = try secp256k1.unmarshalSignature(&sig, publicKey: publicKey, hash: hash)
44-
return Extended(r: r, s: s, v: v)
43+
let components = try secp256k1.unmarshalSignature(&sig, publicKey: publicKey, hash: hash)
44+
return Extended(r: components.r, s: components.s, v: components.v)
4545
}
4646
}
4747

@@ -57,6 +57,10 @@ extension Secp256k1Signature {
5757
return r + s + v
5858
}
5959

60+
var components: Secp256k1SignatureComponents {
61+
(r,s,v)
62+
}
63+
6064
public init(r: Data, s: Data, v: Data) {
6165
self.r = r
6266
self.s = s

TangemSdk/TangemSdk/Crypto/Secp256k1Utils.swift

+12-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import Foundation
1010
import TangemSdk_secp256k1
1111

12+
typealias Secp256k1SignatureComponents = (r: Data, s: Data, v: Data)
13+
1214
@available(iOS 13.0, *)
1315
public final class Secp256k1Utils {
1416
private let context: OpaquePointer
@@ -174,7 +176,7 @@ public final class Secp256k1Utils {
174176
return Data(der[0..<Int(length)])
175177
}
176178

177-
func unmarshalSignature(_ signature: inout secp256k1_ecdsa_signature, publicKey: Data, hash: Data) throws -> (r: Data, s: Data, v: Data) {
179+
func unmarshalSignature(_ signature: inout secp256k1_ecdsa_signature, publicKey: Data, hash: Data) throws -> Secp256k1SignatureComponents {
178180
guard hash.count == 32 else { throw TangemSdkError.cryptoUtilsError("Hash size must be 32 bytes length") }
179181

180182
guard try verifySignature(&signature, publicKey: publicKey, hash: hash) else {
@@ -247,6 +249,15 @@ public final class Secp256k1Utils {
247249
return pubkey
248250
}
249251

252+
func recoverPublicKey(signatureCoponents: Secp256k1SignatureComponents, hash: Data) throws -> secp256k1_pubkey {
253+
guard let intV = Int32(hexData: signatureCoponents.v) else {
254+
throw TangemSdkError.cryptoUtilsError("Failed to parse v")
255+
}
256+
257+
var recoverableSignature = try parseRecoverableSignature(signatureCoponents.r + signatureCoponents.s, v: intV)
258+
return try recoverPublicKey(hash: hash, recoverableSignature: &recoverableSignature)
259+
}
260+
250261
func parseXOnlyPublicKey(_ publicKey: Data) throws -> secp256k1_xonly_pubkey {
251262
var pubkey = secp256k1_xonly_pubkey()
252263

TangemSdk/TangemSdkTests/CryptoUtilsTests.swift

+17-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ class CryptoUtilsTests: XCTestCase {
140140
XCTAssertEqual(normalized.hexString, "5365F955FC45763383936BBC021A15D583E8D2300D1A65D21853B6A0FCAECE4E29AF6C44A13AD6E138336A4BD872F15FC517C30816E9B4C57858697AEBE25364")
141141
}
142142

143-
func testRecover() {
143+
func testSignatureUnmarshal() {
144144
let privateKey = Data(hexString: "fd230007d4a39352f50d8c481456c1f86ddc5ff155df170af0100a62269852f0")
145145
let publicKey = Data(hexString: "0432f507f6a3029028faa5913838c50f5ff3355b9b000b51889d03a2bdb96570cd750e8187482a27ca9d2dd0c92c632155d0384521ed406753c9883621ad0da68c")
146146

@@ -153,6 +153,22 @@ class CryptoUtilsTests: XCTestCase {
153153
XCTAssertEqual(unmarshalled?.v.hexString, "1C")
154154
}
155155

156+
func testRecoverPublicKey() throws {
157+
let privateKey = Data(hexString: "fd230007d4a39352f50d8c481456c1f86ddc5ff155df170af0100a62269852f0")
158+
let publicKey = Data(hexString: "0432f507f6a3029028faa5913838c50f5ff3355b9b000b51889d03a2bdb96570cd750e8187482a27ca9d2dd0c92c632155d0384521ed406753c9883621ad0da68c")
159+
let dummyData = Data(repeating: UInt8(1), count: 64)
160+
let hash = dummyData.getSha256()
161+
162+
let signature = try Secp256k1Utils().sign(dummyData, with: privateKey)
163+
let unmarshalled = try Secp256k1Signature(with: signature).unmarshal(with: publicKey, hash: hash)
164+
165+
let key = try Secp256k1Key(with: unmarshalled, hash: hash)
166+
XCTAssertEqual(try key.decompress().hexString, publicKey.hexString)
167+
168+
let key1 = try Secp256k1Key(with: unmarshalled, message: dummyData)
169+
XCTAssertEqual(try key1.decompress().hexString, publicKey.hexString)
170+
}
171+
156172
func testSecp256k1PrivateKeyValidation() {
157173
let utils = Secp256k1Utils()
158174

0 commit comments

Comments
 (0)