Skip to content

Commit b1f043e

Browse files
authored
Ecdh custom test (#8)
1 parent 27805f1 commit b1f043e

File tree

3 files changed

+81
-21
lines changed

3 files changed

+81
-21
lines changed

README.md

+47-3
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,57 @@ It is worth noting that some Schnorr implementations are incompatible with [BIP3
3636

3737
## ECDH
3838

39+
This library vendors three different EC Diffie-Hellman (ECDH) key exchange functions:
40+
1. `ASN1 x9.63` - No hash, return only the `X` coordinate of the point - `sharedSecretFromKeyAgreement -> SharedSecret`
41+
2. `libsecp256k1` - SHA-256 hash the compressed point - `ecdh -> Data`
42+
3. Custom - No hash, return point uncompressed - `ecdhPoint -> Data`
43+
3944
```swift
4045
let alice = try K1.PrivateKey.generateNew()
4146
let bob = try K1.PrivateKey.generateNew()
47+
```
48+
49+
### `ASN1 x9.63` ECDH
50+
Returning only the `X` coordinate of the point, following [ANSI X9.63][x963] standards, embedded in a [`CryptoKit.SharedSecret`][ckss], which is useful since you can use `CryptoKit` key derivation functions on this SharedSecret, e.g. [`x963DerivedSymmetricKey`](https://developer.apple.com/documentation/cryptokit/sharedsecret/x963derivedsymmetrickey(using:sharedinfo:outputbytecount:)) or [`hkdfDerivedSymmetricKey`](https://developer.apple.com/documentation/cryptokit/sharedsecret/hkdfderivedsymmetrickey(using:salt:sharedinfo:outputbytecount:)).
51+
52+
You can retrieve the `X` coordinate as raw data using `withUnsafeBytes` if you need to.
53+
54+
```swift
55+
let ab: CryptoKit.SharedSecret = try alice.sharedSecretFromKeyAgreement(with: bob.publicKey)
56+
let ba: : CryptoKit.SharedSecret = try bob.sharedSecretFromKeyAgreement(with: alice.publicKey)
57+
58+
assert(ab == ba) // pass
59+
60+
ab.withUnsafeBytes {
61+
assert(Data($0).count == 32) // pass
62+
}
63+
```
64+
65+
### `libsecp256k1` ECDH
4266

43-
let ab = try alice.sharedSecret(with: bob.publicKey)
44-
let ba = try bob.sharedSecret(with: alice.publicKey)
45-
assert(ab == ba, "Alice and Bob should be able to agree on the same secret")
67+
Using `libsecp256k1` default behaviour, returning a SHA-256 hash of the **compressed** point.
68+
69+
```swift
70+
let ab: Data = try alice.ecdh(with: bob.publicKey)
71+
let ba: Data = try bob.ecdh(with: alice.publicKey)
72+
assert(ab == ba) // pass
73+
74+
assert(ab.count == 32) // pass
75+
```
76+
77+
### Custom ECDH
78+
79+
Returns an entire uncompresed EC point, without hashing it. Might be useful if you wanna construct your own cryptographic functions, e.g. some custom ECIES.
80+
81+
```swift
82+
let ab: Data = try alice.ecdhPoint(with: bob.publicKey)
83+
let ba: Data = try bob.ecdhPoint(with: alice.publicKey)
84+
assert(ab == ba) // pass
85+
86+
assert(ab.count == 65) // pass
4687
```
4788

89+
4890
# Alternatives
4991

5092
- [GigaBitcoin/secp256k1.swift](https://github.com/GigaBitcoin/secp256k1.swift) (also using `libsecp256k1`, ⚠️ possibly unsafe, ✅ Schnorr support)
@@ -70,3 +112,5 @@ To clone the dependency [libsecp256k1][lib], using commit [427bc3cdcfbc747780704
70112

71113
[BIP340]: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
72114
[lib]: https://github.com/bitcoin-core/secp256k1
115+
[x963]: https://webstore.ansi.org/standards/ascx9/ansix9632011r2017
116+
[ckss]: https://developer.apple.com/documentation/cryptokit/sharedsecret

Sources/K1/K1/Keys/PrivateKey/PrivateKey/PrivateKey+Bridge+To+C.swift

+11-11
Original file line numberDiff line numberDiff line change
@@ -548,16 +548,14 @@ extension K1.PrivateKey {
548548
return sharedSecretData
549549
}
550550

551-
552-
553551
/// Computes a shared secret with the provided public key from another party,
554552
/// returning only the `X` coordinate of the point, following [ANSI X9.63][ansix963] standards.
555553
///
556554
/// This is one of three ECDH functions, this library vendors, all three versions
557555
/// uses different serialization of the shared EC Point, specifically:
558-
/// 1. SHA-256 hash the compressed point
559-
/// 2. No hash, return point uncompressed
560-
/// 3. No hash, return only the `X` coordinate of the point <- this function
556+
/// 1. `ASN1 x9.63`: No hash, return only the `X` coordinate of the point <- this function
557+
/// 2. `libsecp256k1`: SHA-256 hash the compressed point
558+
/// 3. Custom: No hash, return point uncompressed
561559
///
562560
/// This function uses 3. i.e. no hash, and returns only the `X` coordinate of the point.
563561
/// This is following the [ANSI X9.63][ansix963] standard serialization of the shared point.
@@ -591,9 +589,9 @@ extension K1.PrivateKey {
591589
///
592590
/// This is one of three ECDH functions, this library vendors, all three versions
593591
/// uses different serialization of the shared EC Point, specifically:
594-
/// 1. SHA-256 hash the compressed point <- this function
595-
/// 2. No hash, return point uncompressed
596-
/// 3. No hash, return only the `X` coordinate of the point.
592+
/// 1. `ASN1 x9.63`: No hash, return only the `X` coordinate of the point
593+
/// 2. `libsecp256k1`: SHA-256 hash the compressed point <- this function
594+
/// 3. Custom: No hash, return point uncompressed
597595
///
598596
/// This function uses 1. i.e.SHA-256 hash the compressed point.
599597
/// This is using the [default behaviour of `libsecp256k1`][libsecp256k1], which does not adhere to any
@@ -611,14 +609,15 @@ extension K1.PrivateKey {
611609
try _ecdh(publicKey: publicKey, serializeOutputFunction: .libsecp256kDefault)
612610
}
613611

612+
614613
/// Computes a shared secret with the provided public key from another party,
615614
/// returning an uncompressed public point, unhashed.
616615
///
617616
/// This is one of three ECDH functions, this library vendors, all three versions
618617
/// uses different serialization of the shared EC Point, specifically:
619-
/// 1. SHA-256 hash the compressed point
620-
/// 2. No hash, return point uncompressed <- this function
621-
/// 3. No hash, return only the `X` coordinate of the point.
618+
/// 1. `ASN1 x9.63`: No hash, return only the `X` coordinate of the point
619+
/// 2. `libsecp256k1`: SHA-256 hash the compressed point
620+
/// 3. Custom: No hash, return point uncompressed <- this function
622621
///
623622
/// This function uses 2. i.e. no hash, return point uncompressed
624623
/// **This is not following any standard at all**, but might be useful if you want to write your
@@ -627,6 +626,7 @@ extension K1.PrivateKey {
627626
public func ecdhPoint(with publicKey: K1.PublicKey) throws -> Data {
628627
try _ecdh(publicKey: publicKey, serializeOutputFunction: .noHashWholePoint)
629628
}
629+
630630
}
631631

632632
// MUST match https://github.com/apple/swift-crypto/blob/main/Sources/Crypto/Key%20Agreement/DH.swift#L34

Tests/K1Tests/TestCases/ECDH/ECDHTests.swift

+23-7
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,36 @@ import XCTest
1111

1212
final class ECDHTests: XCTestCase {
1313

14-
func testECDHPoint() throws {
14+
func testECDHX963() throws {
1515
let alice = try K1.PrivateKey.generateNew()
1616
let bob = try K1.PrivateKey.generateNew()
1717

18-
let ab = try alice.ecdhPoint(with: bob.publicKey)
19-
let ba = try bob.ecdhPoint(with: alice.publicKey)
18+
let ab = try alice.sharedSecretFromKeyAgreement(with: bob.publicKey)
19+
let ba = try bob.sharedSecretFromKeyAgreement(with: alice.publicKey)
20+
ab.withUnsafeBytes {
21+
XCTAssertEqual(Data($0).count, 32)
22+
}
2023
XCTAssertEqual(ab, ba, "Alice and Bob should be able to agree on the same secret")
2124
}
22-
25+
2326
func testECDHLibsecp256k1() throws {
2427
let alice = try K1.PrivateKey.generateNew()
2528
let bob = try K1.PrivateKey.generateNew()
2629

2730
let ab = try alice.ecdh(with: bob.publicKey)
2831
let ba = try bob.ecdh(with: alice.publicKey)
2932
XCTAssertEqual(ab, ba, "Alice and Bob should be able to agree on the same secret")
33+
XCTAssertEqual(ab.count, 32)
3034
}
3135

32-
func testECDHX963() throws {
36+
func testECDHPoint() throws {
3337
let alice = try K1.PrivateKey.generateNew()
3438
let bob = try K1.PrivateKey.generateNew()
3539

36-
let ab = try alice.sharedSecretFromKeyAgreement(with: bob.publicKey)
37-
let ba = try bob.sharedSecretFromKeyAgreement(with: alice.publicKey)
40+
let ab = try alice.ecdhPoint(with: bob.publicKey)
41+
let ba = try bob.ecdhPoint(with: alice.publicKey)
3842
XCTAssertEqual(ab, ba, "Alice and Bob should be able to agree on the same secret")
43+
XCTAssertEqual(ab.count, 65)
3944
}
4045

4146
/// Test vectors from: https://crypto.stackexchange.com/q/57695
@@ -50,6 +55,17 @@ final class ECDHTests: XCTestCase {
5055
XCTAssertEqual(ans1X963.hex, "3a17fe5fa33c4f2c7e61799a65061214913f39bfcbee178ab351493d5ee17b2f")
5156

5257
}
58+
59+
/// Assert we do not introduce any regression bugs for the custom ECDh `ecdhPoint`
60+
func testECDHCustom() throws {
61+
let alice = try K1.PrivateKey.import(rawRepresentation: Data(repeating: 0xAA, count: 32))
62+
let bob = try K1.PrivateKey.import(rawRepresentation: Data(repeating: 0xBB, count: 32))
63+
let ab = try alice.ecdhPoint(with: bob.publicKey)
64+
let ba = try bob.ecdhPoint(with: alice.publicKey)
65+
XCTAssertEqual(ab, ba, "Alice and Bob should be able to agree on the same secret")
66+
XCTAssertEqual(ab.hex, "041d3e7279da3f845c4246087cdd3dd42bea3dea7245ceaf75609d8eb0a4e89c4e8e7a7c012045a2eae87463012468d7aae911b8a1140e240c828c96d9b19bd8e7")
67+
68+
}
5369
}
5470

5571
extension K1.PrivateKey {

0 commit comments

Comments
 (0)