From 37cc6e53e32e614fb9563b4b7d14be0f1b4334f5 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 24 Apr 2023 00:35:56 -0700 Subject: [PATCH] BIP340 improvements (#343) * BIP340 improvements * README cleanup --- README.md | 3 +- Sources/zkp/ECDH.swift | 4 +- Sources/zkp/ECDSA.swift | 38 +++++- Sources/zkp/MuSig2.swift | 13 ++ Sources/zkp/Recovery.swift | 12 +- Sources/zkp/SHA256.swift | 10 +- Sources/zkp/Schnorr.swift | 73 ++++++++-- Sources/zkp/Tweak.swift | 25 ++-- Sources/zkp/secp256k1.swift | 202 ++++++++++++++++++++-------- Tests/zkpTests/secp256k1Tests.swift | 26 +++- 10 files changed, 306 insertions(+), 100 deletions(-) create mode 100644 Sources/zkp/MuSig2.swift diff --git a/README.md b/README.md index 20a1094..8965e0c 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,8 @@ print(try! signature.derRepresentation.base64EncodedString()) ## Schnorr ```swift -let privateKey = try! secp256k1.Schnorr.PrivateKey() +// Disable BIP340 to enable Schnorr signatures of variable length messages +let privateKey = try! secp256k1.Schnorr.PrivateKey(strict: false) // Extra params for custom signing var auxRand = try! "C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906".bytes diff --git a/Sources/zkp/ECDH.swift b/Sources/zkp/ECDH.swift index 463c436..a12ec2b 100644 --- a/Sources/zkp/ECDH.swift +++ b/Sources/zkp/ECDH.swift @@ -150,11 +150,11 @@ extension secp256k1.KeyAgreement.PrivateKey: DiffieHellmanKeyAgreement { handler: HashFunctionType? = nil, data: UnsafeMutableRawPointer? = nil ) throws -> SharedSecret { - let context = secp256k1.Context.raw + let context = secp256k1.Context.rawRepresentation var publicKey = secp256k1_pubkey() var sharedSecret = [UInt8](repeating: 0, count: 32) - guard secp256k1_ec_pubkey_parse(secp256k1.Context.raw, &publicKey, publicKeyShare.bytes, publicKeyShare.bytes.count).boolValue, + guard secp256k1_ec_pubkey_parse(context, &publicKey, publicKeyShare.bytes, publicKeyShare.bytes.count).boolValue, secp256k1_ecdh(context, &sharedSecret, &publicKey, baseKey.key.bytes, handler, data).boolValue else { throw secp256k1Error.underlyingCryptoError } diff --git a/Sources/zkp/ECDSA.swift b/Sources/zkp/ECDSA.swift index 39c0a97..91fa491 100644 --- a/Sources/zkp/ECDSA.swift +++ b/Sources/zkp/ECDSA.swift @@ -64,10 +64,16 @@ public extension secp256k1.Signing { /// - Parameter derRepresentation: A DER representation of the key as a collection of contiguous bytes. /// - Throws: If there is a failure with parsing the derRepresentation public init(derRepresentation: D) throws { + let context = secp256k1.Context.rawRepresentation let derSignatureBytes = Array(derRepresentation) var signature = secp256k1_ecdsa_signature() - guard secp256k1_ecdsa_signature_parse_der(secp256k1.Context.raw, &signature, derSignatureBytes, derSignatureBytes.count).boolValue else { + guard secp256k1_ecdsa_signature_parse_der( + context, + &signature, + derSignatureBytes, + derSignatureBytes.count + ).boolValue else { throw secp256k1Error.underlyingCryptoError } @@ -78,9 +84,14 @@ public extension secp256k1.Signing { /// - Parameter derRepresentation: A Compact representation of the key as a collection of contiguous bytes. /// - Throws: If there is a failure with parsing the derRepresentation public init(compactRepresentation: D) throws { + let context = secp256k1.Context.rawRepresentation var signature = secp256k1_ecdsa_signature() - guard secp256k1_ecdsa_signature_parse_compact(secp256k1.Context.raw, &signature, Array(compactRepresentation)).boolValue else { + guard secp256k1_ecdsa_signature_parse_compact( + context, + &signature, + Array(compactRepresentation) + ).boolValue else { throw secp256k1Error.underlyingCryptoError } @@ -100,13 +111,18 @@ public extension secp256k1.Signing { /// - Returns: a 64-byte data representation of the compact serialization public var compactRepresentation: Data { get throws { + let context = secp256k1.Context.rawRepresentation let compactSignatureLength = 64 var signature = secp256k1_ecdsa_signature() var compactSignature = [UInt8](repeating: 0, count: compactSignatureLength) rawRepresentation.copyToUnsafeMutableBytes(of: &signature.data) - guard secp256k1_ecdsa_signature_serialize_compact(secp256k1.Context.raw, &compactSignature, &signature).boolValue else { + guard secp256k1_ecdsa_signature_serialize_compact( + context, + &compactSignature, + &signature + ).boolValue else { throw secp256k1Error.underlyingCryptoError } @@ -119,13 +135,19 @@ public extension secp256k1.Signing { /// - Returns: a DER representation of the signature public var derRepresentation: Data { get throws { + let context = secp256k1.Context.rawRepresentation var signature = secp256k1_ecdsa_signature() var derSignatureLength = 80 var derSignature = [UInt8](repeating: 0, count: derSignatureLength) rawRepresentation.copyToUnsafeMutableBytes(of: &signature.data) - guard secp256k1_ecdsa_signature_serialize_der(secp256k1.Context.raw, &derSignature, &derSignatureLength, &signature).boolValue else { + guard secp256k1_ecdsa_signature_serialize_der( + context, + &derSignature, + &derSignatureLength, + &signature + ).boolValue else { throw secp256k1Error.underlyingCryptoError } @@ -144,10 +166,11 @@ extension secp256k1.Signing.PrivateKey: DigestSigner { /// - Returns: The ECDSA Signature. /// - Throws: If there is a failure producing the signature public func signature(for digest: D) throws -> secp256k1.Signing.ECDSASignature { + let context = secp256k1.Context.rawRepresentation var signature = secp256k1_ecdsa_signature() guard secp256k1_ecdsa_sign( - secp256k1.Context.raw, + context, &signature, Array(digest), Array(rawRepresentation), @@ -183,13 +206,14 @@ extension secp256k1.Signing.PublicKey: DigestValidator { /// - digest: The digest that was signed. /// - Returns: True if the signature is valid, false otherwise. public func isValidSignature(_ signature: secp256k1.Signing.ECDSASignature, for digest: D) -> Bool { + let context = secp256k1.Context.rawRepresentation var ecdsaSignature = secp256k1_ecdsa_signature() var publicKey = secp256k1_pubkey() signature.rawRepresentation.copyToUnsafeMutableBytes(of: &ecdsaSignature.data) - return secp256k1_ec_pubkey_parse(secp256k1.Context.raw, &publicKey, bytes, bytes.count).boolValue && - secp256k1_ecdsa_verify(secp256k1.Context.raw, &ecdsaSignature, Array(digest), &publicKey).boolValue + return secp256k1_ec_pubkey_parse(context, &publicKey, bytes, bytes.count).boolValue && + secp256k1_ecdsa_verify(context, &ecdsaSignature, Array(digest), &publicKey).boolValue } } diff --git a/Sources/zkp/MuSig2.swift b/Sources/zkp/MuSig2.swift new file mode 100644 index 0000000..f0870ef --- /dev/null +++ b/Sources/zkp/MuSig2.swift @@ -0,0 +1,13 @@ +// +// MuSig2.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.Schnorr {} diff --git a/Sources/zkp/Recovery.swift b/Sources/zkp/Recovery.swift index 4b10e7e..ff7f8d9 100644 --- a/Sources/zkp/Recovery.swift +++ b/Sources/zkp/Recovery.swift @@ -124,6 +124,7 @@ public extension secp256k1.Recovery { /// - Returns: a 64-byte data representation of the compact serialization public var compactRepresentation: ECDSACompactSignature { get throws { + let context = secp256k1.Context.rawRepresentation let compactSignatureLength = 64 var recoveryId = Int32() var recoverableSignature = secp256k1_ecdsa_recoverable_signature() @@ -132,7 +133,7 @@ public extension secp256k1.Recovery { rawRepresentation.copyToUnsafeMutableBytes(of: &recoverableSignature.data) guard secp256k1_ecdsa_recoverable_signature_serialize_compact( - secp256k1.Context.raw, + context, &compactSignature, &recoveryId, &recoverableSignature @@ -150,13 +151,14 @@ public extension secp256k1.Recovery { /// Convert a recoverable signature into a normal signature. public var normalize: secp256k1.Signing.ECDSASignature { get throws { + let context = secp256k1.Context.rawRepresentation var normalizedSignature = secp256k1_ecdsa_signature() var recoverableSignature = secp256k1_ecdsa_recoverable_signature() rawRepresentation.copyToUnsafeMutableBytes(of: &recoverableSignature.data) guard secp256k1_ecdsa_recoverable_signature_convert( - secp256k1.Context.raw, + context, &normalizedSignature, &recoverableSignature ).boolValue else { @@ -195,10 +197,11 @@ public extension secp256k1.Recovery { /// - Parameter compactRepresentation: A Compact representation of the key as a collection of contiguous bytes. /// - Throws: If there is a failure with parsing the derRepresentation public init(compactRepresentation: D, recoveryId: Int32) throws { + let context = secp256k1.Context.rawRepresentation var recoverableSignature = secp256k1_ecdsa_recoverable_signature() guard secp256k1_ecdsa_recoverable_signature_parse_compact( - secp256k1.Context.raw, + context, &recoverableSignature, Array(compactRepresentation), recoveryId @@ -230,10 +233,11 @@ extension secp256k1.Recovery.PrivateKey: DigestSigner { /// - Returns: The recoverable ECDSA Signature. /// - Throws: If there is a failure producing the signature public func signature(for digest: D) throws -> Signature { + let context = secp256k1.Context.rawRepresentation var signature = secp256k1_ecdsa_recoverable_signature() guard secp256k1_ecdsa_sign_recoverable( - secp256k1.Context.raw, + context, &signature, Array(digest), Array(rawRepresentation), diff --git a/Sources/zkp/SHA256.swift b/Sources/zkp/SHA256.swift index b0c0039..badaa5a 100644 --- a/Sources/zkp/SHA256.swift +++ b/Sources/zkp/SHA256.swift @@ -31,11 +31,19 @@ public enum SHA256 { /// - Throws: An error if the tagged hash computation fails. /// - Returns: The computed digest. public static func taggedHash(tag: D, data: D) throws -> SHA256Digest { + let context = secp256k1.Context.rawRepresentation let tagBytes = Array(tag) let messageBytes = Array(data) var output = [UInt8](repeating: 0, count: 32) - guard secp256k1_tagged_sha256(secp256k1.Context.raw, &output, tagBytes, tagBytes.count, messageBytes, messageBytes.count).boolValue else { + guard secp256k1_tagged_sha256( + context, + &output, + tagBytes, + tagBytes.count, + messageBytes, + messageBytes.count + ).boolValue else { throw secp256k1Error.underlyingCryptoError } diff --git a/Sources/zkp/Schnorr.swift b/Sources/zkp/Schnorr.swift index fa896bc..7fd9c4b 100644 --- a/Sources/zkp/Schnorr.swift +++ b/Sources/zkp/Schnorr.swift @@ -35,6 +35,11 @@ public extension secp256k1.Schnorr { /// Generated secp256k1 Signing Key. private let baseKey: PrivateKeyImplementation + /// Whether strict BIP340 adherence is enabled + public var strict: Bool { + baseKey.strict + } + /// The associated x-only public key for verifying Schnorr signatures. /// /// - Returns: The associated x-only public key. @@ -51,8 +56,8 @@ public extension secp256k1.Schnorr { /// /// - Parameter format: The key format, default is .compressed. /// - Throws: An error if the private key cannot be generated. - public init(format: secp256k1.Format = .compressed) throws { - self.baseKey = try PrivateKeyImplementation(format: format) + public init(strict: Bool = true) throws { + self.baseKey = try PrivateKeyImplementation(strict: strict) } /// Creates a secp256k1 private key for signing from a data representation. @@ -60,8 +65,11 @@ public extension secp256k1.Schnorr { /// - Parameter data: A raw representation of the key. /// - Parameter format: The key format, default is .compressed. /// - Throws: An error if the raw representation does not create a private key for signing. - public init(rawRepresentation data: D, format: secp256k1.Format = .compressed) throws { - self.baseKey = try PrivateKeyImplementation(rawRepresentation: data, format: format) + public init( + rawRepresentation data: D, + strict: Bool = true + ) throws { + self.baseKey = try PrivateKeyImplementation(rawRepresentation: data, strict: strict) } /// Determines if two private keys are equal. @@ -76,7 +84,7 @@ public extension secp256k1.Schnorr { } /// The corresponding x-only public key for the secp256k1 curve. - struct XonlyKey { + struct XonlyKey: Equatable { /// Generated secp256k1 x-only public key. private let baseKey: XonlyKeyImplementation @@ -87,7 +95,12 @@ public extension secp256k1.Schnorr { /// Schnorr x-only public key are implicit of the point being even, therefore this will always return `false`.` public var parity: Bool { - false + baseKey.strict ? false : baseKey.keyParity.boolValue + } + + /// Whether strict BIP340 adherence is enabled + public var strict: Bool { + baseKey.strict } /// Generates a secp256k1 x-only public key. @@ -103,6 +116,16 @@ public extension secp256k1.Schnorr { public init(rawRepresentation data: D) { self.baseKey = XonlyKeyImplementation(rawRepresentation: data, keyParity: 0) } + + /// Determines if two x-only keys are equal. + /// + /// - Parameters: + /// - lhs: The left-hand side private key. + /// - rhs: The right-hand side private key. + /// - Returns: True if the private keys are equal, false otherwise. + public static func == (lhs: Self, rhs: Self) -> Bool { + lhs.baseKey.bytes == rhs.baseKey.bytes + } } } @@ -229,14 +252,29 @@ extension secp256k1.Schnorr.PrivateKey: DigestSigner, Signer { /// - auxiliaryRand: Auxiliary randomness; BIP340 requires 32-bytes. /// - Returns: The Schnorr Signature. /// - Throws: If there is a failure creating the context or signature. - public func signature(message: inout [UInt8], auxiliaryRand: UnsafeMutableRawPointer?) throws -> secp256k1.Schnorr.SchnorrSignature { + public func signature( + message: inout [UInt8], + auxiliaryRand: UnsafeMutableRawPointer? + ) throws -> secp256k1.Schnorr.SchnorrSignature { + guard strict == false || message.count == secp256k1.Schnorr.xonlyByteCount else { + throw secp256k1Error.incorrectParameterSize + } + + let context = secp256k1.Context.rawRepresentation + let magic = secp256k1.Schnorr.magic var keypair = secp256k1_keypair() var signature = [UInt8](repeating: 0, count: secp256k1.Schnorr.signatureByteCount) - var extraParams = secp256k1_schnorrsig_extraparams(magic: secp256k1.Schnorr.magic, noncefp: nil, ndata: auxiliaryRand) - - guard secp256k1_keypair_create(secp256k1.Context.raw, &keypair, Array(rawRepresentation)).boolValue, - secp256k1_schnorrsig_sign_custom(secp256k1.Context.raw, &signature, &message, message.count, &keypair, &extraParams).boolValue - else { + var extraParams = secp256k1_schnorrsig_extraparams(magic: magic, noncefp: nil, ndata: auxiliaryRand) + + guard secp256k1_keypair_create(context, &keypair, Array(rawRepresentation)).boolValue, + secp256k1_schnorrsig_sign_custom( + context, + &signature, + &message, + message.count, + &keypair, + &extraParams + ).boolValue else { throw secp256k1Error.underlyingCryptoError } @@ -290,9 +328,16 @@ extension secp256k1.Schnorr.XonlyKey: DigestValidator, DataValidator { /// - message: The message that was signed. /// - Returns: True if the signature is valid, false otherwise. public func isValid(_ signature: secp256k1.Schnorr.SchnorrSignature, for message: inout [UInt8]) -> Bool { + let context = secp256k1.Context.rawRepresentation var pubKey = secp256k1_xonly_pubkey() - return secp256k1_xonly_pubkey_parse(secp256k1.Context.raw, &pubKey, bytes).boolValue && - secp256k1_schnorrsig_verify(secp256k1.Context.raw, signature.rawRepresentation.bytes, message, message.count, &pubKey).boolValue + return secp256k1_xonly_pubkey_parse(context, &pubKey, bytes).boolValue && + secp256k1_schnorrsig_verify( + context, + signature.rawRepresentation.bytes, + message, + message.count, + &pubKey + ).boolValue } } diff --git a/Sources/zkp/Tweak.swift b/Sources/zkp/Tweak.swift index be6e0e9..d526277 100644 --- a/Sources/zkp/Tweak.swift +++ b/Sources/zkp/Tweak.swift @@ -15,10 +15,11 @@ public extension secp256k1.Signing.PrivateKey { /// - Parameter tweak: the 32-byte tweak object /// - Returns: tweaked `PrivateKey` object func add(_ tweak: [UInt8]) throws -> Self { + let context = secp256k1.Context.rawRepresentation var privateBytes = key.bytes - guard secp256k1_ec_seckey_tweak_add(secp256k1.Context.raw, &privateBytes, tweak).boolValue, - secp256k1_ec_seckey_verify(secp256k1.Context.raw, privateBytes).boolValue else { + guard secp256k1_ec_seckey_tweak_add(context, &privateBytes, tweak).boolValue, + secp256k1_ec_seckey_verify(context, privateBytes).boolValue else { throw secp256k1Error.underlyingCryptoError } @@ -31,15 +32,16 @@ public extension secp256k1.Signing.PrivateKey { /// - Parameter tweak: the 32-byte tweak object /// - Returns: tweaked `PrivateKey` object func add(xonly tweak: [UInt8]) throws -> Self { + let context = secp256k1.Context.rawRepresentation var keypair = secp256k1_keypair() var privateBytes = [UInt8](repeating: 0, count: secp256k1.ByteDetails.count) var xonly = secp256k1_xonly_pubkey() var keyParity = Int32() - guard secp256k1_keypair_create(secp256k1.Context.raw, &keypair, key.bytes).boolValue, - secp256k1_keypair_xonly_tweak_add(secp256k1.Context.raw, &keypair, tweak).boolValue, - secp256k1_keypair_sec(secp256k1.Context.raw, &privateBytes, &keypair).boolValue, - secp256k1_keypair_xonly_pub(secp256k1.Context.raw, &xonly, &keyParity, &keypair).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 } @@ -50,10 +52,11 @@ public extension secp256k1.Signing.PrivateKey { /// - Parameter tweak: the 32-byte tweak object /// - Returns: tweaked `PrivateKey` object func multiply(_ tweak: [UInt8]) throws -> Self { + let context = secp256k1.Context.rawRepresentation var privateBytes = key.bytes - guard secp256k1_ec_seckey_tweak_mul(secp256k1.Context.raw, &privateBytes, tweak).boolValue, - secp256k1_ec_seckey_verify(secp256k1.Context.raw, privateBytes).boolValue else { + guard secp256k1_ec_seckey_tweak_mul(context, &privateBytes, tweak).boolValue, + secp256k1_ec_seckey_verify(context, privateBytes).boolValue else { throw secp256k1Error.underlyingCryptoError } @@ -68,7 +71,7 @@ public extension secp256k1.Signing.PublicKey { /// - format: the format of the tweaked `PublicKey` object /// - Returns: tweaked `PublicKey` object func add(_ tweak: [UInt8], format: secp256k1.Format = .compressed) throws -> Self { - let context = secp256k1.Context.raw + let context = secp256k1.Context.rawRepresentation var pubKey = secp256k1_pubkey() var pubKeyLen = format.length var pubKeyBytes = [UInt8](repeating: 0, count: pubKeyLen) @@ -88,7 +91,7 @@ public extension secp256k1.Signing.PublicKey { /// - format: the format of the tweaked `PublicKey` object /// - Returns: tweaked `PublicKey` object func multiply(_ tweak: [UInt8], format: secp256k1.Format = .compressed) throws -> Self { - let context = secp256k1.Context.raw + let context = secp256k1.Context.rawRepresentation var pubKey = secp256k1_pubkey() var pubKeyLen = format.length var pubKeyBytes = [UInt8](repeating: 0, count: pubKeyLen) @@ -110,7 +113,7 @@ public extension secp256k1.Signing.XonlyKey { /// - format: the format of the tweaked `XonlyKey` object /// - Returns: tweaked `PublicKey` object func add(_ tweak: [UInt8]) throws -> Self { - let context = secp256k1.Context.raw + let context = secp256k1.Context.rawRepresentation var pubKey = secp256k1_pubkey() var inXonlyPubKey = secp256k1_xonly_pubkey() var outXonlyPubKey = secp256k1_xonly_pubkey() diff --git a/Sources/zkp/secp256k1.swift b/Sources/zkp/secp256k1.swift index 15f7e72..57f637b 100644 --- a/Sources/zkp/secp256k1.swift +++ b/Sources/zkp/secp256k1.swift @@ -17,6 +17,8 @@ public enum secp256k1 {} /// passed to secp256k1_context_create, secp256k1_context_preallocated_size, and secp256k1_context_preallocated_create. public extension secp256k1 { struct Context: OptionSet { + /// The raw representation of `secp256k1.Context` + public static let rawRepresentation = try! secp256k1.Context.create() /// Raw value representing the underlying UInt32 flags. public let rawValue: UInt32 @@ -30,10 +32,6 @@ public extension secp256k1 { /// No context flag. public static let none = Context(rawValue: SECP256K1_CONTEXT_NONE) - /// Context flag for signing. - public static let sign = Context(rawValue: SECP256K1_CONTEXT_SIGN) - /// Context flag for verifying. - public static let verify = Context(rawValue: SECP256K1_CONTEXT_VERIFY) /// Creates a new secp256k1 context with the specified flags. /// - Parameter context: The context flags to create a new secp256k1 context. @@ -48,9 +46,6 @@ public extension secp256k1 { return context } - - /// The raw secp256k1 context. - public static let raw = try! secp256k1.Context.create() } } @@ -113,57 +108,76 @@ extension secp256k1 { /// Private key for signing implementation @usableFromInline struct PrivateKeyImplementation { /// Backing private key object - private var _privateBytes: SecureBytes + private var privateBytes: SecureBytes /// Backing public key object - @usableFromInline let _publicBytes: [UInt8] + @usableFromInline let publicBytes: [UInt8] /// Backing x-only public key object - @usableFromInline let _xonlyBytes: [UInt8] + @usableFromInline let xonlyBytes: [UInt8] /// Backing public key format - @usableFromInline let _format: secp256k1.Format + @usableFromInline let format: secp256k1.Format + + /// Backing strict adherence to BIP340 + @usableFromInline let strict: Bool /// Backing key parity - @usableFromInline var _keyParity: Int32 + @usableFromInline var keyParity: Int32 /// Backing implementation for a public key object @usableFromInline var publicKey: PublicKeyImplementation { - PublicKeyImplementation(_publicBytes, xonly: _xonlyBytes, keyParity: _keyParity, format: _format) + PublicKeyImplementation(publicBytes, xonly: xonlyBytes, keyParity: keyParity, format: format) } /// Backing secp256k1 private key object var key: SecureBytes { - _privateBytes + privateBytes } /// A data representation of the backing private key @usableFromInline var rawRepresentation: Data { - Data(_privateBytes) + Data(privateBytes) } /// Backing initialization that creates a random secp256k1 private key for signing - @usableFromInline init(format: secp256k1.Format = .compressed) throws { + @usableFromInline init(format: secp256k1.Format = .compressed, strict: Bool = false) throws { let privateKey = SecureBytes(count: secp256k1.ByteDetails.count) // Verify Private Key here - self._keyParity = 0 - self._format = format - self._privateBytes = privateKey - self._publicBytes = try PublicKeyImplementation.generate(bytes: &_privateBytes, format: format) - self._xonlyBytes = try XonlyKeyImplementation.generate(bytes: _publicBytes, keyParity: &_keyParity, format: format) + self.keyParity = 0 + self.format = format + self.strict = strict + self.privateBytes = privateKey + self.publicBytes = try PublicKeyImplementation.generate(bytes: &privateBytes, format: format, strict: strict) + self.xonlyBytes = try XonlyKeyImplementation.generate( + bytes: publicBytes, + keyParity: &keyParity, + format: format, + strict: strict + ) } /// Backing initialization that creates a secp256k1 private key for signing from a data representation. /// - Parameter data: A raw representation of the key. /// - Throws: An error is thrown when the raw representation does not create a private key for signing. - init(rawRepresentation data: D, format: secp256k1.Format = .compressed) throws { + init( + rawRepresentation data: D, + format: secp256k1.Format = .compressed, + strict: Bool = false + ) throws { let privateKey = SecureBytes(bytes: data) // Verify Private Key here - self._keyParity = 0 - self._format = format - self._privateBytes = privateKey - self._publicBytes = try PublicKeyImplementation.generate(bytes: &_privateBytes, format: format) - self._xonlyBytes = try XonlyKeyImplementation.generate(bytes: _publicBytes, keyParity: &_keyParity, format: format) + self.keyParity = 0 + self.format = format + self.strict = strict + self.privateBytes = privateKey + self.publicBytes = try PublicKeyImplementation.generate(bytes: &privateBytes, format: format, strict: strict) + self.xonlyBytes = try XonlyKeyImplementation.generate( + bytes: publicBytes, + keyParity: &keyParity, + format: format, + strict: strict + ) } } @@ -173,14 +187,20 @@ extension secp256k1 { @usableFromInline let bytes: [UInt8] /// Backing x-only public key object - @usableFromInline let _xonlyBytes: [UInt8] + @usableFromInline let xonlyBytes: [UInt8] /// Backing key parity object - @usableFromInline let _keyParity: Int32 + @usableFromInline let keyParity: Int32 + + /// A key format representation of the backing public key + @usableFromInline let format: secp256k1.Format + + /// Backing strict adherence to BIP340 + @usableFromInline let strict: Bool /// Backing implementation for a public key object @usableFromInline var xonly: XonlyKeyImplementation { - XonlyKeyImplementation(_xonlyBytes, keyParity: _keyParity) + XonlyKeyImplementation(xonlyBytes, keyParity: keyParity, strict: strict) } /// A data representation of the backing public key @@ -188,29 +208,44 @@ extension secp256k1 { Data(bytes) } - /// A key format representation of the backing public key - @usableFromInline let format: secp256k1.Format - /// Backing initialization that generates a secp256k1 public key from only a raw representation and key format. /// - Parameters: /// - data: A raw representation of the public key. /// - format: an enum that represents the format of the public key - @usableFromInline init(rawRepresentation data: D, format: secp256k1.Format) throws { + @usableFromInline init( + rawRepresentation data: D, + format: secp256k1.Format, + strict: Bool = false + ) throws { var keyParity = Int32() + self.xonlyBytes = try XonlyKeyImplementation.generate( + bytes: data.bytes, + keyParity: &keyParity, + format: format, + strict: strict + ) + self.bytes = data.bytes self.format = format - self._xonlyBytes = try XonlyKeyImplementation.generate(bytes: data.bytes, keyParity: &keyParity, format: format) - self._keyParity = keyParity + self.strict = strict + self.keyParity = keyParity } /// Backing initialization that sets the public key from a public key object. /// - Parameter keyBytes: a public key object - @usableFromInline init(_ bytes: [UInt8], xonly: [UInt8], keyParity: Int32, format: secp256k1.Format) { + @usableFromInline init( + _ bytes: [UInt8], + xonly: [UInt8], + keyParity: Int32, + format: secp256k1.Format, + strict: Bool = false + ) { self.bytes = bytes self.format = format - self._xonlyBytes = xonly - self._keyParity = keyParity + self.strict = strict + self.xonlyBytes = xonly + self.keyParity = keyParity } /// Backing initialization that sets the public key from a xonly key object. @@ -219,8 +254,9 @@ extension secp256k1 { let yCoord: [UInt8] = xonlyKey.keyParity.boolValue ? [3] : [2] self.format = .compressed - self._xonlyBytes = xonlyKey.bytes - self._keyParity = xonlyKey.keyParity + self.strict = xonlyKey.strict + self.xonlyBytes = xonlyKey.bytes + self.keyParity = xonlyKey.keyParity self.bytes = yCoord + xonlyKey.bytes } @@ -230,7 +266,12 @@ extension secp256k1 { /// - signature: The signature to recover the public key from /// - format: the format of the public key object /// - Throws: An error is thrown when a public key is not recoverable from the signature. - @usableFromInline init(_ digest: D, signature: secp256k1.Recovery.ECDSASignature, format: secp256k1.Format) throws { + @usableFromInline init( + _ digest: D, + signature: secp256k1.Recovery.ECDSASignature, + format: secp256k1.Format + ) throws { + let context = secp256k1.Context.rawRepresentation var keyParity = Int32() var pubKeyLen = format.length var pubKey = secp256k1_pubkey() @@ -239,14 +280,21 @@ extension secp256k1 { signature.rawRepresentation.copyToUnsafeMutableBytes(of: &recoverySignature.data) - guard secp256k1_ecdsa_recover(secp256k1.Context.raw, &pubKey, &recoverySignature, Array(digest)).boolValue, - secp256k1_ec_pubkey_serialize(secp256k1.Context.raw, &pubBytes, &pubKeyLen, &pubKey, format.rawValue).boolValue else { + guard secp256k1_ecdsa_recover(context, &pubKey, &recoverySignature, Array(digest)).boolValue, + secp256k1_ec_pubkey_serialize(context, &pubBytes, &pubKeyLen, &pubKey, format.rawValue).boolValue else { throw secp256k1Error.underlyingCryptoError } - self._xonlyBytes = try XonlyKeyImplementation.generate(bytes: pubBytes, keyParity: &keyParity, format: format) - self._keyParity = keyParity + self.xonlyBytes = try XonlyKeyImplementation.generate( + bytes: pubBytes, + keyParity: &keyParity, + format: format, + strict: false + ) + + self.keyParity = keyParity self.format = format + self.strict = false self.bytes = pubBytes } @@ -254,19 +302,38 @@ extension secp256k1 { /// - Parameter privateBytes: a private key object in bytes form /// - Returns: a public key object /// - Throws: An error is thrown when the bytes does not create a public key. - static func generate(bytes privateBytes: inout SecureBytes, format: secp256k1.Format) throws -> [UInt8] { + static func generate( + bytes privateBytes: inout SecureBytes, + format: secp256k1.Format, + strict: Bool + ) throws -> [UInt8] { guard privateBytes.count == secp256k1.ByteDetails.count else { throw secp256k1Error.incorrectKeySize } + let context = secp256k1.Context.rawRepresentation var pubKeyLen = format.length + var keyParity = Int32() var pubKey = secp256k1_pubkey() - var pubBytes = [UInt8](repeating: 0, count: pubKeyLen) + var keypair = secp256k1_keypair() + var xonly = secp256k1_xonly_pubkey() + var pubBytes = [UInt8](repeating: 0, count: format.length) + var secBytes = privateBytes.bytes + + // Verify seckey and get key parity for BIP340 + guard secp256k1_keypair_create(context, &keypair, privateBytes.bytes).boolValue, + secp256k1_keypair_pub(context, &pubKey, &keypair).boolValue, + secp256k1_keypair_xonly_pub(context, &xonly, &keyParity, &keypair).boolValue else { + throw secp256k1Error.underlyingCryptoError + } - guard secp256k1_ec_seckey_verify(secp256k1.Context.raw, privateBytes.bytes).boolValue, - secp256k1_ec_pubkey_create(secp256k1.Context.raw, &pubKey, privateBytes.bytes).boolValue, - secp256k1_ec_pubkey_serialize(secp256k1.Context.raw, &pubBytes, &pubKeyLen, &pubKey, format.rawValue).boolValue - else { + // For BIP340, negate the private key when the Y coordinate is odd + if strict, keyParity.boolValue, + secp256k1_ec_seckey_negate(context, &secBytes).boolValue { + privateBytes = SecureBytes(bytes: secBytes) + } + + guard secp256k1_ec_pubkey_serialize(context, &pubBytes, &pubKeyLen, &pubKey, format.rawValue).boolValue else { throw secp256k1Error.underlyingCryptoError } @@ -287,36 +354,55 @@ extension secp256k1 { /// Backing key parity object @usableFromInline let keyParity: Int32 + /// Backing strict adherence to BIP340 + @usableFromInline let strict: Bool + /// Backing initialization that generates a x-only public key from a raw representation. /// - Parameter data: A raw representation of the key. - @usableFromInline init(rawRepresentation data: D, keyParity: Int32) { + @usableFromInline init( + rawRepresentation data: D, + keyParity: Int32, + strict: Bool = false + ) { self.bytes = data.bytes self.keyParity = keyParity + self.strict = strict } /// Backing initialization that sets the public key from a x-only public key object. /// - Parameter bytes: a x-only public key in byte form - @usableFromInline init(_ bytes: [UInt8], keyParity: Int32) { + @usableFromInline init( + _ bytes: [UInt8], + keyParity: Int32, + strict: Bool = false + ) { self.bytes = bytes self.keyParity = keyParity + self.strict = strict } /// Create a x-only public key from bytes representation. /// - Parameter privateBytes: a private key object in byte form /// - Returns: a public key object /// - Throws: An error is thrown when the bytes does not create a public key. - static func generate(bytes publicBytes: [UInt8], keyParity: inout Int32, format: secp256k1.Format) throws -> [UInt8] { + static func generate( + bytes publicBytes: [UInt8], + keyParity: inout Int32, + format: secp256k1.Format, + strict: Bool = false + ) throws -> [UInt8] { guard publicBytes.count == format.length else { throw secp256k1Error.incorrectKeySize } + let context = secp256k1.Context.rawRepresentation var pubKey = secp256k1_pubkey() var xonlyPubKey = secp256k1_xonly_pubkey() var xonlyBytes = [UInt8](repeating: 0, count: secp256k1.ByteDetails.count) - guard secp256k1_ec_pubkey_parse(secp256k1.Context.raw, &pubKey, publicBytes, format.length).boolValue, - secp256k1_xonly_pubkey_from_pubkey(secp256k1.Context.raw, &xonlyPubKey, &keyParity, &pubKey).boolValue, - secp256k1_xonly_pubkey_serialize(secp256k1.Context.raw, &xonlyBytes, &xonlyPubKey).boolValue else { + guard secp256k1_ec_pubkey_parse(context, &pubKey, publicBytes, format.length).boolValue, + secp256k1_xonly_pubkey_from_pubkey(context, &xonlyPubKey, &keyParity, &pubKey).boolValue, + secp256k1_xonly_pubkey_serialize(context, &xonlyBytes, &xonlyPubKey).boolValue else { throw secp256k1Error.underlyingCryptoError } diff --git a/Tests/zkpTests/secp256k1Tests.swift b/Tests/zkpTests/secp256k1Tests.swift index aab62cd..3f93734 100644 --- a/Tests/zkpTests/secp256k1Tests.swift +++ b/Tests/zkpTests/secp256k1Tests.swift @@ -261,7 +261,8 @@ final class secp256k1Tests: XCTestCase { let expectedPrivateKey = "4894b8087f428971b55ff96e16f7127340138bc84e7973821a224cad02055975" let expectedSignature = "ad57c21d383ef8ac799adfd469a221c40ef9f09563a16682b9ab1edc46c33d6d6a1d719761d269e87ab971e0ffafc1618a4666a4f9aef4abddc3ea9fc0cd5b12" let privateKeyBytes = try! expectedPrivateKey.bytes - let privateKey = try! secp256k1.Schnorr.PrivateKey(rawRepresentation: privateKeyBytes) + let throwKey = try! secp256k1.Schnorr.PrivateKey(rawRepresentation: privateKeyBytes) + let privateKey = try! secp256k1.Schnorr.PrivateKey(rawRepresentation: privateKeyBytes, strict: false) var messageDigest = "We're all Satoshi Nakamoto and a bit of Harold Thomas Finney II.".data(using: .utf8)!.bytes var auxRand = try! "f50c8c99e39a82f125fa83186b5f2483f39fb0fb56269c755689313a177be6ea".bytes @@ -270,6 +271,7 @@ final class secp256k1Tests: XCTestCase { // Test the verification of the signature output XCTAssertEqual(expectedSignature, String(bytes: signature.rawRepresentation.bytes)) XCTAssertTrue(privateKey.xonly.isValid(signature, for: &messageDigest)) + XCTAssertThrowsError(try throwKey.signature(message: &messageDigest, auxiliaryRand: &auxRand)) } func testSchnorrVerifying() { @@ -545,6 +547,25 @@ final class secp256k1Tests: XCTestCase { XCTAssertEqual(compactBytes, String(bytes: Array(Data(bytes).compactSizePrefix)), "Compact size prefix encoding is incorrect.") } + func testSchnorrNegating() { + let privateBytes = try! "56baa476b36a5b1548279f5bf57b82db39e594aee7912cde30977b8e80e6edca".bytes + let negatedBytes = try! "a9455b894c95a4eab7d860a40a847d2380c94837c7b7735d8f3ae2fe4f4f5377".bytes + + let privateKey = try! secp256k1.Schnorr.PrivateKey(rawRepresentation: privateBytes) + let negatedKey = try! secp256k1.Schnorr.PrivateKey(rawRepresentation: negatedBytes) + let notStrictKey = try! secp256k1.Schnorr.PrivateKey(rawRepresentation: privateBytes, strict: false) + + XCTAssertEqual(privateKey, negatedKey) + XCTAssertEqual(privateKey.rawRepresentation, negatedKey.rawRepresentation) + XCTAssertEqual(privateKey.xonly, negatedKey.xonly) + XCTAssertEqual(privateKey.xonly.bytes, negatedKey.xonly.bytes) + + XCTAssertNotEqual(privateKey, notStrictKey) + XCTAssertNotEqual(privateKey.rawRepresentation, notStrictKey.rawRepresentation) + XCTAssertEqual(privateKey.xonly, notStrictKey.xonly) + XCTAssertEqual(privateKey.xonly.bytes, notStrictKey.xonly.bytes) + } + static var allTests = [ ("testUncompressedKeypairCreation", testUncompressedKeypairCreation), ("testCompressedKeypairCreation", testCompressedKeypairCreation), @@ -578,6 +599,7 @@ final class secp256k1Tests: XCTestCase { ("testKeyAgreementPublicKeyTweakAdd", testKeyAgreementPublicKeyTweakAdd), ("testXonlyToPublicKey", testXonlyToPublicKey), ("testTapscript", testTapscript), - ("testCompactSizePrefix", testCompactSizePrefix) + ("testCompactSizePrefix", testCompactSizePrefix), + ("testSchnorrNegating", testSchnorrNegating) ] }