Skip to content

Commit

Permalink
Initial PublicKey Combine API (#439)
Browse files Browse the repository at this point in the history
  • Loading branch information
csjones authored Nov 14, 2023
1 parent acea892 commit 0f5f60e
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 15 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@ let publicKey = try! secp256k1.Recovery.PublicKey(messageData, signature: recove
let signature = try! recoverySignature.normalize
```

# Combine Public Keys

```swift
let privateKey = try! secp256k1.Signing.PrivateKey()
let publicKey = try! secp256k1.Signing.PrivateKey().public

// The Combine API arguments are an array of PublicKey objects and an optional format
publicKey.combine([privateKey.publicKey], format: .uncompressed)
```


# Danger
These APIs should not be considered stable and may change at any time.
Expand Down
1 change: 1 addition & 0 deletions Sources/secp256k1/Combine.swift
60 changes: 60 additions & 0 deletions Sources/zkp/Combine.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// Combine.swift
// GigaBitcoin/secp256k1.swift
//
// Copyright (c) 2023 GigaBitcoin LLC
// Distributed under the MIT software license
//
// See the accompanying file LICENSE for information
//

import Foundation

public extension secp256k1.Signing.PublicKey {
/// Create a new `PublicKey` by combining the current public key with an array of public keys.
/// - Parameters:
/// - pubkeys: the array of public key objects to be combined with
/// - format: the format of the combined `PublicKey` object
/// - Returns: combined `PublicKey` object
func combine(_ pubkeys: [Self], format: secp256k1.Format = .compressed) throws -> Self {
let context = secp256k1.Context.rawRepresentation
let allPubKeys = [self] + pubkeys
var pubKeyLen = format.length
var combinedKey = secp256k1_pubkey()
var combinedBytes = [UInt8](repeating: 0, count: pubKeyLen)

guard withUnsafePointersToPubKeys(allPubKeys.map { $0.rawRepresentation }, { ptrsToCombine in
secp256k1_ec_pubkey_combine(context, &combinedKey, ptrsToCombine, ptrsToCombine.count).boolValue
}), secp256k1_ec_pubkey_serialize(context, &combinedBytes, &pubKeyLen, &combinedKey, format.rawValue).boolValue else {
throw secp256k1Error.underlyingCryptoError
}

return try Self(dataRepresentation: combinedBytes, format: format)
}
}

extension secp256k1.Signing.PublicKey {
/// Executes a closure with an array of `UnsafePointer<secp256k1_pubkey>?` for pointer operations on an array of `secp256k1_pubkey`.
/// - Parameters:
/// - pubKeys: An array of `secp256k1_pubkey` to be converted to `UnsafePointer<secp256k1_pubkey>?`.
/// - body: A closure that takes an array of `UnsafePointer<secp256k1_pubkey>?` and returns a result of type `Result`.
/// - Returns: The result of the closure of type `Result`.
func withUnsafePointersToPubKeys<Result>(
_ pubKeys: [secp256k1_pubkey],
_ body: ([UnsafePointer<secp256k1_pubkey>?]) -> Result
) -> Result {
let pointers = pubKeys.map { pubKey -> UnsafePointer<secp256k1_pubkey>? in
let mutablePubKey = UnsafeMutablePointer<secp256k1_pubkey>.allocate(capacity: 1)
mutablePubKey.initialize(to: pubKey)
return UnsafePointer(mutablePubKey)
}

defer {
for ptr in pointers {
ptr?.deallocate()
}
}

return body(pointers)
}
}
5 changes: 2 additions & 3 deletions Sources/zkp/ECDH.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,11 @@ extension secp256k1.KeyAgreement.PrivateKey: DiffieHellmanKeyAgreement {
format: secp256k1.Format = .compressed
) throws -> SharedSecret {
let context = secp256k1.Context.rawRepresentation
var publicKey = secp256k1_pubkey()
var publicKey = publicKeyShare.rawRepresentation
var sharedSecret = [UInt8](repeating: 0, count: format.length)
var data = [UInt8](repeating: format == .compressed ? 1 : 0, count: 1)

guard secp256k1_ec_pubkey_parse(context, &publicKey, publicKeyShare.bytes, publicKeyShare.bytes.count).boolValue,
secp256k1_ecdh(context, &sharedSecret, &publicKey, baseKey.key.bytes, hashClosure(), &data).boolValue else {
guard secp256k1_ecdh(context, &sharedSecret, &publicKey, baseKey.key.bytes, hashClosure(), &data).boolValue else {
throw secp256k1Error.underlyingCryptoError
}

Expand Down
5 changes: 2 additions & 3 deletions Sources/zkp/ECDSA.swift
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,11 @@ extension secp256k1.Signing.PublicKey: DigestValidator {
public func isValidSignature<D: Digest>(_ signature: secp256k1.Signing.ECDSASignature, for digest: D) -> Bool {
let context = secp256k1.Context.rawRepresentation
var ecdsaSignature = secp256k1_ecdsa_signature()
var publicKey = secp256k1_pubkey()
var publicKey = rawRepresentation

signature.dataRepresentation.copyToUnsafeMutableBytes(of: &ecdsaSignature.data)

return secp256k1_ec_pubkey_parse(context, &publicKey, bytes, bytes.count).boolValue &&
secp256k1_ecdsa_verify(context, &ecdsaSignature, Array(digest), &publicKey).boolValue
return secp256k1_ecdsa_verify(context, &ecdsaSignature, Array(digest), &publicKey).boolValue
}
}

Expand Down
10 changes: 4 additions & 6 deletions Sources/zkp/Tweak.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,11 @@ public extension secp256k1.Signing.PublicKey {
/// - Returns: tweaked `PublicKey` object
func add(_ tweak: [UInt8], format: secp256k1.Format = .compressed) throws -> Self {
let context = secp256k1.Context.rawRepresentation
var pubKey = secp256k1_pubkey()
var pubKey = rawRepresentation
var pubKeyLen = format.length
var pubKeyBytes = [UInt8](repeating: 0, count: pubKeyLen)

guard secp256k1_ec_pubkey_parse(context, &pubKey, bytes, pubKeyLen).boolValue,
secp256k1_ec_pubkey_tweak_add(context, &pubKey, tweak).boolValue,
guard secp256k1_ec_pubkey_tweak_add(context, &pubKey, tweak).boolValue,
secp256k1_ec_pubkey_serialize(context, &pubKeyBytes, &pubKeyLen, &pubKey, format.rawValue).boolValue else {
throw secp256k1Error.underlyingCryptoError
}
Expand All @@ -92,12 +91,11 @@ public extension secp256k1.Signing.PublicKey {
/// - Returns: tweaked `PublicKey` object
func multiply(_ tweak: [UInt8], format: secp256k1.Format = .compressed) throws -> Self {
let context = secp256k1.Context.rawRepresentation
var pubKey = secp256k1_pubkey()
var pubKey = rawRepresentation
var pubKeyLen = format.length
var pubKeyBytes = [UInt8](repeating: 0, count: pubKeyLen)

guard secp256k1_ec_pubkey_parse(context, &pubKey, bytes, pubKeyLen).boolValue,
secp256k1_ec_pubkey_tweak_mul(context, &pubKey, tweak).boolValue,
guard secp256k1_ec_pubkey_tweak_mul(context, &pubKey, tweak).boolValue,
secp256k1_ec_pubkey_serialize(context, &pubKeyBytes, &pubKeyLen, &pubKey, format.rawValue).boolValue else {
throw secp256k1Error.underlyingCryptoError
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/zkp/secp256k1.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ extension secp256k1 {
/// A raw representation of the backing public key
@usableFromInline var rawRepresentation: secp256k1_pubkey {
var pubKey = secp256k1_pubkey()
dataRepresentation.copyToUnsafeMutableBytes(of: &pubKey.data)
_ = secp256k1_ec_pubkey_parse(secp256k1.Context.rawRepresentation, &pubKey, bytes, bytes.count)
return pubKey
}

Expand Down
54 changes: 52 additions & 2 deletions Tests/zkpTests/secp256k1Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ final class secp256k1Tests: XCTestCase {

let set0 = Set(array)

array = [UInt8](repeating: 1, count: Int.random(in: 10...100_000))
array = [UInt8](repeating: 1, count: Int.random(in: 10...100000))

XCTAssertGreaterThan(array.count, 9)

Expand Down Expand Up @@ -601,6 +601,54 @@ final class secp256k1Tests: XCTestCase {
XCTAssertEqual(outputKey.bytes, outputKeyBytes)
}

func testPubkeyCombine() {
let publicKeyBytes1 = try! "021b4f0e9851971998e732078544c96b36c3d01cedf7caa332359d6f1d83567014".bytes
let publicKeyBytes2 = try! "0260303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752".bytes

let publicKey1 = try! secp256k1.Signing.PublicKey(dataRepresentation: publicKeyBytes1, format: .compressed)
let publicKey2 = try! secp256k1.Signing.PublicKey(dataRepresentation: publicKeyBytes2, format: .compressed)

let combinedPublicKey = try! publicKey1.combine([publicKey2])

// Define the expected combined key
let expectedCombinedKey = try! "03d6a3a9d62c7650fcac18f9ee68c7a004ebad71b7581b683062213ad9f37ddb28".bytes

XCTAssertEqual(combinedPublicKey.dataRepresentation.bytes, expectedCombinedKey)
}

func testPubkeyCombineBindings() {
// Initialize context
let context = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_NONE))!

// Destroy context after execution
defer { secp256k1_context_destroy(context) }

// Setup private and public key variables
var pubKeyLen = 33
var cPubKey1 = secp256k1_pubkey()
var cPubKey2 = secp256k1_pubkey()

let publicKeyBytes1 = try! "021b4f0e9851971998e732078544c96b36c3d01cedf7caa332359d6f1d83567014".bytes
let publicKeyBytes2 = try! "0260303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752".bytes

// Verify the context and keys are setup correctly
XCTAssertEqual(secp256k1_ec_pubkey_parse(context, &cPubKey1, publicKeyBytes1, pubKeyLen), 1)
XCTAssertEqual(secp256k1_ec_pubkey_parse(context, &cPubKey2, publicKeyBytes2, pubKeyLen), 1)

let pubKeys: [UnsafePointer<secp256k1_pubkey>?] = [UnsafePointer(&cPubKey1), UnsafePointer(&cPubKey2)]
var combinedKey = secp256k1_pubkey()
var combinedKeyBytes = [UInt8](repeating: 0, count: pubKeyLen)

// Combine the two public keys
XCTAssertEqual(secp256k1_ec_pubkey_combine(context, &combinedKey, pubKeys, 2), 1)
XCTAssertEqual(secp256k1_ec_pubkey_serialize(context, &combinedKeyBytes, &pubKeyLen, &combinedKey, secp256k1.Format.compressed.rawValue), 1)

// Define the expected combined key
let expectedCombinedKey = try! "03d6a3a9d62c7650fcac18f9ee68c7a004ebad71b7581b683062213ad9f37ddb28".bytes

XCTAssertEqual(combinedKeyBytes, expectedCombinedKey)
}

static var allTests = [
("testUncompressedKeypairCreation", testUncompressedKeypairCreation),
("testCompressedKeypairCreation", testCompressedKeypairCreation),
Expand Down Expand Up @@ -637,6 +685,8 @@ final class secp256k1Tests: XCTestCase {
("testTapscript", testTapscript),
("testCompactSizePrefix", testCompactSizePrefix),
("testSchnorrNegating", testSchnorrNegating),
("testTaprootDerivation", testTaprootDerivation)
("testTaprootDerivation", testTaprootDerivation),
("testPubkeyCombine", testPubkeyCombine),
("testPubkeyCombineBindings", testPubkeyCombineBindings)
]
}

0 comments on commit 0f5f60e

Please sign in to comment.