Skip to content

Commit 49d8690

Browse files
committed
fix(swift): Various bugs in key related things
Add tests too! Signed-off-by: Skyler Ross <[email protected]>
1 parent ff1c7d2 commit 49d8690

File tree

4 files changed

+228
-4
lines changed

4 files changed

+228
-4
lines changed

sdk/swift/Sources/Hedera/PrivateKey.swift

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,28 @@ private struct ChainCode {
5252
#endif
5353

5454
/// A private key on the Hedera network.
55-
public struct PrivateKey: LosslessStringConvertible, ExpressibleByStringLiteral {
55+
public struct PrivateKey: LosslessStringConvertible, ExpressibleByStringLiteral, CustomStringConvertible,
56+
CustomDebugStringConvertible
57+
{
58+
/// Debug description for `PrivateKey`
59+
///
60+
/// Please note that debugDescriptions of any kind should not be considered a stable format.
61+
public var debugDescription: String {
62+
"PrivateKey(kind: \(String(reflecting: guts)), chainCode: \(String(describing: chainCode?.data))"
63+
}
64+
5665
// we need to be sendable, so...
5766
// The idea being that we initialize the key whenever we need it, which is absolutely not free, but it is `Sendable`.
58-
fileprivate enum Repr {
67+
fileprivate enum Repr: CustomDebugStringConvertible {
68+
fileprivate var debugDescription: String {
69+
switch self {
70+
case .ed25519:
71+
return "ed25519([redacted])"
72+
case .ecdsa:
73+
return "ecdsa([redacted])"
74+
}
75+
}
76+
5977
case ed25519(Data)
6078
case ecdsa(Data)
6179

@@ -462,6 +480,35 @@ public struct PrivateKey: LosslessStringConvertible, ExpressibleByStringLiteral
462480
}
463481
}
464482

483+
// for testing purposes :/
484+
extension PrivateKey {
485+
internal func withChainCode(chainCode: Data) -> Self {
486+
precondition(chainCode.count == 32)
487+
return Self(kind: kind, chainCode: chainCode)
488+
}
489+
490+
internal func prettyPrint() -> String {
491+
let data = toStringRaw()
492+
let chainCode = String(describing: chainCode?.data.hexStringEncoded())
493+
494+
let start: String
495+
496+
switch guts {
497+
case .ecdsa:
498+
start = "PrivateKey.ecdsa"
499+
case .ed25519:
500+
start = "PrivateKey.ed25519"
501+
}
502+
503+
return """
504+
\(start)(
505+
key: \(data),
506+
chainCode: \(chainCode)
507+
)
508+
"""
509+
}
510+
}
511+
465512
#if compiler(>=5.7)
466513
extension PrivateKey.Repr: Sendable {}
467514
#else

sdk/swift/Sources/Hedera/PublicKey.swift

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import Foundation
2323
import HederaProtobufs
2424
import SwiftASN1
2525
import secp256k1
26+
import secp256k1_bindings
2627

2728
/// A public key on the Hedera network.
2829
public struct PublicKey: LosslessStringConvertible, ExpressibleByStringLiteral, Equatable, Hashable {
@@ -279,7 +280,7 @@ public struct PublicKey: LosslessStringConvertible, ExpressibleByStringLiteral,
279280
let isValid: Bool
280281
do {
281282
isValid = try key.ecdsa.isValidSignature(
282-
.init(rawRepresentation: signature), for: Keccak256Digest(Crypto.Sha3.keccak256(message))!)
283+
.init(compactRepresentation: signature), for: Keccak256Digest(Crypto.Sha3.keccak256(message))!)
283284
} catch {
284285
throw HError(kind: .signatureVerify, description: "invalid signature")
285286
}
@@ -323,8 +324,38 @@ public struct PublicKey: LosslessStringConvertible, ExpressibleByStringLiteral,
323324
return nil
324325
}
325326

327+
// when the bindings aren't enough :/
328+
var pubkey = secp256k1_pubkey()
329+
330+
key.rawRepresentation.withUnsafeTypedBytes { bytes in
331+
let result = secp256k1_bindings.secp256k1_ec_pubkey_parse(
332+
secp256k1.Context.raw,
333+
&pubkey,
334+
bytes.baseAddress!,
335+
bytes.count
336+
)
337+
338+
precondition(result == 1)
339+
}
340+
341+
var output = Data(repeating: 0, count: 65)
342+
343+
output.withUnsafeMutableTypedBytes { output in
344+
var outputLen = output.count
345+
346+
let result = secp256k1_ec_pubkey_serialize(
347+
secp256k1.Context.raw, output.baseAddress!,
348+
&outputLen,
349+
&pubkey,
350+
secp256k1.Format.uncompressed.rawValue
351+
)
352+
353+
precondition(result == 1)
354+
precondition(outputLen == output.count)
355+
}
356+
326357
// fixme(important): sec1 uncompressed point
327-
let hash = Crypto.Sha3.keccak256(key.rawRepresentation)
358+
let hash = Crypto.Sha3.keccak256(output[1...])
328359

329360
return try! EvmAddress(Data(hash.dropFirst(12)))
330361

sdk/swift/Tests/HederaTests/PrivateKeyTests.swift

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ import XCTest
2222

2323
@testable import Hedera
2424

25+
private func keyWithChain(key: PrivateKey, chainCode: String) -> PrivateKey {
26+
key.withChainCode(chainCode: Data(hexEncoded: chainCode)!)
27+
}
28+
2529
internal final class PrivateKeyTests: XCTestCase {
2630
internal func testParseEd25519() throws {
2731
let privateKey: PrivateKey =
@@ -41,6 +45,30 @@ internal final class PrivateKeyTests: XCTestCase {
4145
"3030020100300706052b8104000a042204208776c6b831a1b61ac10dac0304a2843de4716f54b1919bb91a2685d0fe3f3048")
4246
}
4347

48+
internal func testEd25519Sign() throws {
49+
let message = "hello, world".data(using: .utf8)!
50+
let privateKey: PrivateKey =
51+
"302e020100300506032b657004220420db484b828e64b2d8f12ce3c0a0e93a0b8cce7af1bb8f39c97732394482538e10"
52+
53+
let signature = privateKey.sign(message)
54+
55+
// note that CryptoKit randomizes the signature, *sigh*, so the only thing we *can* test is that the signature verifies.
56+
57+
XCTAssertNoThrow(try privateKey.publicKey.verify(message, signature))
58+
}
59+
60+
internal func testEcdsaSign() throws {
61+
let privateKey: PrivateKey =
62+
"3030020100300706052b8104000a042204208776c6b831a1b61ac10dac0304a2843de4716f54b1919bb91a2685d0fe3f3048"
63+
64+
let signature = privateKey.sign("hello world".data(using: .utf8)!)
65+
66+
XCTAssertEqual(
67+
signature.hexStringEncoded(),
68+
"f3a13a555f1f8cd6532716b8f388bd4e9d8ed0b252743e923114c0c6cbfe414c086e3717a6502c3edff6130d34df252fb94b6f662d0cd27e2110903320563851"
69+
)
70+
}
71+
4472
internal func testEd25519LegacyDerive() throws {
4573
let privateKey: PrivateKey =
4674
"302e020100300506032b65700422042098aa82d6125b5efa04bf8372be7931d05cd77f5ef3330b97d6ee7c006eaaf312"
@@ -69,6 +97,47 @@ internal final class PrivateKeyTests: XCTestCase {
6997
"302e020100300506032b6570042204206890dc311754ce9d3fc36bdf83301aa1c8f2556e035a6d0d13c2cccdbbab1242")
7098
}
7199

100+
// "iosKey"
101+
internal func testEd25519Derive1() throws {
102+
let key = keyWithChain(
103+
key: "302e020100300506032b657004220420a6b9548d7e123ad4c8bc6fee58301e9b96360000df9d03785c07b620569e7728",
104+
chainCode: "cde7f535264f1db4e2ded409396f8c72f8075cc43757bd5a205c97699ea40271"
105+
)
106+
107+
let child = try key.derive(0)
108+
109+
XCTAssertEqual(
110+
child.prettyPrint(),
111+
#"""
112+
PrivateKey.ed25519(
113+
key: 5f66a51931e8c99089472e0d70516b6272b94dd772b967f8221e1077f966dbda,
114+
chainCode: Optional("0e5c869c1cf9daecd03edb2d49cf2621412578a352578a4bb7ef4eef2942b7c9")
115+
)
116+
"""#
117+
)
118+
}
119+
120+
// "androidKey"
121+
internal func testEd25519Derive2() throws {
122+
let key = keyWithChain(
123+
key:
124+
"302e020100300506032b65700422042097dbce1988ef8caf5cf0fd13a5374969e2be5f50650abd19314db6b32f96f18e",
125+
chainCode: "b7b406314eb2224f172c1907fe39f807e306655e81f2b3bc4766486f42ef1433"
126+
)
127+
128+
let child = try key.derive(0)
129+
130+
XCTAssertEqual(
131+
child.prettyPrint(),
132+
#"""
133+
PrivateKey.ed25519(
134+
key: c284c25b3a1458b59423bc289e83703b125c8eefec4d5aa1b393c2beb9f2bae6,
135+
chainCode: Optional("a7a1c2d115a988e51efc12c23692188a4796b312a4a700d6c703e4de4cf1a7f6")
136+
)
137+
"""#
138+
)
139+
}
140+
72141
internal func testEd25519FromPem() throws {
73142
let pemString = """
74143
-----BEGIN PRIVATE KEY-----

sdk/swift/Tests/HederaTests/PublicKeyTests.swift

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,81 @@ internal final class PublicKeyTests: XCTestCase {
4646
key: "302d300706052b8104000a03220002703a9370b0443be6ae7c507b0aec81a55e94e4a863b9655360bd65358caa6588"
4747
)
4848
}
49+
50+
internal func testToEvmAddress() throws {
51+
let publicKey = try PrivateKey.fromStringEcdsa(
52+
"debae3ca62ab3157110dba79c8de26540dc320ee9be73a77d70ba175643a3500"
53+
).publicKey
54+
55+
let evmAddress = publicKey.toEvmAddress()
56+
57+
XCTAssertEqual(evmAddress, "0xd8eb8db03c699faa3f47adcdcd2ae91773b10f8b")
58+
}
59+
60+
internal func testToEvmAddress2() throws {
61+
let publicKey = try PublicKey.fromStringEcdsa(
62+
"029469a657510f3bf199a0e29b21e11e7039d8883f3547d59c3568f9c89f704cbc")
63+
64+
let evmAddress = publicKey.toEvmAddress()
65+
66+
XCTAssertEqual(evmAddress, "0xbbaa6bdfe888ae1fc8e7c8cee82081fa79ba8834")
67+
}
68+
69+
internal func testEd25519Verify() throws {
70+
let publicKey = try PublicKey.fromStringDer(
71+
"302a300506032b6570032100e0c8ec2758a5879ffac226a13c0c516b799e72e35141a0dd828f94d37988a4b7"
72+
)
73+
74+
let signature = Data(
75+
hexEncoded: "9d04bfed7baa97c80d29a6ae48c0d896ce8463a7ea0c16197d55a563c73996ef"
76+
+ "062b2adf507f416c108422c0310fc6fb21886e11ce3de3e951d7a56049743f07"
77+
)!
78+
79+
XCTAssertNoThrow(try publicKey.verify("hello, world".data(using: .utf8)!, signature))
80+
}
81+
82+
internal func testEcdsaVerify() throws {
83+
let publicKey = try PublicKey.fromStringDer(
84+
"302d300706052b8104000a03220002703a9370b0443be6ae7c507b0aec81a55e94e4a863b9655360bd65358caa6588"
85+
)
86+
87+
let signature = Data(
88+
hexEncoded: "f3a13a555f1f8cd6532716b8f388bd4e9d8ed0b252743e923114c0c6cbfe414c"
89+
+ "086e3717a6502c3edff6130d34df252fb94b6f662d0cd27e2110903320563851"
90+
)!
91+
92+
XCTAssertNoThrow(try publicKey.verify("hello world".data(using: .utf8)!, signature))
93+
}
94+
95+
/// We should error if we try to verify a signature with the wrong public key (or the right public key with the wrong signature, same thing)
96+
///
97+
/// This in particular tests attempting to verify a ecdsa-secp256k1 signature for an ed25519 public key.
98+
internal func testEd25519VerifyErrorEcdsa() throws {
99+
let publicKey = try PublicKey.fromStringDer(
100+
"302a300506032b6570032100e0c8ec2758a5879ffac226a13c0c516b799e72e35141a0dd828f94d37988a4b7"
101+
)
102+
103+
let signature = Data(
104+
hexEncoded: "f3a13a555f1f8cd6532716b8f388bd4e9d8ed0b252743e923114c0c6cbfe414c"
105+
+ "086e3717a6502c3edff6130d34df252fb94b6f662d0cd27e2110903320563851"
106+
)!
107+
108+
XCTAssertThrowsError(try publicKey.verify("hello, world".data(using: .utf8)!, signature))
109+
}
110+
111+
/// We should error if we try to verify a signature with the wrong public key (or the right public key with the wrong signature, same thing)
112+
///
113+
/// This in particular tests attempting to verify a ed25519 signature for an ecdsa-secp256k1 public key.
114+
internal func testEcdsaVerifyErrorEd25519() throws {
115+
let publicKey = try PublicKey.fromStringDer(
116+
"302d300706052b8104000a03220002703a9370b0443be6ae7c507b0aec81a55e94e4a863b9655360bd65358caa6588"
117+
)
118+
119+
let signature = Data(
120+
hexEncoded: "9d04bfed7baa97c80d29a6ae48c0d896ce8463a7ea0c16197d55a563c73996ef"
121+
+ "062b2adf507f416c108422c0310fc6fb21886e11ce3de3e951d7a56049743f07"
122+
)!
123+
124+
XCTAssertThrowsError(try publicKey.verify("hello world".data(using: .utf8)!, signature))
125+
}
49126
}

0 commit comments

Comments
 (0)