Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ let sharedSecretSign1 = try! P256K.Signing.PrivateKey(dataRepresentation: symmet
let sharedSecretSign2 = try! P256K.Signing.PrivateKey(dataRepresentation: symmetricKey2.bytes)

// Spendable Silent Payment private key
let privateTweak1 = try! sharedSecretSign1.add(xonly: privateSign1.publicKey.xonly.bytes)
let privateTweak1 = try! sharedSecretSign1.add(privateSign1.publicKey.xonly.bytes)
let publicTweak2 = try! sharedSecretSign2.publicKey.add(privateSign1.publicKey.xonly.bytes)

let schnorrPrivate = try! P256K.Schnorr.PrivateKey(dataRepresentation: sharedSecretSign2.dataRepresentation)
Expand Down Expand Up @@ -243,4 +243,4 @@ let isValid = aggregateKey.isValidSignature(
)

print("Is valid MuSig signature: \(isValid)")
```
```
15 changes: 1 addition & 14 deletions Sources/ZKP/Asymmetric.swift
Original file line number Diff line number Diff line change
Expand Up @@ -166,20 +166,7 @@ public extension P256K {

/// Returns a public key in uncompressed 65 byte form
public var uncompressedRepresentation: Data {
let context = P256K.Context.rawRepresentation
var pubKey = baseKey.rawRepresentation
var pubKeyLen = ByteLength.uncompressedPublicKey
var pubKeyBytes = [UInt8](repeating: 0, count: pubKeyLen)

_ = secp256k1_ec_pubkey_serialize(
context,
&pubKeyBytes,
&pubKeyLen,
&pubKey,
UInt32(SECP256K1_EC_UNCOMPRESSED)
)

return Data(pubKeyBytes)
baseKey.uncompressedRepresentation
}

/// Generates a secp256k1 public key.
Expand Down
5 changes: 5 additions & 0 deletions Sources/ZKP/ECDH.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public extension P256K {
XonlyKey(baseKey: baseKey.xonly)
}

/// Returns a public key in uncompressed 65 byte form
public var uncompressedRepresentation: Data {
baseKey.uncompressedRepresentation
}

/// A data representation of the public key.
public var dataRepresentation: Data { baseKey.dataRepresentation }

Expand Down
17 changes: 17 additions & 0 deletions Sources/ZKP/P256K.swift
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,23 @@ extension P256K {
Data(bytes)
}

var uncompressedRepresentation: Data {
let context = P256K.Context.rawRepresentation
var pubKey = rawRepresentation
var pubKeyLen = P256K.ByteLength.uncompressedPublicKey
var pubKeyBytes = [UInt8](repeating: 0, count: pubKeyLen)

_ = secp256k1_ec_pubkey_serialize(
context,
&pubKeyBytes,
&pubKeyLen,
&pubKey,
UInt32(SECP256K1_EC_UNCOMPRESSED)
)

return Data(pubKeyBytes)
}

/// A raw representation of the backing public key
var rawRepresentation: secp256k1_pubkey {
var pubKey = secp256k1_pubkey()
Expand Down
55 changes: 7 additions & 48 deletions Sources/ZKP/Schnorr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ public extension P256K.Schnorr {
/// Generated secp256k1 Signing Key.
private let baseKey: PrivateKeyImplementation

/// The secp256k1 private key object.
var key: SecureBytes {
baseKey.key
}

/// The associated public key for verifying signatures created with this private key.
///
/// - Returns: The associated public key.
Expand Down Expand Up @@ -251,22 +256,7 @@ public extension P256K.Schnorr {

// MARK: - secp256k1 + Schnorr

extension P256K.Schnorr.PrivateKey: DigestSigner, Signer {
/// Generates an Schnorr signature from a hash of a variable length data object
///
/// This function uses SHA256 to create a hash of the variable length the data argument to ensure only 32-byte messages are signed.
/// Strictly does _not_ follow BIP340. Read ``secp256k1_schnorrsig_sign`` documentation for more info.
///
/// [bitcoin-core/secp256k1](https://github.com/bitcoin-core/secp256k1/blob/master/include/secp256k1_schnorrsig.h#L95L118)
///
/// - Parameters:
/// - data: The data object to hash and sign.
/// - Returns: The Schnorr Signature.
/// - Throws: If there is a failure producing the signature.
public func signature<D: DataProtocol>(for data: D) throws -> P256K.Schnorr.SchnorrSignature {
try signature(for: data, auxiliaryRand: SecureBytes(count: P256K.ByteLength.dimension).bytes)
}

extension P256K.Schnorr.PrivateKey: DigestSigner {
/// Generates an Schnorr signature from the hash digest object
///
/// This function is used when a hash digest has been created before invoking.
Expand All @@ -282,22 +272,6 @@ extension P256K.Schnorr.PrivateKey: DigestSigner, Signer {
try signature(for: digest, auxiliaryRand: SecureBytes(count: P256K.ByteLength.dimension).bytes)
}

/// Generates an Schnorr signature from a hash of a variable length data object
///
/// This function uses SHA256 to create a hash of the variable length the data argument to ensure only 32-byte messages are signed.
/// Strictly does _not_ follow BIP340. Read ``secp256k1_schnorrsig_sign`` documentation for more info.
///
/// [bitcoin-core/secp256k1](https://github.com/bitcoin-core/secp256k1/blob/master/include/secp256k1_schnorrsig.h#L95L118)
///
/// - Parameters:
/// - data: The data object to hash and sign.
/// - auxiliaryRand: Auxiliary randomness.
/// - Returns: The Schnorr Signature.
/// - Throws: If there is a failure producing the signature.
public func signature<D: DataProtocol>(for data: D, auxiliaryRand: [UInt8]) throws -> P256K.Schnorr.SchnorrSignature {
try signature(for: SHA256.hash(data: data), auxiliaryRand: auxiliaryRand)
}

/// Generates an Schnorr signature from the hash digest object
///
/// This function is used when a hash digest has been created before invoking.
Expand Down Expand Up @@ -362,22 +336,7 @@ extension P256K.Schnorr.PrivateKey: DigestSigner, Signer {

// MARK: - Schnorr + Validating Key

extension P256K.Schnorr.XonlyKey: DigestValidator, DataValidator {
/// Verifies a Schnorr signature with a variable length data object
///
/// This function uses SHA256 to create a hash of the variable length the data argument to ensure only 32-byte messages are verified.
/// Strictly does _not_ follow BIP340. Read ``secp256k1_schnorrsig_sign`` documentation for more info.
///
/// [bitcoin-core/secp256k1](https://github.com/bitcoin-core/secp256k1/blob/master/include/secp256k1_schnorrsig.h#L95L118)
///
/// - Parameters:
/// - signature: The signature to verify
/// - data: The data that was signed.
/// - Returns: True if the signature is valid, false otherwise.
public func isValidSignature<D: DataProtocol>(_ signature: P256K.Schnorr.SchnorrSignature, for data: D) -> Bool {
isValidSignature(signature, for: SHA256.hash(data: data))
}

extension P256K.Schnorr.XonlyKey: DigestValidator {
/// Verifies a Schnorr signature with a digest
///
/// This function is used when a hash digest has been created before invoking.
Expand Down
36 changes: 19 additions & 17 deletions Sources/ZKP/Tweak.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,37 +32,39 @@ public extension P256K.Signing.PrivateKey {
return try Self(dataRepresentation: privateBytes)
}

/// Create a new `PrivateKey` by adding tweak to the secret key. When tweaking x-only keys,
/// the implicit negations are handled when odd Y coordinates are reached.
/// [REF](https://github.com/bitcoin-core/secp256k1/issues/1021#issuecomment-983021759)
/// Create a new `PrivateKey` by multiplying tweak to the secret key.
/// - Parameter tweak: the 32-byte tweak object
/// - Returns: tweaked `PrivateKey` object
func add(xonly tweak: [UInt8]) throws -> Self {
func multiply(_ tweak: [UInt8]) throws -> Self {
let context = P256K.Context.rawRepresentation
var keypair = secp256k1_keypair()
var privateBytes = [UInt8](repeating: 0, count: P256K.ByteLength.privateKey)
var xonly = secp256k1_xonly_pubkey()
var keyParity = Int32()
var privateBytes = key.bytes

guard secp256k1_keypair_create(context, &keypair, key.bytes).boolValue,
secp256k1_keypair_xonly_tweak_add(context, &keypair, tweak).boolValue,
secp256k1_keypair_sec(context, &privateBytes, &keypair).boolValue,
secp256k1_keypair_xonly_pub(context, &xonly, &keyParity, &keypair).boolValue else {
guard secp256k1_ec_seckey_tweak_mul(context, &privateBytes, tweak).boolValue,
secp256k1_ec_seckey_verify(context, privateBytes).boolValue else {
throw secp256k1Error.underlyingCryptoError
}

return try Self(dataRepresentation: privateBytes)
}
}

/// Create a new `PrivateKey` by multiplying tweak to the secret key.
public extension P256K.Schnorr.PrivateKey {
/// Create a new `PrivateKey` by adding tweak to the secret key. When tweaking keys, implicit
/// negations are handled when odd Y coordinates are reached.
/// [REF](https://github.com/bitcoin-core/secp256k1/issues/1021#issuecomment-983021759)
/// - Parameter tweak: the 32-byte tweak object
/// - Returns: tweaked `PrivateKey` object
func multiply(_ tweak: [UInt8]) throws -> Self {
func add(_ tweak: [UInt8]) throws -> Self {
let context = P256K.Context.rawRepresentation
var privateBytes = key.bytes
var keypair = secp256k1_keypair()
var privateBytes = [UInt8](repeating: 0, count: P256K.ByteLength.privateKey)
var xonly = secp256k1_xonly_pubkey()
var keyParity = Int32()

guard secp256k1_ec_seckey_tweak_mul(context, &privateBytes, tweak).boolValue,
secp256k1_ec_seckey_verify(context, privateBytes).boolValue else {
guard secp256k1_keypair_create(context, &keypair, key.bytes).boolValue,
secp256k1_keypair_xonly_tweak_add(context, &keypair, tweak).boolValue,
secp256k1_keypair_sec(context, &privateBytes, &keypair).boolValue,
secp256k1_keypair_xonly_pub(context, &xonly, &keyParity, &keypair).boolValue else {
throw secp256k1Error.underlyingCryptoError
}

Expand Down
22 changes: 11 additions & 11 deletions Tests/ZKPTests/AsymmetricTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,21 @@ struct AsymmetricTestSuite {
}

@Test("Verify compressed public key characteristics")
func testCompressedPublicKey() {
func compressedPublicKey() {
let privateKey = try! P256K.Signing.PrivateKey()
#expect(privateKey.publicKey.format == .compressed, "PublicKey format should be compressed")
#expect(privateKey.publicKey.dataRepresentation.count == P256K.Format.compressed.length, "PublicKey length mismatch for compressed format")
}

@Test("Verify uncompressed public key characteristics")
func testUncompressedPublicKey() {
func uncompressedPublicKey() {
let privateKey = try! P256K.Signing.PrivateKey(format: .uncompressed)
#expect(privateKey.publicKey.format == .uncompressed, "PublicKey format should be uncompressed")
#expect(privateKey.publicKey.dataRepresentation.count == P256K.Format.uncompressed.length, "PublicKey length mismatch for uncompressed format")
}

@Test("Verify uncompressed public key with specific bytes")
func testUncompressedPublicKeyWithKey() {
func uncompressedPublicKeyWithKey() {
let privateBytes = try! "703d3b63e84421e59f9359f8b27c25365df9d85b6b1566e3168412fa599c12f4".bytes
let privateKey = try! P256K.Signing.PrivateKey(dataRepresentation: privateBytes, format: .uncompressed)
#expect(privateKey.publicKey.format == .uncompressed, "PublicKey format should be uncompressed")
Expand All @@ -62,15 +62,15 @@ struct AsymmetricTestSuite {
}

@Test("Verify invalid private key bytes throw appropriate error")
func testInvalidPrivateKeyBytes() {
func invalidPrivateKeyBytes() {
let expectedPrivateKey = "55f6d5afecc677d66fb3d41eee7a8ad8195659ceff588edaf416a9a17daf38fdd"
#expect(throws: (any Error).self) {
_ = try expectedPrivateKey.bytes
}
}

@Test("Verify initialization with invalid private key length throws appropriate error")
func testInvalidPrivateKeyLength() {
func invalidPrivateKeyLength() {
let expectedPrivateKey = "555f6d5afecc677d66fb3d41eee7a8ad8195659ceff588edaf416a9a17daf38fdd"
let privateKeyBytes = try! expectedPrivateKey.bytes

Expand All @@ -80,15 +80,15 @@ struct AsymmetricTestSuite {
}

@Test("Test conversion of xonly public key to full public key")
func testXonlyToPublicKey() {
func xonlyToPublicKey() {
let privateKey = try! P256K.Signing.PrivateKey()
let publicKey = P256K.Signing.PublicKey(xonlyKey: privateKey.publicKey.xonly)

#expect(privateKey.publicKey.dataRepresentation == publicKey.dataRepresentation, "Public key data representations should match")
}

@Test("Public Key Combination Test")
func testPubkeyCombine() {
func pubkeyCombine() {
let publicKeyBytes1 = try! "021b4f0e9851971998e732078544c96b36c3d01cedf7caa332359d6f1d83567014".bytes
let publicKeyBytes2 = try! "0260303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752".bytes

Expand All @@ -103,7 +103,7 @@ struct AsymmetricTestSuite {
}

@Test("Uncompressed Public Key Test")
func testKeyFormatConversion() {
func keyFormatConversion() {
let pubkey = try! P256K.Signing.PublicKey(
dataRepresentation: "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2".bytes,
format: .compressed
Expand All @@ -113,7 +113,7 @@ struct AsymmetricTestSuite {
}

@Test("Private Key PEM Test")
func testPrivateKeyPEM() {
func privateKeyPEM() {
let privateKeyString = """
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIBXwHPDpec6b07GeLbnwetT0dvWzp0nV3MR+4pPKXIc7oAcGBSuBBAAK
Expand All @@ -129,7 +129,7 @@ struct AsymmetricTestSuite {
}

@Test("Public Key PEM Test")
func testPublicKeyPEM() {
func publicKeyPEM() {
let publicKeyString = """
-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEt2uDn+2GqqYs/fmkBr5+rCQ3oiFSIJMA
Expand All @@ -146,7 +146,7 @@ struct AsymmetricTestSuite {

@Test("Test UInt256")
@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, macCatalyst 16.4, visionOS 1.0, *)
func testUInt256() {
func uInt256() {
let expectedPrivateKey: UInt256 = 0x7DA1_2CC3_9BB4_189A_C72D_34FC_2225_DF5C_F36A_AACD_CAC7_E5A4_3963_299B_C8D8_88ED
let expectedPrivateKey2: UInt256 = 0x1BB5_FC86_3773_7549_414D_7F1B_82A5_C12D_234B_56DB_AC17_5E14_0F63_046A_EBA8_DF87
let expectedPublicKey = "023521df7b94248ffdf0d37f738a4792cc3932b6b1b89ef71cddde8251383b26e7"
Expand Down
8 changes: 4 additions & 4 deletions Tests/ZKPTests/ECDHTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import Testing

struct ECDHTestSuite {
@Test("Test Key Agreement")
func testKeyAgreement() {
func keyAgreement() {
let privateString1 = "7da12cc39bb4189ac72d34fc2225df5cf36aaacdcac7e5a43963299bc8d888ed"
let privateString2 = "5f6d5afecc677d66fb3d41eee7a8ad8195659ceff588edaf416a9a17daf38fdd"

Expand All @@ -36,7 +36,7 @@ struct ECDHTestSuite {
}

@Test("Test Key Agreement Public Key Tweak Addition")
func testKeyAgreementPublicKeyTweakAdd() {
func keyAgreementPublicKeyTweakAdd() {
let privateSign1 = try! P256K.Signing.PrivateKey()
let privateSign2 = try! P256K.Signing.PrivateKey()

Expand All @@ -53,10 +53,10 @@ struct ECDHTestSuite {
let symmetricKey1 = SHA256.hash(data: sharedSecret1.bytes)
let symmetricKey2 = SHA256.hash(data: sharedSecret2.bytes)

let sharedSecretSign1 = try! P256K.Signing.PrivateKey(dataRepresentation: symmetricKey1.bytes)
let sharedSecretSign1 = try! P256K.Schnorr.PrivateKey(dataRepresentation: symmetricKey1.bytes)
let sharedSecretSign2 = try! P256K.Signing.PrivateKey(dataRepresentation: symmetricKey2.bytes)

let privateTweak1 = try! sharedSecretSign1.add(xonly: privateSign1.publicKey.xonly.bytes)
let privateTweak1 = try! sharedSecretSign1.add(privateSign1.publicKey.xonly.bytes)
let publicTweak2 = try! sharedSecretSign2.publicKey.add(privateSign1.publicKey.xonly.bytes)

let schnorrPrivate = try! P256K.Schnorr.PrivateKey(dataRepresentation: sharedSecretSign2.dataRepresentation)
Expand Down
8 changes: 4 additions & 4 deletions Tests/ZKPTests/ECDSATests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,21 +65,21 @@ struct ECDSATestSuite {
}

@Test("Verify invalid raw signature initialization throws correct error")
func testInvalidRawSignature() {
func invalidRawSignature() {
#expect(throws: secp256k1Error.incorrectParameterSize) {
_ = try P256K.Signing.ECDSASignature(dataRepresentation: Data())
}
}

@Test("Verify invalid DER signature initialization throws correct error")
func testInvalidDerSignature() {
func invalidDerSignature() {
#expect(throws: secp256k1Error.underlyingCryptoError) {
_ = try P256K.Signing.ECDSASignature(derRepresentation: Data())
}
}

@Test("Signing with PEM representation")
func testSigningPEM() {
func signingPEM() {
let privateKeyString = """
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIBXwHPDpec6b07GeLbnwetT0dvWzp0nV3MR+4pPKXIc7oAcGBSuBBAAK
Expand All @@ -99,7 +99,7 @@ struct ECDSATestSuite {
}

@Test("Verifying with PEM representation")
func testVerifyingPEM() {
func verifyingPEM() {
let publicKeyString = """
-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEt2uDn+2GqqYs/fmkBr5+rCQ3oiFSIJMA
Expand Down
2 changes: 1 addition & 1 deletion Tests/ZKPTests/MusigTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import Testing

struct MuSigTestSuite {
@Test("MuSig Signing and Verification")
func testMusig() {
func musig() {
// Test MuSig aggregate
let privateKeys = [
try! P256K.Schnorr.PrivateKey(),
Expand Down
2 changes: 1 addition & 1 deletion Tests/ZKPTests/SchnorrTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ struct SchnorrTestSuite {
}

@Test("Test Schnorr Negating")
func testSchnorrNegating() {
func schnorrNegating() {
let privateBytes = try! "56baa476b36a5b1548279f5bf57b82db39e594aee7912cde30977b8e80e6edca".bytes
let negatedBytes = try! "a9455b894c95a4eab7d860a40a847d2380c94837c7b7735d8f3ae2fe4f4f5377".bytes

Expand Down
Loading