Skip to content

Commit

Permalink
BIP340 improvements (#343)
Browse files Browse the repository at this point in the history
* BIP340 improvements

* README cleanup
  • Loading branch information
csjones committed Apr 24, 2023
1 parent a6c3b48 commit 37cc6e5
Show file tree
Hide file tree
Showing 10 changed files with 306 additions and 100 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions Sources/zkp/ECDH.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
38 changes: 31 additions & 7 deletions Sources/zkp/ECDSA.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<D: DataProtocol>(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
}

Expand All @@ -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<D: DataProtocol>(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
}

Expand All @@ -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
}

Expand All @@ -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
}

Expand All @@ -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<D: Digest>(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),
Expand Down Expand Up @@ -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<D: Digest>(_ 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
}
}

Expand Down
13 changes: 13 additions & 0 deletions Sources/zkp/MuSig2.swift
Original file line number Diff line number Diff line change
@@ -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 {}
12 changes: 8 additions & 4 deletions Sources/zkp/Recovery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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<D: DataProtocol>(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
Expand Down Expand Up @@ -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<D: Digest>(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),
Expand Down
10 changes: 9 additions & 1 deletion Sources/zkp/SHA256.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,19 @@ public enum SHA256 {
/// - Throws: An error if the tagged hash computation fails.
/// - Returns: The computed digest.
public static func taggedHash<D: DataProtocol>(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
}

Expand Down
73 changes: 59 additions & 14 deletions Sources/zkp/Schnorr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -51,17 +56,20 @@ 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.
///
/// - 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<D: ContiguousBytes>(rawRepresentation data: D, format: secp256k1.Format = .compressed) throws {
self.baseKey = try PrivateKeyImplementation(rawRepresentation: data, format: format)
public init<D: ContiguousBytes>(
rawRepresentation data: D,
strict: Bool = true
) throws {
self.baseKey = try PrivateKeyImplementation(rawRepresentation: data, strict: strict)
}

/// Determines if two private keys are equal.
Expand All @@ -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

Expand All @@ -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.
Expand All @@ -103,6 +116,16 @@ public extension secp256k1.Schnorr {
public init<D: ContiguousBytes>(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
}
}
}

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}
}
Loading

0 comments on commit 37cc6e5

Please sign in to comment.