Skip to content

Commit b803887

Browse files
authored
Public Key and ECDH revisions (#218)
1 parent b5fa560 commit b803887

File tree

5 files changed

+84
-38
lines changed

5 files changed

+84
-38
lines changed

Sources/implementation/Asymmetric.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ public extension secp256k1 {
109109
/// Generates a secp256k1 public key from a raw representation.
110110
/// - Parameter data: A raw representation of the key.
111111
/// - Throws: An error is thrown when the raw representation does not create a public key.
112-
public init<D: ContiguousBytes>(rawRepresentation data: D, xonly: D, keyParity: Int32, format: secp256k1.Format) {
113-
self.baseKey = PublicKeyImplementation(rawRepresentation: data, xonly: xonly, keyParity: keyParity, format: format)
112+
public init<D: ContiguousBytes>(rawRepresentation data: D, format: secp256k1.Format) throws {
113+
self.baseKey = try PublicKeyImplementation(rawRepresentation: data, format: format)
114114
}
115115
}
116116

Sources/implementation/ECDH.swift

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,55 @@ public extension secp256k1 {
1717
enum KeyAgreement {
1818
public struct PublicKey /*: NISTECPublicKey */ {
1919
let baseKey: PublicKeyImplementation
20-
20+
2121
/// Creates a secp256k1 public key for key agreement from a collection of bytes.
2222
/// - Parameters:
2323
/// - data: A raw representation of the public key as a collection of contiguous bytes.
2424
/// - xonly: A raw representation of the xonly key as a collection of contiguous bytes.
2525
/// - format: the format of the public key object
26-
public init<D: ContiguousBytes>(rawRepresentation data: D, xonly: D, keyParity: Int32, format: secp256k1.Format) {
27-
self.baseKey = PublicKeyImplementation(rawRepresentation: data, xonly: xonly, keyParity: keyParity, format: format)
26+
public init<D: ContiguousBytes>(rawRepresentation data: D, format: secp256k1.Format = .compressed) throws {
27+
self.baseKey = try PublicKeyImplementation(rawRepresentation: data, format: format)
2828
}
2929

3030
/// Initializes a secp256k1 public key for key agreement.
3131
/// - Parameter baseKey: generated secp256k1 public key.
3232
init(baseKey: PublicKeyImplementation) {
3333
self.baseKey = baseKey
3434
}
35+
36+
/// The associated x-only public key for verifying Schnorr signatures.
37+
///
38+
/// - Returns: The associated x-only public key
39+
public var xonly: secp256k1.KeyAgreement.XonlyKey {
40+
XonlyKey(baseKey: baseKey.xonly)
41+
}
3542

3643
/// A data representation of the public key
3744
public var rawRepresentation: Data { baseKey.rawRepresentation }
3845

3946
/// Implementation public key object
4047
var bytes: [UInt8] { baseKey.bytes }
4148
}
49+
50+
public struct XonlyKey {
51+
/// Generated secp256k1 x-only public key.
52+
private let baseKey: XonlyKeyImplementation
53+
54+
/// A data representation of the backing x-only public key
55+
public var rawRepresentation: Data { baseKey.rawRepresentation }
56+
57+
/// A boolean that will be set to true if the point encoded by xonly is the
58+
/// negation of the pubkey and set to false otherwise.
59+
public var parity: Bool { baseKey.keyParity.boolValue }
60+
61+
/// Initializes a secp256k1 x-only key for key agreement.
62+
/// - Parameter baseKey: generated secp256k1 public key.
63+
init(baseKey: XonlyKeyImplementation) {
64+
self.baseKey = baseKey
65+
}
66+
}
4267

43-
public struct PrivateKey /*: NISTECPrivateKey */ {
68+
public struct PrivateKey {
4469
let baseKey: PrivateKeyImplementation
4570

4671
/// Creates a random secp256k1 private key for key agreement.
@@ -78,17 +103,40 @@ public extension secp256k1 {
78103
// MARK: - secp256k1 + DH
79104

80105
extension secp256k1.KeyAgreement.PrivateKey: DiffieHellmanKeyAgreement {
106+
/// A pointer to a function that hashes an EC point to obtain an ECDH secret
107+
public typealias HashFunctionType = @convention(c) (
108+
UnsafeMutablePointer<UInt8>?,
109+
UnsafePointer<UInt8>?,
110+
UnsafePointer<UInt8>?,
111+
UnsafeMutableRawPointer?
112+
) -> Int32
113+
81114
/// Performs a key agreement with provided public key share.
82115
///
83116
/// - Parameter publicKeyShare: The public key to perform the ECDH with.
84117
/// - Returns: Returns a shared secret
85118
/// - Throws: An error occurred while computing the shared secret
86-
public func sharedSecretFromKeyAgreement(with publicKeyShare: secp256k1.KeyAgreement.PublicKey) throws -> SharedSecret {
119+
func sharedSecretFromKeyAgreement(with publicKeyShare: secp256k1.KeyAgreement.PublicKey) throws -> SharedSecret {
120+
try sharedSecretFromKeyAgreement(with: publicKeyShare, handler: nil)
121+
}
122+
123+
/// Performs a key agreement with provided public key share.
124+
/// - Parameters:
125+
/// - publicKeyShare: The public key to perform the ECDH with.
126+
/// - handler: Closure for customizing a hash function; Defaults to nil.
127+
/// - Returns: Returns a shared secret
128+
/// - Throws: An error occurred while computing the shared secret
129+
public func sharedSecretFromKeyAgreement(
130+
with publicKeyShare: secp256k1.KeyAgreement.PublicKey,
131+
handler: HashFunctionType? = nil,
132+
data: UnsafeMutableRawPointer? = nil
133+
) throws -> SharedSecret {
134+
let context = secp256k1.Context.raw
87135
var publicKey = secp256k1_pubkey()
88136
var sharedSecret = [UInt8](repeating: 0, count: 32)
89137

90138
guard secp256k1_ec_pubkey_parse(secp256k1.Context.raw, &publicKey, publicKeyShare.bytes, publicKeyShare.bytes.count).boolValue,
91-
secp256k1_ecdh(secp256k1.Context.raw, &sharedSecret, &publicKey, baseKey.key.bytes, nil, nil).boolValue else {
139+
secp256k1_ecdh(context, &sharedSecret, &publicKey, baseKey.key.bytes, handler, data).boolValue else {
92140
throw secp256k1Error.underlyingCryptoError
93141
}
94142

Sources/implementation/Tweak.swift

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -69,22 +69,18 @@ public extension secp256k1.Signing.PublicKey {
6969
/// - format: the format of the tweaked `PublicKey` object
7070
/// - Returns: tweaked `PublicKey` object
7171
func add(_ tweak: [UInt8], format: secp256k1.Format = .compressed) throws -> Self {
72+
let context = secp256k1.Context.raw
7273
var pubKey = secp256k1_pubkey()
7374
var pubKeyLen = format.length
7475
var pubKeyBytes = [UInt8](repeating: 0, count: pubKeyLen)
75-
var xonlyKey = secp256k1_xonly_pubkey()
76-
var xonlyBytes = [UInt8](repeating: 0, count: secp256k1.Schnorr.xonlyByteCount)
77-
var keyParity = Int32()
7876

79-
guard secp256k1_ec_pubkey_parse(secp256k1.Context.raw, &pubKey, bytes, pubKeyLen).boolValue,
80-
secp256k1_ec_pubkey_tweak_add(secp256k1.Context.raw, &pubKey, tweak).boolValue,
81-
secp256k1_ec_pubkey_serialize(secp256k1.Context.raw, &pubKeyBytes, &pubKeyLen, &pubKey, format.rawValue).boolValue,
82-
secp256k1_xonly_pubkey_from_pubkey(secp256k1.Context.raw, &xonlyKey, &keyParity, &pubKey).boolValue,
83-
secp256k1_xonly_pubkey_serialize(secp256k1.Context.raw, &xonlyBytes, &xonlyKey).boolValue else {
77+
guard secp256k1_ec_pubkey_parse(context, &pubKey, bytes, pubKeyLen).boolValue,
78+
secp256k1_ec_pubkey_tweak_add(context, &pubKey, tweak).boolValue,
79+
secp256k1_ec_pubkey_serialize(context, &pubKeyBytes, &pubKeyLen, &pubKey, format.rawValue).boolValue else {
8480
throw secp256k1Error.underlyingCryptoError
8581
}
8682

87-
return Self(rawRepresentation: pubKeyBytes, xonly: xonlyBytes, keyParity: keyParity, format: format)
83+
return try Self(rawRepresentation: pubKeyBytes, format: format)
8884
}
8985

9086
/// Create a new `PublicKey` by multiplying tweak to the public key.
@@ -93,22 +89,18 @@ public extension secp256k1.Signing.PublicKey {
9389
/// - format: the format of the tweaked `PublicKey` object
9490
/// - Returns: tweaked `PublicKey` object
9591
func multiply(_ tweak: [UInt8], format: secp256k1.Format = .compressed) throws -> Self {
92+
let context = secp256k1.Context.raw
9693
var pubKey = secp256k1_pubkey()
9794
var pubKeyLen = format.length
9895
var pubKeyBytes = [UInt8](repeating: 0, count: pubKeyLen)
99-
var xonlyKey = secp256k1_xonly_pubkey()
100-
var xonlyBytes = [UInt8](repeating: 0, count: secp256k1.Schnorr.xonlyByteCount)
101-
var keyParity = Int32()
10296

103-
guard secp256k1_ec_pubkey_parse(secp256k1.Context.raw, &pubKey, bytes, pubKeyLen).boolValue,
104-
secp256k1_ec_pubkey_tweak_mul(secp256k1.Context.raw, &pubKey, tweak).boolValue,
105-
secp256k1_ec_pubkey_serialize(secp256k1.Context.raw, &pubKeyBytes, &pubKeyLen, &pubKey, format.rawValue).boolValue,
106-
secp256k1_xonly_pubkey_from_pubkey(secp256k1.Context.raw, &xonlyKey, &keyParity, &pubKey).boolValue,
107-
secp256k1_xonly_pubkey_serialize(secp256k1.Context.raw, &xonlyBytes, &xonlyKey).boolValue else {
97+
guard secp256k1_ec_pubkey_parse(context, &pubKey, bytes, pubKeyLen).boolValue,
98+
secp256k1_ec_pubkey_tweak_mul(context, &pubKey, tweak).boolValue,
99+
secp256k1_ec_pubkey_serialize(context, &pubKeyBytes, &pubKeyLen, &pubKey, format.rawValue).boolValue else {
108100
throw secp256k1Error.underlyingCryptoError
109101
}
110102

111-
return Self(rawRepresentation: pubKeyBytes, xonly: xonlyBytes, keyParity: keyParity, format: format)
103+
return try Self(rawRepresentation: pubKeyBytes, format: format)
112104
}
113105
}
114106

@@ -119,17 +111,18 @@ public extension secp256k1.Signing.XonlyKey {
119111
/// - format: the format of the tweaked `XonlyKey` object
120112
/// - Returns: tweaked `PublicKey` object
121113
func add(_ tweak: [UInt8]) throws -> Self {
114+
let context = secp256k1.Context.raw
122115
var pubKey = secp256k1_pubkey()
123116
var inXonlyPubKey = secp256k1_xonly_pubkey()
124117
var outXonlyPubKey = secp256k1_xonly_pubkey()
125118
var xonlyBytes = [UInt8](repeating: 0, count: secp256k1.Schnorr.xonlyByteCount)
126119
var keyParity = Int32()
127120

128-
guard secp256k1_xonly_pubkey_parse(secp256k1.Context.raw, &inXonlyPubKey, bytes).boolValue,
129-
secp256k1_xonly_pubkey_tweak_add(secp256k1.Context.raw, &pubKey, &inXonlyPubKey, tweak).boolValue,
130-
secp256k1_xonly_pubkey_from_pubkey(secp256k1.Context.raw, &outXonlyPubKey, &keyParity, &pubKey).boolValue,
131-
secp256k1_xonly_pubkey_serialize(secp256k1.Context.raw, &xonlyBytes, &outXonlyPubKey).boolValue,
132-
secp256k1_xonly_pubkey_tweak_add_check(secp256k1.Context.raw, &xonlyBytes, keyParity, &inXonlyPubKey, tweak).boolValue else {
121+
guard secp256k1_xonly_pubkey_parse(context, &inXonlyPubKey, bytes).boolValue,
122+
secp256k1_xonly_pubkey_tweak_add(context, &pubKey, &inXonlyPubKey, tweak).boolValue,
123+
secp256k1_xonly_pubkey_from_pubkey(context, &outXonlyPubKey, &keyParity, &pubKey).boolValue,
124+
secp256k1_xonly_pubkey_serialize(context, &xonlyBytes, &outXonlyPubKey).boolValue,
125+
secp256k1_xonly_pubkey_tweak_add_check(context, &xonlyBytes, keyParity, &inXonlyPubKey, tweak).boolValue else {
133126
throw secp256k1Error.underlyingCryptoError
134127
}
135128

Sources/implementation/secp256k1.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ public extension secp256k1 {
2020
public let rawValue: UInt32
2121
public init(rawValue: UInt32) { self.rawValue = rawValue }
2222
init(rawValue: Int32) { self.rawValue = UInt32(rawValue) }
23-
public static let declassify = Context(rawValue: SECP256K1_CONTEXT_DECLASSIFY)
2423
public static let none = Context(rawValue: SECP256K1_CONTEXT_NONE)
2524
public static let sign = Context(rawValue: SECP256K1_CONTEXT_SIGN)
2625
public static let verify = Context(rawValue: SECP256K1_CONTEXT_VERIFY)
@@ -164,13 +163,17 @@ extension secp256k1 {
164163

165164
/// A key format representation of the backing public key
166165
@usableFromInline let format: secp256k1.Format
167-
168-
/// Backing initialization that generates a secp256k1 public key from a raw representation.
169-
/// - Parameter data: A raw representation of the key.
170-
@usableFromInline init<D: ContiguousBytes>(rawRepresentation data: D, xonly: D, keyParity: Int32, format: secp256k1.Format) {
166+
167+
/// Backing initialization that generates a secp256k1 public key from only a raw representation and key format.
168+
/// - Parameters:
169+
/// - data: A raw representation of the public key.
170+
/// - format: an enum that represents the format of the public key
171+
@usableFromInline init<D: ContiguousBytes>(rawRepresentation data: D, format: secp256k1.Format) throws {
172+
var keyParity = Int32()
173+
171174
self.bytes = data.bytes
172175
self.format = format
173-
self._xonlyBytes = xonly.bytes
176+
self._xonlyBytes = try XonlyKeyImplementation.generate(bytes: data.bytes, keyParity: &keyParity, format: format)
174177
self._keyParity = keyParity
175178
}
176179

Tests/secp256k1Tests/secp256k1Tests.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,9 +440,11 @@ final class secp256k1Tests: XCTestCase {
440440

441441
let privateKey1 = try! secp256k1.KeyAgreement.PrivateKey(rawRepresentation: privateSign1.rawRepresentation)
442442
let privateKey2 = try! secp256k1.KeyAgreement.PrivateKey(rawRepresentation: privateSign2.rawRepresentation)
443+
444+
let publicKey1 = try! secp256k1.KeyAgreement.PublicKey(rawRepresentation: privateKey1.publicKey.rawRepresentation)
443445

444446
let sharedSecret1 = try! privateKey1.sharedSecretFromKeyAgreement(with: privateKey2.publicKey)
445-
let sharedSecret2 = try! privateKey2.sharedSecretFromKeyAgreement(with: privateKey1.publicKey)
447+
let sharedSecret2 = try! privateKey2.sharedSecretFromKeyAgreement(with: publicKey1)
446448

447449
XCTAssertEqual(sharedSecret1.bytes, sharedSecret2.bytes)
448450

0 commit comments

Comments
 (0)