From 44f72619e63915ac85b5439ca9f3a91da916c468 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 17 Sep 2023 04:33:02 +0000 Subject: [PATCH] Updates to Schnorr, ECDH, and Context (#433) --- Exhaustive/Package/Package.swift | 4 +- Package.swift | 2 +- README.md | 17 ++++--- Sources/secp256k1/Context.swift | 1 + Sources/zkp/Context.swift | 69 +++++++++++++++++++++++++++ Sources/zkp/DH.swift | 3 ++ Sources/zkp/ECDH.swift | 37 +++++++++++---- Sources/zkp/ECDSA.swift | 13 +++--- Sources/zkp/HashDigest.swift | 2 +- Sources/zkp/Recovery.swift | 11 ++--- Sources/zkp/SHA256.swift | 10 +++- Sources/zkp/SafeCompare.swift | 2 +- Sources/zkp/Schnorr.swift | 25 ++++------ Sources/zkp/Tweak.swift | 4 +- Sources/zkp/secp256k1.swift | 72 +++++++---------------------- Tests/zkpTests/secp256k1Tests.swift | 35 +++++++++++--- bitrise.yml | 2 +- 17 files changed, 192 insertions(+), 117 deletions(-) create mode 120000 Sources/secp256k1/Context.swift create mode 100644 Sources/zkp/Context.swift diff --git a/Exhaustive/Package/Package.swift b/Exhaustive/Package/Package.swift index 2f660eb..1cf3d44 100644 --- a/Exhaustive/Package/Package.swift +++ b/Exhaustive/Package/Package.swift @@ -5,13 +5,13 @@ import PackageDescription let package = Package( name: "Package", dependencies: [ - .package(name: "secp256k1", path: "../..") + .package(name: "secp256k1.swift", path: "../..") ], targets: [ .testTarget( name: "secp256k1Tests", dependencies: [ - .product(name: "secp256k1", package: "secp256k1") + .product(name: "secp256k1", package: "secp256k1.swift") ] ) ] diff --git a/Package.swift b/Package.swift index a6a983e..1d93230 100644 --- a/Package.swift +++ b/Package.swift @@ -17,7 +17,7 @@ let dependencies: [Package.Dependency] #endif let package = Package( - name: "secp256k1", + name: "secp256k1.swift", products: [ // WARNING: These APIs should not be considered stable and may change at any time. .library( diff --git a/README.md b/README.md index e27c793..d61f64c 100644 --- a/README.md +++ b/README.md @@ -20,14 +20,14 @@ Long-term goals are: This repository primarily uses Swift package manager as its build tool, so we recommend using that as well. Xcode comes with [built-in support](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app) for Swift packages. From the menu bar, goto: `File > Add Packages...` If you manage packages via a `Package.swift` file, simply add `secp256k1.swift` as a dependencies' clause in your Swift manifest: ```swift -.package(url: "https://github.com/GigaBitcoin/secp256k1.swift.git", from: "0.12.0"), +.package(name: "secp256k1.swift", url: "https://github.com/GigaBitcoin/secp256k1.swift.git", exact: "0.13.0"), ``` Include `secp256k1` as a dependency for your executable target: ```swift .target(name: "", dependencies: [ - .product(name: "secp256k1", package: "secp256k1.swift")]), + .product(name: "secp256k1", package: "secp256k1.swift") ]), ``` @@ -63,8 +63,8 @@ print(try! signature.derRepresentation.base64EncodedString()) ## Schnorr ```swift -// Disable BIP340 to enable Schnorr signatures of variable length messages -let privateKey = try! secp256k1.Schnorr.PrivateKey(strict: false) +// Strict BIP340 mode is disabled by default for Schnorr signatures with variable length messages +let privateKey = try! secp256k1.Schnorr.PrivateKey() // Extra params for custom signing var auxRand = try! "C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906".bytes @@ -91,11 +91,14 @@ let tweakedPublicKeyKey = try! privateKey.publicKey.add(tweak) let privateKey = try! secp256k1.KeyAgreement.PrivateKey() let publicKey = try! secp256k1.KeyAgreement.PrivateKey().publicKey -// Create a shared secret with a private key from only a public key -let sharedSecret = try! privateKey.sharedSecretFromKeyAgreement(with: publicKey) +// Create a compressed shared secret with a private key from only a public key +let sharedSecret = try! privateKey.sharedSecretFromKeyAgreement(with: publicKey, format: .compressed) + +// By default, libsecp256k1 hashes the x-coordinate with version information. +let symmetricKey = SHA256.hash(data: sharedSecret.bytes) ``` -## Silent Payments +## Silent Payments Scheme ```swift let privateSign1 = try! secp256k1.Signing.PrivateKey() diff --git a/Sources/secp256k1/Context.swift b/Sources/secp256k1/Context.swift new file mode 120000 index 0000000..6c68f71 --- /dev/null +++ b/Sources/secp256k1/Context.swift @@ -0,0 +1 @@ +../zkp/Context.swift \ No newline at end of file diff --git a/Sources/zkp/Context.swift b/Sources/zkp/Context.swift new file mode 100644 index 0000000..ed52fd6 --- /dev/null +++ b/Sources/zkp/Context.swift @@ -0,0 +1,69 @@ +// +// Context.swift +// GigaBitcoin/secp256k1.swift +// +// Copyright (c) 2021 GigaBitcoin LLC +// Distributed under the MIT software license +// +// See the accompanying file LICENSE for information +// + +/// A public extension that provides additional functionality to the `secp256k1` structure. +/// +/// This extension includes a nested structure, `Context`, which represents the context for `secp256k1` operations. +/// The primary purpose of context objects is to store randomization data for enhanced protection against side-channel +/// leakage. This protection is only effective if the context is randomized after its creation. +/// +/// A constructed context can safely be used from multiple threads simultaneously, but API calls that take a non-const +/// pointer to a context need exclusive access to it. In particular this is the case for `secp256k1_context_destroy`, +/// `secp256k1_context_preallocated_destroy`, and `secp256k1_context_randomize`. +public extension secp256k1 { + /// A structure that represents the context flags for `secp256k1` operations. + /// + /// This structure conforms to the `OptionSet` protocol, allowing you to combine different context flags. + /// It includes a static property, `none`, which represents a `Context` with no flags. + /// + /// The `Context` structure is used to create and manage the context for `secp256k1` operations. + /// It is used in the creation of the `secp256k1` context and also in determining the size of the preallocated + /// memory for the context. + struct Context: OptionSet { + /// The raw representation of `secp256k1.Context` + public static let rawRepresentation = try! secp256k1.Context.create() + + /// The raw value of the context flags. + public let rawValue: UInt32 + + /// Creates a new `Context` instance with the specified raw value. + public init(rawValue: UInt32) { self.rawValue = rawValue } + + /// Initializes a new Context with the specified raw value. + /// - Parameter rawValue: The Int32 raw value for the context flags. + init(rawValue: Int32) { self.rawValue = UInt32(rawValue) } + + /// No context flag. + /// + /// This static property represents a `Context` with no flags. It can be used when creating a `secp256k1` + /// context with no flags. + public static let none = Context(rawValue: SECP256K1_CONTEXT_NONE) + + /// Creates a new `secp256k1` context with the specified flags. + /// + /// - Parameter context: The context flags to create a new `secp256k1` context. + /// - Throws: An error of type `secp256k1Error.underlyingCryptoError` if the context creation or randomization + /// fails. + /// - Returns: An opaque pointer to the created context. + /// + /// This static method creates a new `secp256k1` context with the specified flags. The flags are represented by + /// the `Context` structure. The method throws an error if the context creation or randomization fails. If the + /// context creation is successful, the method returns an opaque pointer to the created context. + public static func create(_ context: Context = .none) throws -> OpaquePointer { + var randomBytes = SecureBytes(count: secp256k1.ByteLength.privateKey).bytes + guard let context = secp256k1_context_create(context.rawValue), + secp256k1_context_randomize(context, &randomBytes).boolValue else { + throw secp256k1Error.underlyingCryptoError + } + + return context + } + } +} diff --git a/Sources/zkp/DH.swift b/Sources/zkp/DH.swift index cd978ea..b83c77e 100644 --- a/Sources/zkp/DH.swift +++ b/Sources/zkp/DH.swift @@ -52,6 +52,9 @@ protocol DiffieHellmanKeyAgreement { public struct SharedSecret: ContiguousBytes { var ss: SecureBytes + // An enum that represents the format of the shared secret + let format: secp256k1.Format + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { try ss.withUnsafeBytes(body) } diff --git a/Sources/zkp/ECDH.swift b/Sources/zkp/ECDH.swift index 52f1fb0..3eed786 100644 --- a/Sources/zkp/ECDH.swift +++ b/Sources/zkp/ECDH.swift @@ -140,31 +140,52 @@ extension secp256k1.KeyAgreement.PrivateKey: DiffieHellmanKeyAgreement { /// - Returns: Returns a shared secret. /// - Throws: An error occurred while computing the shared secret. func sharedSecretFromKeyAgreement(with publicKeyShare: secp256k1.KeyAgreement.PublicKey) throws -> SharedSecret { - try sharedSecretFromKeyAgreement(with: publicKeyShare, handler: nil) + try sharedSecretFromKeyAgreement(with: publicKeyShare, format: .compressed) } /// Performs a key agreement with the provided public key share. /// /// - Parameters: /// - publicKeyShare: The public key to perform the ECDH with. - /// - handler: Closure for customizing a hash function; Defaults to nil. - /// - data: Additional data for the hash function; Defaults to nil. + /// - format: An enum that represents the format of the shared secret. /// - Returns: Returns a shared secret. /// - Throws: An error occurred while computing the shared secret. public func sharedSecretFromKeyAgreement( with publicKeyShare: secp256k1.KeyAgreement.PublicKey, - handler: HashFunctionType? = nil, - data: UnsafeMutableRawPointer? = nil + format: secp256k1.Format = .compressed ) throws -> SharedSecret { let context = secp256k1.Context.rawRepresentation var publicKey = secp256k1_pubkey() - var sharedSecret = [UInt8](repeating: 0, count: 32) + 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, handler, data).boolValue else { + secp256k1_ecdh(context, &sharedSecret, &publicKey, baseKey.key.bytes, hashClosure(), &data).boolValue else { throw secp256k1Error.underlyingCryptoError } - return SharedSecret(ss: SecureBytes(bytes: sharedSecret)) + return SharedSecret(ss: SecureBytes(bytes: sharedSecret), format: format) + } + + /// Creates a closure which handles creating either a compressed or uncompressed shared secret + /// + /// - Returns: Closure to override the libsecp256k1 hashing function + func hashClosure() -> HashFunctionType { + { output, x32, y32, data in + guard let output, let x32, let y32, let compressed = data?.load(as: Bool.self) else { return 0 } + + let lastByte = y32.advanced(by: secp256k1.ByteLength.dimension - 1).pointee + let version: UInt8 = compressed ? (lastByte & 0x01) | 0x02 : 0x04 + + output.update(repeating: version, count: 1) + output.advanced(by: 1).update(from: x32, count: secp256k1.ByteLength.dimension) + + if compressed == false { + output.advanced(by: secp256k1.ByteLength.dimension + 1) + .update(from: y32, count: secp256k1.ByteLength.dimension) + } + + return 1 + } } } diff --git a/Sources/zkp/ECDSA.swift b/Sources/zkp/ECDSA.swift index 3e0c16b..faaf587 100644 --- a/Sources/zkp/ECDSA.swift +++ b/Sources/zkp/ECDSA.swift @@ -10,7 +10,7 @@ import Foundation -typealias NISTECDSASignature = DataSignature & DERSignature +typealias NISTECDSASignature = DERSignature & DataSignature protocol DataSignature { init(dataRepresentation: D) throws @@ -41,7 +41,7 @@ public extension secp256k1.Signing { /// - dataRepresentation: A data representation of the key as a collection of contiguous bytes. /// - Throws: If there is a failure with the dataRepresentation count public init(dataRepresentation: D) throws { - guard dataRepresentation.count == 4 * secp256k1.CurveDetails.coordinateByteCount else { + guard dataRepresentation.count == secp256k1.ByteLength.signature else { throw secp256k1Error.incorrectParameterSize } @@ -52,8 +52,8 @@ public extension secp256k1.Signing { /// - Parameters: /// - dataRepresentation: A data representation of the key as a collection of contiguous bytes. /// - Throws: If there is a failure with the dataRepresentation count - internal init(_ dataRepresentation: Data) throws { - guard dataRepresentation.count == 4 * secp256k1.CurveDetails.coordinateByteCount else { + init(_ dataRepresentation: Data) throws { + guard dataRepresentation.count == secp256k1.ByteLength.signature else { throw secp256k1Error.incorrectParameterSize } @@ -112,9 +112,8 @@ public extension secp256k1.Signing { 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) + var compactSignature = [UInt8](repeating: 0, count: secp256k1.ByteLength.signature) dataRepresentation.copyToUnsafeMutableBytes(of: &signature.data) @@ -126,7 +125,7 @@ public extension secp256k1.Signing { throw secp256k1Error.underlyingCryptoError } - return Data(bytes: &compactSignature, count: compactSignatureLength) + return Data(bytes: &compactSignature, count: secp256k1.ByteLength.signature) } } diff --git a/Sources/zkp/HashDigest.swift b/Sources/zkp/HashDigest.swift index 86b9c6d..2db6175 100644 --- a/Sources/zkp/HashDigest.swift +++ b/Sources/zkp/HashDigest.swift @@ -54,7 +54,7 @@ public struct HashDigest: Digest { /// The byte count of the hash digest. public static var byteCount: Int { - get { 32 } + get { SHA256.digestByteCount } set { fatalError("Cannot set SHA256.byteCount") } } diff --git a/Sources/zkp/Recovery.swift b/Sources/zkp/Recovery.swift index 088d706..ce3dcd2 100644 --- a/Sources/zkp/Recovery.swift +++ b/Sources/zkp/Recovery.swift @@ -128,10 +128,9 @@ public extension secp256k1.Recovery { public var compactRepresentation: ECDSACompactSignature { get throws { let context = secp256k1.Context.rawRepresentation - let compactSignatureLength = 64 var recoveryId = Int32() var recoverableSignature = secp256k1_ecdsa_recoverable_signature() - var compactSignature = [UInt8](repeating: 0, count: compactSignatureLength) + var compactSignature = [UInt8](repeating: 0, count: secp256k1.ByteLength.signature) dataRepresentation.copyToUnsafeMutableBytes(of: &recoverableSignature.data) @@ -145,7 +144,7 @@ public extension secp256k1.Recovery { } return secp256k1.Recovery.ECDSACompactSignature( - signature: Data(bytes: &compactSignature, count: compactSignatureLength), + signature: Data(bytes: &compactSignature, count: secp256k1.ByteLength.signature), recoveryId: recoveryId ) } @@ -177,7 +176,7 @@ public extension secp256k1.Recovery { /// - dataRepresentation: A data representation of the key as a collection of contiguous bytes. /// - Throws: If there is a failure with the dataRepresentation count public init(dataRepresentation: D) throws { - guard dataRepresentation.count == 4 * secp256k1.CurveDetails.coordinateByteCount + 1 else { + guard dataRepresentation.count == secp256k1.ByteLength.signature + 1 else { throw secp256k1Error.incorrectParameterSize } @@ -188,8 +187,8 @@ public extension secp256k1.Recovery { /// - Parameters: /// - dataRepresentation: A data representation of the key as a collection of contiguous bytes. /// - Throws: If there is a failure with the dataRepresentation count - internal init(_ dataRepresentation: Data) throws { - guard dataRepresentation.count == 4 * secp256k1.CurveDetails.coordinateByteCount + 1 else { + init(_ dataRepresentation: Data) throws { + guard dataRepresentation.count == secp256k1.ByteLength.signature + 1 else { throw secp256k1Error.incorrectParameterSize } diff --git a/Sources/zkp/SHA256.swift b/Sources/zkp/SHA256.swift index badaa5a..cf4dee5 100644 --- a/Sources/zkp/SHA256.swift +++ b/Sources/zkp/SHA256.swift @@ -12,12 +12,18 @@ import Foundation /// The SHA256 hashing algorithm. public enum SHA256 { + /// The number of bytes in a SHA256 digest. + @inlinable + static var digestByteCount: Int { + 32 + } + /// Computes a digest of the data. /// - Parameter data: The data to be hashed. /// - Returns: The computed digest. public static func hash(data: D) -> SHA256Digest { let stringData = Array(data) - var output = [UInt8](repeating: 0, count: 32) + var output = [UInt8](repeating: 0, count: Self.digestByteCount) secp256k1_swift_sha256(&output, stringData, stringData.count) @@ -34,7 +40,7 @@ public enum SHA256 { let context = secp256k1.Context.rawRepresentation let tagBytes = Array(tag) let messageBytes = Array(data) - var output = [UInt8](repeating: 0, count: 32) + var output = [UInt8](repeating: 0, count: Self.digestByteCount) guard secp256k1_tagged_sha256( context, diff --git a/Sources/zkp/SafeCompare.swift b/Sources/zkp/SafeCompare.swift index e361079..7c7c270 100644 --- a/Sources/zkp/SafeCompare.swift +++ b/Sources/zkp/SafeCompare.swift @@ -34,7 +34,7 @@ import Foundation /// A constant-time comparison function for any two collections of bytes. - internal func safeCompare(_ lhs: LHS, _ rhs: RHS) -> Bool { + func safeCompare(_ lhs: LHS, _ rhs: RHS) -> Bool { let lBytes = lhs.bytes let rBytes = rhs.bytes diff --git a/Sources/zkp/Schnorr.swift b/Sources/zkp/Schnorr.swift index 361947c..2e3746a 100644 --- a/Sources/zkp/Schnorr.swift +++ b/Sources/zkp/Schnorr.swift @@ -12,13 +12,6 @@ import Foundation public extension secp256k1 { enum Schnorr { - /// Fixed number of bytes for Schnorr signature - /// - /// [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#abstract) - @inlinable static var signatureByteCount: Int { 64 } - - @inlinable static var xonlyByteCount: Int { 32 } - /// Tuple representation of ``SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC`` /// /// Only used at initialization and has no other function than making sure the object is initialized. @@ -136,7 +129,7 @@ public extension secp256k1.Schnorr { /// - dataRepresentation: A raw representation of the key as a collection of contiguous bytes. /// - Throws: If there is a failure with the rawRepresentation count public init(dataRepresentation: D) throws { - guard dataRepresentation.count == secp256k1.Schnorr.signatureByteCount else { + guard dataRepresentation.count == secp256k1.ByteLength.signature else { throw secp256k1Error.incorrectParameterSize } @@ -147,8 +140,8 @@ public extension secp256k1.Schnorr { /// - Parameters: /// - rawRepresentation: A raw representation of the key as a collection of contiguous bytes. /// - Throws: If there is a failure with the dataRepresentation count - internal init(_ dataRepresentation: Data) throws { - guard dataRepresentation.count == secp256k1.Schnorr.signatureByteCount else { + init(_ dataRepresentation: Data) throws { + guard dataRepresentation.count == secp256k1.ByteLength.signature else { throw secp256k1Error.incorrectParameterSize } @@ -181,7 +174,7 @@ extension secp256k1.Schnorr.PrivateKey: DigestSigner, Signer { /// - Returns: The Schnorr Signature. /// - Throws: If there is a failure producing the signature. public func signature(for data: D) throws -> secp256k1.Schnorr.SchnorrSignature { - try signature(for: data, auxiliaryRand: SecureBytes(count: secp256k1.Schnorr.xonlyByteCount).bytes) + try signature(for: data, auxiliaryRand: SecureBytes(count: secp256k1.ByteLength.dimension).bytes) } /// Generates an Schnorr signature from the hash digest object @@ -196,7 +189,7 @@ extension secp256k1.Schnorr.PrivateKey: DigestSigner, Signer { /// - Returns: The Schnorr Signature. /// - Throws: If there is a failure producing the signature. public func signature(for digest: D) throws -> secp256k1.Schnorr.SchnorrSignature { - try signature(for: digest, auxiliaryRand: SecureBytes(count: secp256k1.Schnorr.xonlyByteCount).bytes) + try signature(for: digest, auxiliaryRand: SecureBytes(count: secp256k1.ByteLength.dimension).bytes) } /// Generates an Schnorr signature from a hash of a variable length data object @@ -249,16 +242,16 @@ extension secp256k1.Schnorr.PrivateKey: DigestSigner, Signer { public func signature( message: inout [UInt8], auxiliaryRand: UnsafeMutableRawPointer?, - strict: Bool = true + strict: Bool = false ) throws -> secp256k1.Schnorr.SchnorrSignature { - guard strict == false || message.count == secp256k1.Schnorr.xonlyByteCount else { + guard strict == false || message.count == secp256k1.ByteLength.dimension 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 signature = [UInt8](repeating: 0, count: secp256k1.ByteLength.signature) var extraParams = secp256k1_schnorrsig_extraparams(magic: magic, noncefp: nil, ndata: auxiliaryRand) guard secp256k1_keypair_create(context, &keypair, Array(dataRepresentation)).boolValue, @@ -273,7 +266,7 @@ extension secp256k1.Schnorr.PrivateKey: DigestSigner, Signer { throw secp256k1Error.underlyingCryptoError } - return try secp256k1.Schnorr.SchnorrSignature(Data(bytes: signature, count: secp256k1.Schnorr.signatureByteCount)) + return try secp256k1.Schnorr.SchnorrSignature(Data(bytes: signature, count: secp256k1.ByteLength.signature)) } } diff --git a/Sources/zkp/Tweak.swift b/Sources/zkp/Tweak.swift index 6735f3c..5e177c1 100644 --- a/Sources/zkp/Tweak.swift +++ b/Sources/zkp/Tweak.swift @@ -34,7 +34,7 @@ public extension secp256k1.Signing.PrivateKey { 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 privateBytes = [UInt8](repeating: 0, count: secp256k1.ByteLength.privateKey) var xonly = secp256k1_xonly_pubkey() var keyParity = Int32() @@ -117,7 +117,7 @@ public extension secp256k1.Schnorr.XonlyKey { var pubKey = secp256k1_pubkey() var inXonlyPubKey = secp256k1_xonly_pubkey() var outXonlyPubKey = secp256k1_xonly_pubkey() - var xonlyBytes = [UInt8](repeating: 0, count: secp256k1.Schnorr.xonlyByteCount) + var xonlyBytes = [UInt8](repeating: 0, count: secp256k1.ByteLength.dimension) var keyParity = Int32() guard secp256k1_xonly_pubkey_parse(context, &inXonlyPubKey, bytes).boolValue, diff --git a/Sources/zkp/secp256k1.swift b/Sources/zkp/secp256k1.swift index 5a8bb78..f18d64f 100644 --- a/Sources/zkp/secp256k1.swift +++ b/Sources/zkp/secp256k1.swift @@ -13,42 +13,6 @@ import Foundation /// The secp256k1 Elliptic Curve. public enum secp256k1 {} -/// An extension to secp256k1 containing a Context structure that represents the flags -/// 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 - - /// Initializes a new Context with the specified raw value. - /// - Parameter rawValue: The UInt32 raw value for the context flags. - public init(rawValue: UInt32) { self.rawValue = rawValue } - - /// Initializes a new Context with the specified raw value. - /// - Parameter rawValue: The Int32 raw value for the context flags. - init(rawValue: Int32) { self.rawValue = UInt32(rawValue) } - - /// No context flag. - public static let none = Context(rawValue: SECP256K1_CONTEXT_NONE) - - /// Creates a new secp256k1 context with the specified flags. - /// - Parameter context: The context flags to create a new secp256k1 context. - /// - Throws: Throws an error if the context creation or randomization fails. - /// - Returns: Returns an opaque pointer to the created context. - public static func create(_ context: Context = .none) throws -> OpaquePointer { - var randomBytes = SecureBytes(count: secp256k1.ByteDetails.count).bytes - guard let context = secp256k1_context_create(context.rawValue), - secp256k1_context_randomize(context, &randomBytes).boolValue else { - throw secp256k1Error.underlyingCryptoError - } - - return context - } - } -} - /// An extension to secp256k1 containing an enum for public key formats. public extension secp256k1 { /// Enum representing public key formats to be passed to `secp256k1_ec_pubkey_serialize`. @@ -61,8 +25,8 @@ public extension secp256k1 { /// The length of the public key in bytes, based on the format. public var length: Int { switch self { - case .compressed: return 33 - case .uncompressed: return 65 + case .compressed: return secp256k1.ByteLength.dimension + 1 + case .uncompressed: return 2 * secp256k1.ByteLength.dimension + 1 } } @@ -80,26 +44,22 @@ public extension secp256k1 { } } -/// An extension for secp256k1 containing nested enums for curve and byte details. +/// An extension for secp256k1 containing nested enum byte length details. extension secp256k1 { - /// An enum containing details about the secp256k1 curve. + /// An enum containing byte details about in secp256k1. @usableFromInline - enum CurveDetails { - /// The number of bytes in a coordinate of the secp256k1 curve. + enum ByteLength { + /// Number of bytes for one dimension of a secp256k1 coordinate. @inlinable - static var coordinateByteCount: Int { - 16 - } - } + static var dimension: Int { 32 } - /// An enum containing details about bytes in secp256k1. - @usableFromInline - enum ByteDetails { - /// The number of bytes in a secp256k1 object. + /// Number of bytes in a secp256k1 private key. @inlinable - static var count: Int { - 32 - } + static var privateKey: Int { 32 } + + /// Number of bytes in a secp256k1 signature. + @inlinable + static var signature: Int { 64 } } } @@ -151,7 +111,7 @@ extension secp256k1 { /// Backing initialization that creates a random secp256k1 private key for signing @usableFromInline init(format: secp256k1.Format = .compressed) throws { - let privateKey = SecureBytes(count: secp256k1.ByteDetails.count) + let privateKey = SecureBytes(count: secp256k1.ByteLength.privateKey) self.keyParity = 0 self.format = format self.privateBytes = privateKey @@ -322,7 +282,7 @@ extension secp256k1 { bytes privateBytes: inout SecureBytes, format: secp256k1.Format ) throws -> [UInt8] { - guard privateBytes.count == secp256k1.ByteDetails.count else { + guard privateBytes.count == secp256k1.ByteLength.privateKey else { throw secp256k1Error.incorrectKeySize } @@ -398,7 +358,7 @@ extension secp256k1 { let context = secp256k1.Context.rawRepresentation var pubKey = secp256k1_pubkey() var xonlyPubKey = secp256k1_xonly_pubkey() - var xonlyBytes = [UInt8](repeating: 0, count: secp256k1.ByteDetails.count) + var xonlyBytes = [UInt8](repeating: 0, count: secp256k1.ByteLength.privateKey) guard secp256k1_ec_pubkey_parse(context, &pubKey, publicBytes, format.length).boolValue, secp256k1_xonly_pubkey_from_pubkey(context, &xonlyPubKey, &keyParity, &pubKey).boolValue, diff --git a/Tests/zkpTests/secp256k1Tests.swift b/Tests/zkpTests/secp256k1Tests.swift index 9feb6e7..4c2d06b 100644 --- a/Tests/zkpTests/secp256k1Tests.swift +++ b/Tests/zkpTests/secp256k1Tests.swift @@ -266,12 +266,12 @@ final class secp256k1Tests: XCTestCase { var messageDigest = "We're all Satoshi Nakamoto and a bit of Harold Thomas Finney II.".data(using: .utf8)!.bytes var auxRand = try! "f50c8c99e39a82f125fa83186b5f2483f39fb0fb56269c755689313a177be6ea".bytes - let signature = try! privateKey.signature(message: &messageDigest, auxiliaryRand: &auxRand, strict: false) + let signature = try! privateKey.signature(message: &messageDigest, auxiliaryRand: &auxRand) // Test the verification of the signature output XCTAssertEqual(expectedSignature, String(bytes: signature.dataRepresentation.bytes)) XCTAssertTrue(privateKey.xonly.isValid(signature, for: &messageDigest)) - XCTAssertThrowsError(try throwKey.signature(message: &messageDigest, auxiliaryRand: &auxRand)) + XCTAssertThrowsError(try throwKey.signature(message: &messageDigest, auxiliaryRand: &auxRand, strict: true)) } func testSchnorrVerifying() { @@ -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) @@ -435,12 +435,29 @@ final class secp256k1Tests: XCTestCase { let privateKey1 = try! secp256k1.KeyAgreement.PrivateKey(dataRepresentation: privateBytes1) let privateKey2 = try! secp256k1.KeyAgreement.PrivateKey(dataRepresentation: privateBytes2) - let sharedSecret1 = try! privateKey1.sharedSecretFromKeyAgreement(with: privateKey2.publicKey) - let sharedSecret2 = try! privateKey2.sharedSecretFromKeyAgreement(with: privateKey1.publicKey) + let sharedSecret1 = try! privateKey1.sharedSecretFromKeyAgreement(with: privateKey2.publicKey, format: .uncompressed) + let sharedSecret2 = try! privateKey2.sharedSecretFromKeyAgreement(with: privateKey1.publicKey, format: .uncompressed) XCTAssertEqual(sharedSecret1.bytes, sharedSecret2.bytes) } + func testKeyAgreementHashFunction() { + let context = secp256k1.Context.rawRepresentation + let privateKey1 = try! secp256k1.KeyAgreement.PrivateKey() + let privateKey2 = try! secp256k1.KeyAgreement.PrivateKey() + + var pub = secp256k1_pubkey() + let sharedSecret1 = try! privateKey1.sharedSecretFromKeyAgreement(with: privateKey2.publicKey) + var sharedSecret2 = [UInt8](repeating: 0, count: 32) + + XCTAssertEqual(secp256k1_ec_pubkey_parse(context, &pub, privateKey1.publicKey.bytes, privateKey1.publicKey.bytes.count), 1) + XCTAssertEqual(secp256k1_ecdh(context, &sharedSecret2, &pub, privateKey2.baseKey.key.bytes, nil, nil), 1) + + let symmerticKey = SHA256.hash(data: sharedSecret1.bytes) + + XCTAssertEqual(symmerticKey.bytes, sharedSecret2) + } + func testKeyAgreementPublicKeyTweakAdd() { let privateSign1 = try! secp256k1.Signing.PrivateKey() let privateSign2 = try! secp256k1.Signing.PrivateKey() @@ -455,8 +472,11 @@ final class secp256k1Tests: XCTestCase { XCTAssertEqual(sharedSecret1.bytes, sharedSecret2.bytes) - let sharedSecretSign1 = try! secp256k1.Signing.PrivateKey(dataRepresentation: sharedSecret1.bytes) - let sharedSecretSign2 = try! secp256k1.Signing.PrivateKey(dataRepresentation: sharedSecret2.bytes) + let symmetricKey1 = SHA256.hash(data: sharedSecret1.bytes) + let symmetricKey2 = SHA256.hash(data: sharedSecret2.bytes) + + let sharedSecretSign1 = try! secp256k1.Signing.PrivateKey(dataRepresentation: symmetricKey1.bytes) + let sharedSecretSign2 = try! secp256k1.Signing.PrivateKey(dataRepresentation: symmetricKey2.bytes) let privateTweak1 = try! sharedSecretSign1.add(xonly: privateSign1.publicKey.xonly.bytes) let publicTweak2 = try! sharedSecretSign2.publicKey.add(privateSign1.publicKey.xonly.bytes) @@ -611,6 +631,7 @@ final class secp256k1Tests: XCTestCase { ("testZeroization", testZeroization), ("testPrivateKeyTweakAdd", testPrivateKeyTweakAdd), ("testKeyAgreement", testKeyAgreement), + ("testKeyAgreementHashFunction", testKeyAgreementHashFunction), ("testKeyAgreementPublicKeyTweakAdd", testKeyAgreementPublicKeyTweakAdd), ("testXonlyToPublicKey", testXonlyToPublicKey), ("testTapscript", testTapscript), diff --git a/bitrise.yml b/bitrise.yml index 5183838..a94206d 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -4,7 +4,7 @@ default_step_lib_source: 'https://github.com/bitrise-io/bitrise-steplib.git' project_type: other meta: bitrise.io: - stack: osx-xcode-14.2.x-ventura + stack: osx-xcode-14.3.x-ventura machine_type_id: g2-m1.4core trigger_map: - push_branch: main