From 6e18455a63a8f4a4cd8a628d1d499640a9f5bc88 Mon Sep 17 00:00:00 2001 From: Gruruya Date: Tue, 11 Apr 2023 04:17:03 -0400 Subject: [PATCH] Enable and expose Schnorrsig (#44) * Enable Schnorrsig module in wrapper The extrakeys module is a dependency for Schnorrsig, so that's enabled as well. * Add {.bycopy.} pragma * Add Schnorrsig interface to `abi.nim` Multikey interface is a dependency the for schnorrsig, so it was added as well. * Add tests for Schnorr signing * Fix schnorr magic const declaration on 1.6 and below * Remove unnecessary {.bycopy.} pragmas Done under the impression that {.bycopy.} is not necessary when only passing the object to C via ptr. * Make SkKeyPair a wrapper around secp256k1_keypair * Add more helper procs for new SkKeyPair * Small fixes * Re-order * Rework patch. Implement Schnorr signing and undo breaking changes. * Reduce code duplication * Fix type * Remove accidental extra indentation * Add `default` {.error.} proc for SkSchnorrSignature * Remove extra test * Add from/to raw/hex * Comments * Add low-level test for `secp256k1_keypair` * Fix errors on Nim 1.2 * Comment * Allow passing a `Rng`/`FoolproofRng` to `signSchnorr` for improved security * Comments * Correct `noncefp` to be a pointer in `extraparams` object * Remove unneeded {.bycopy.} Co-authored-by: Jacek Sieka * Don't check the RNG for Schnorr sig using private key requirements. * Add comment detailing that `signSchnorr` without an `rng` is discouraged * Remove non-`rng` signSchnorr variant from tests * Rename `signSchnorr` without `rng` to `signSchnorrUnsafe` * Unify `schnorrSig` implementations and add `array[32, bytes]` variant * Fix on Nim 1.2 * Make `signSchnorr` accept `Opt[array[32, byte]]` rather than `[array[32,byte]]` * Remove unused template param * Inline `signSchnorr Rng` procs * Remove `nimble.lock`, was breaking tests on Nim >1.6 Was causing `Error: cannot open file: stew/byteutils` * Correct template parameter naming * Consistently apply {.noinit.} pragma * `{.noinit.}` random byte array * Revert "`{.noinit.}` random byte array" This reverts commit a3f99817d9627880974be1ae81014fa17d14f2db. * Correct template pragmas * Explicitly declare `noncefp` as `nil` * Create and export `xonly_pubkey` wrapping type * Complete implementation of `SkXOnlyPublicKey` * Correct comment * Add tests for 'SkXOnlyPublicKey` * Correct conversion proc name * Correct conversion proc name cont. --------- Co-authored-by: Jacek Sieka --- nimble.lock | 25 --- secp256k1.nim | 156 ++++++++++++++++- secp256k1/abi.nim | 215 ++++++++++++++++++++++++ secp256k1_wrapper/gen.sh | 2 +- secp256k1_wrapper/libsecp256k1-config.h | 4 +- tests/test_secp256k1.nim | 15 ++ tests/test_secp256k1_abi.nim | 14 ++ 7 files changed, 401 insertions(+), 30 deletions(-) delete mode 100644 nimble.lock diff --git a/nimble.lock b/nimble.lock deleted file mode 100644 index 3e67944..0000000 --- a/nimble.lock +++ /dev/null @@ -1,25 +0,0 @@ -{ - "version": 1, - "packages": { - "nimcrypto": { - "version": "0.5.4", - "vcsRevision": "a5742a9a214ac33f91615f3862c7b099aec43b00", - "url": "https://github.com/cheatfate/nimcrypto", - "downloadMethod": "git", - "dependencies": [], - "checksums": { - "sha1": "f76c87707cd4e96355b8bb6ef27e7f8b0aac1e08" - } - }, - "stew": { - "version": "0.1.0", - "vcsRevision": "cdb1f213d073fd2ecbdaf35a866417657da9294c", - "url": "https://github.com/status-im/nim-stew", - "downloadMethod": "git", - "dependencies": [], - "checksums": { - "sha1": "3dd9e4ec78d906b59b6f9bcf74f5f8fa6b5c24fe" - } - } - } -} \ No newline at end of file diff --git a/secp256k1.nim b/secp256k1.nim index b1e4bb5..f3c7ddd 100644 --- a/secp256k1.nim +++ b/secp256k1.nim @@ -54,12 +54,18 @@ const SkRawRecoverableSignatureSize* = 65 ## Size of recoverable signature in octets (bytes) + SkRawSchnorrSignatureSize* = 64 + ## Size of Schnorr signature in octets (bytes) + SkRawPublicKeySize* = 65 ## Size of uncompressed public key in octets (bytes) SkRawCompressedPublicKeySize* = 33 ## Size of compressed public key in octets (bytes) + SkRawXOnlyPublicKeySize* = 32 + ## Size of x-only public key in octets (bytes) + SkMessageSize* = 32 ## Size of message that can be signed @@ -74,6 +80,10 @@ type ## Representation of public key. data: secp256k1_pubkey + SkXOnlyPublicKey* {.requiresInit.} = object + ## Representation of public key that only reveals the x-coordinate. + data: secp256k1_xonly_pubkey + SkSecretKey* {.requiresInit.} = object ## Representation of secret key. data: array[SkRawSecretKeySize, byte] @@ -91,6 +101,10 @@ type ## Representation of recoverable signature. data: secp256k1_ecdsa_recoverable_signature + SkSchnorrSignature* {.requiresInit.} = object + ## Representation of a Schnorr signature. + data: array[SkRawSchnorrSignatureSize, byte] + SkContext = object ## Representation of Secp256k1 context object. context: ptr secp256k1_context @@ -305,6 +319,43 @@ func toRawCompressed*(pubkey: SkPublicKey): array[SkRawCompressedPublicKeySize, func toHexCompressed*(pubkey: SkPublicKey): string = toHex(toRawCompressed(pubkey)) +func toXOnly*(pk: SkPublicKey): SkXOnlyPublicKey = + ## Gets a pubkey that reveals only the x-coordinate on the curve. + var data {.noinit.}: secp256k1_xonly_pubkey + let res = secp256k1_xonly_pubkey_from_pubkey( + secp256k1_context_no_precomp, addr data, nil, unsafeAddr pk.data) + doAssert res == 1, "cannot get xonly pubkey from pubkey, key invalid?" + + SkXOnlyPublicKey(data: data) + +func fromRaw*(T: type SkXOnlyPublicKey, data: openArray[byte]): SkResult[T] = + ## Initialize Secp256k1 `x-only public key` ``key`` from raw binary + ## representation ``data``. + if len(data) != SkRawXOnlyPublicKeySize: + return err(static( + &"secp: x-only public key must be {SkRawXOnlyPublicKeySize} bytes")) + + var key {.noinit.}: secp256k1_xonly_pubkey + if secp256k1_xonly_pubkey_parse( + secp256k1_context_no_precomp, addr key, data.baseAddr) != 1: + return err("secp: cannot parse x-only public key") + + ok(SkXOnlyPublicKey(data: key)) + +func fromHex*(T: type SkXOnlyPublicKey, data: string): SkResult[T] = + ## Initialize Secp256k1 `x-only public key` ``key`` from hexadecimal string + ## representation ``data``. + T.fromRaw(? seq[byte].fromHex(data)) + +func toRaw*(pubkey: SkXOnlyPublicKey): array[SkRawXOnlyPublicKeySize, byte] = + ## Serialize Secp256k1 `x-only public key` ``key`` to raw form. + let res = secp256k1_xonly_pubkey_serialize( + secp256k1_context_no_precomp, result.baseAddr, unsafeAddr pubkey.data) + doAssert res == 1, "Can't fail, per documentation" + +func toHex*(pubkey: SkXOnlyPublicKey): string = + toHex(toRaw(pubkey)) + func fromRaw*(T: type SkSignature, data: openArray[byte]): SkResult[T] = ## Load compact signature from data if data.len() < SkRawSignatureSize: @@ -392,13 +443,31 @@ func toRaw*(sig: SkRecoverableSignature): array[SkRawRecoverableSignatureSize, b var recid = cint(0) let res = secp256k1_ecdsa_recoverable_signature_serialize_compact( secp256k1_context_no_precomp, result.baseAddr, addr recid, unsafeAddr sig.data) - doAssert res == 1, "can't fail, per documentation" + doAssert res == 1, "Can't fail, per documentation" result[64] = byte(recid) func toHex*(sig: SkRecoverableSignature): string = toHex(toRaw(sig)) +func fromRaw*(T: type SkSchnorrSignature, data: openArray[byte]): SkResult[T] = + ## Load Schnorr `signature` from data as created by `toRaw`. + if len(data) < SkRawSchnorrSignatureSize: + return err(static(&"secp: raw schnorr signature should be {SkRawSchnorrSignatureSize} bytes")) + + ok(T(data: toArray(64, data.toOpenArray(0, SkRawSchnorrSignatureSize - 1)))) + +func fromHex*(T: type SkSchnorrSignature, data: string): SkResult[T] = + ## Initialize Schnorr `signature` from hexadecimal string representation ``data``. + T.fromRaw(? seq[byte].fromHex(data)) + +func toRaw*(sig: SkSchnorrSignature): array[SkRawSchnorrSignatureSize, byte] = + ## Serialize Schnorr `signature` ``sig`` to raw binary form. + sig.data + +func toHex*(sig: SkSchnorrSignature): string = + toHex(toRaw(sig)) + proc random*(T: type SkKeyPair, rng: Rng): SkResult[T] = ## Generates new random key pair. let seckey = ? SkSecretKey.random(rng) @@ -423,10 +492,18 @@ func `==`*(lhs, rhs: SkSignature): bool = ## Compare Secp256k1 `signature` objects for equality. CT.isEqual(lhs.toRaw(), rhs.toRaw()) +func `==`*(lhs, rhs: SkXOnlyPublicKey): bool = + ## Compare Secp256k1 `x-only public key` objects for equality. + CT.isEqual(lhs.toRaw(), rhs.toRaw()) + func `==`*(lhs, rhs: SkRecoverableSignature): bool = ## Compare Secp256k1 `recoverable signature` objects for equality. CT.isEqual(lhs.toRaw(), rhs.toRaw()) +func `==`*(lhs, rhs: SkSchnorrSignature): bool = + ## Compare Schnorr signature objects for equality. + CT.isEqual(lhs.toRaw(), rhs.toRaw()) + func sign*(key: SkSecretKey, msg: SkMessage): SkSignature = ## Sign message `msg` using private key `key` and return signature object. ## It is recommended that `msg` is produced by hashing the input data to @@ -445,10 +522,83 @@ func signRecoverable*(key: SkSecretKey, msg: SkMessage): SkRecoverableSignature doAssert res == 1, "cannot create recoverable signature, key invalid?" SkRecoverableSignature(data: data) +template signSchnorrImpl(signMsg: untyped): untyped = + var kp {.noinit, inject.}: secp256k1_keypair + let res = secp256k1_keypair_create( + getContext(), addr kp, key.data.baseAddr) + doAssert res == 1, "cannot create keypair, key invalid?" + + var data {.noinit, inject.}: array[SkRawSchnorrSignatureSize, byte] + let res2 = signMsg + doAssert res2 == 1, "cannot create signature, key invalid?" + SkSchnorrSignature(data: data) + +func signSchnorr*(key: SkSecretKey, msg: SkMessage, randbytes: Opt[array[32, byte]]): SkSchnorrSignature = + ## Sign message `msg` using private key `key` with the Schnorr signature algorithm and return signature object. + ## `randbytes` should be an array of 32 freshly generated random bytes. + let aux_rand32 = if randbytes.isSome: randbytes[].baseAddr else: nil + signSchnorrImpl( + secp256k1_schnorrsig_sign32( + getContext(), data.baseAddr, msg.baseAddr, addr kp, aux_rand32)) + +func signSchnorr*(key: SkSecretKey, msg: openArray[byte], randbytes: Opt[array[32, byte]]): SkSchnorrSignature = + ## Sign message `msg` using private key `key` with the Schnorr signature algorithm and return signature object. + ## `randbytes` should be an array of 32 freshly generated random bytes. + let aux_rand32 = if randbytes.isSome: randbytes[].baseAddr else: nil + let extraparams = secp256k1_schnorrsig_extraparams(magic: SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC, noncefp: nil, ndata: aux_rand32) + signSchnorrImpl( + secp256k1_schnorrsig_sign_custom( + getContext(), data.baseAddr, msg.baseAddr, csize_t msg.len, addr kp, unsafeAddr extraparams)) + +template signSchnorrRngImpl(): untyped = + var randbytes: array[32, byte] + if rng(randbytes): + return ok(signSchnorr(key, msg, Opt.some randbytes)) + return err("secp: cannot get random bytes for signature") + +proc signSchnorr*(key: SkSecretKey, msg: SkMessage, rng: Rng): SkResult[SkSchnorrSignature] {.inline.} = + ## Sign message `msg` using private key `key` with the Schnorr signature algorithm and return signature object. + ## Uses ``rng`` to generate 32-bytes of random data for signature generation. + signSchnorrRngImpl() + +proc signSchnorr*(key: SkSecretKey, msg: openArray[byte], rng: Rng): SkResult[SkSchnorrSignature] {.inline.} = + ## Sign message `msg` using private key `key` with the Schnorr signature algorithm and return signature object. + ## Uses ``rng`` to generate 32-bytes of random data for signature generation. + signSchnorrRngImpl() + +template signSchnorrFoolproofRngImpl(): untyped = + var randbytes: array[32, byte] + rng(randbytes) + return signSchnorr(key, msg, Opt.some randbytes) + +proc signSchnorr*(key: SkSecretKey, msg: SkMessage, rng: FoolproofRng): SkSchnorrSignature {.inline.} = + ## Sign message `msg` using private key `key` with the Schnorr signature algorithm and return signature object. + ## Uses ``rng`` to generate 32-bytes of random data for signature generation. + signSchnorrFoolproofRngImpl() + +proc signSchnorr*(key: SkSecretKey, msg: openArray[byte], rng: FoolproofRng): SkSchnorrSignature {.inline.} = + ## Sign message `msg` using private key `key` with the Schnorr signature algorithm and return signature object. + ## Uses ``rng`` to generate 32-bytes of random data for signature generation. + signSchnorrFoolproofRngImpl() + func verify*(sig: SkSignature, msg: SkMessage, key: SkPublicKey): bool = secp256k1_ecdsa_verify( getContext(), unsafeAddr sig.data, msg.baseAddr, unsafeAddr key.data) == 1 +func verify*(sig: SkSchnorrSignature, msg: SkMessage, pubkey: SkXOnlyPublicKey): bool = + secp256k1_schnorrsig_verify( + getContext(), unsafeAddr sig.data[0], msg.baseAddr, csize_t SkMessageSize, unsafeAddr pubkey.data) == 1 + +func verify*(sig: SkSchnorrSignature, msg: openArray[byte], pubkey: SkXOnlyPublicKey): bool = + secp256k1_schnorrsig_verify( + getContext(), unsafeAddr sig.data[0], msg.baseAddr, csize_t msg.len, unsafeAddr pubkey.data) == 1 + +template verify*(sig: SkSchnorrSignature, msg: SkMessage, pubkey: SkPublicKey): bool = + verify(sig, msg, pubkey.toXOnly) + +template verify*(sig: SkSchnorrSignature, msg: openArray[byte], pubkey: SkPublicKey): bool = + verify(sig, msg, pubkey.toXOnly) + func recover*(sig: SkRecoverableSignature, msg: SkMessage): SkResult[SkPublicKey] = var data {.noinit.}: secp256k1_pubkey if secp256k1_ecdsa_recover( @@ -497,7 +647,7 @@ func clear*(v: var SkEcdhRawSecret) = burnMem(v.data) func `$`*( - v: SkPublicKey | SkSecretKey | SkSignature | SkRecoverableSignature): string = + v: SkPublicKey | SkSecretKey | SkXOnlyPublicKey | SkSignature | SkRecoverableSignature | SkSchnorrSignature): string = toHex(v) func fromBytes*(T: type SkMessage, data: openArray[byte]): SkResult[SkMessage] = @@ -510,8 +660,10 @@ func fromBytes*(T: type SkMessage, data: openArray[byte]): SkResult[SkMessage] = # TODO replace `requiresInit` with a pragma that does the expected thing proc default*(T: type SkPublicKey): T {.error: "loophole".} proc default*(T: type SkSecretKey): T {.error: "loophole".} +proc default*(T: type SkXOnlyPublicKey): T {.error: "loophole".} proc default*(T: type SkSignature): T {.error: "loophole".} proc default*(T: type SkRecoverableSignature): T {.error: "loophole".} +proc default*(T: type SkSchnorrSignature): T {.error: "loophole".} proc default*(T: type SkEcdhSecret): T {.error: "loophole".} proc default*(T: type SkEcdhRawSecret): T {.error: "loophole".} diff --git a/secp256k1/abi.nim b/secp256k1/abi.nim index eba645e..f129cf3 100644 --- a/secp256k1/abi.nim +++ b/secp256k1/abi.nim @@ -326,3 +326,218 @@ proc secp256k1_ecdh_raw*(ctx: ptr secp256k1_context; output32: ptr byte; ## initialized public key ## privkey: a 32-byte scalar with which to multiply the point ## + +## Multikey interface follows + +type + secp256k1_xonly_pubkey* = object + ## Opaque data structure that holds a parsed and valid "x-only" public key. + ## An x-only pubkey encodes a point whose Y coordinate is even. It is + ## serialized using only its X coordinate (32 bytes). See BIP-340 for more + ## information about x-only pubkeys. + ## + ## The exact representation of data inside is implementation defined and not + ## guaranteed to be portable between different platforms or versions. It is + ## however guaranteed to be 64 bytes in size, and can be safely copied/moved. + ## If you need to convert to a format suitable for storage, transmission, use + ## use secp256k1_xonly_pubkey_serialize and secp256k1_xonly_pubkey_parse. To + ## compare keys, use secp256k1_xonly_pubkey_cmp. + ## + data*: array[64, uint8] + + secp256k1_keypair* = object + ## Opaque data structure that holds a keypair consisting of a secret and a + ## public key. + ## + ## The exact representation of data inside is implementation defined and not + ## guaranteed to be portable between different platforms or versions. It is + ## however guaranteed to be 96 bytes in size, and can be safely copied/moved. + ## + data*: array[96, uint8] + +proc secp256k1_xonly_pubkey_parse*(ctx: ptr secp256k1_context; + pubkey: ptr secp256k1_xonly_pubkey; + input32: ptr byte): cint {.secp.} + ## Parse a 32-byte sequence into a xonly_pubkey object. + ## + ## Returns: 1 if the public key was fully valid. + ## 0 if the public key could not be parsed or is invalid. + ## + ## Args: ctx: a secp256k1 context object. + ## Out: pubkey: pointer to a pubkey object. If 1 is returned, it is set to a + ## parsed version of input. If not, it's set to an invalid value. + ## In: input32: pointer to a serialized xonly_pubkey. + ## + +proc secp256k1_xonly_pubkey_serialize*(ctx: ptr secp256k1_context; + output32: ptr byte; + pubkey: ptr secp256k1_xonly_pubkey): cint {.secp.} + ## Serialize an xonly_pubkey object into a 32-byte sequence. + ## + ## Returns: 1 always. + ## + ## Args: ctx: a secp256k1 context object. + ## Out: output32: a pointer to a 32-byte array to place the serialized key in. + ## In: pubkey: a pointer to a secp256k1_xonly_pubkey containing an initialized public key. + ## + +proc secp256k1_xonly_pubkey_from_pubkey*(ctx: ptr secp256k1_context; + xonly_pubkey: ptr secp256k1_xonly_pubkey; + pk_parity: ptr cint; + pubkey: ptr secp256k1_pubkey): cint {.secp.} + ## Converts a secp256k1_pubkey into a secp256k1_xonly_pubkey. + ## + ## Returns: 1 always. + ## + ## Args: ctx: pointer to a context object. + ## Out: xonly_pubkey: pointer to an x-only public key object for placing the converted public key. + ## pk_parity: Ignored if NULL. Otherwise, pointer to an integer that + ## will be set to 1 if the point encoded by xonly_pubkey is + ## the negation of the pubkey and set to 0 otherwise. + ## In: pubkey: pointer to a public key that is converted. + ## + +proc secp256k1_xonly_pubkey_tweak_add*(ctx: ptr secp256k1_context; + output_pubkey: ptr secp256k1_pubkey; + internal_pubkey: ptr secp256k1_xonly_pubkey; + tweak32: ptr byte): cint {.secp.} + +proc secp256k1_xonly_pubkey_tweak_add_check*(ctx: ptr secp256k1_context; + tweaked_pubkey32: ptr byte; + tweaked_pk_parity: cint; + internal_pubkey: ptr secp256k1_xonly_pubkey; + tweak32: ptr byte): cint {.secp.} + +proc secp256k1_keypair_create*(ctx: ptr secp256k1_context; + keypair: ptr secp256k1_keypair; + seckey: ptr byte): cint {.secp.} + ## Compute the keypair for a secret key. + ## + ## Returns: 1: secret was valid, keypair is ready to use + ## 0: secret was invalid, try again with a different secret + ## Args: ctx: pointer to a context object, initialized for signing. + ## Out: keypair: pointer to the created keypair. + ## In: seckey: pointer to a 32-byte secret key. + ## + +proc secp256k1_keypair_sec*(ctx: ptr secp256k1_context; + seckey: ptr byte; + keypair: ptr secp256k1_keypair): cint {.secp.} + +proc secp256k1_keypair_pub*(ctx: ptr secp256k1_context; + pubkey: ptr secp256k1_pubkey; + keypair: ptr secp256k1_keypair): cint {.secp.} + +proc secp256k1_keypair_xonly_pub*(ctx: ptr secp256k1_context; + pubkey: ptr secp256k1_xonly_pubkey; + pk_parity: ptr cint; + keypair: ptr secp256k1_keypair): cint {.secp.} + +proc secp256k1_keypair_xonly_tweak_add*(ctx: ptr secp256k1_context; + keypair: ptr secp256k1_keypair; + tweak32: ptr byte): cint {.secp.} + +## Schnorrsig interface follows + +type + secp256k1_nonce_function_hardened* = object + nonce32*: ptr byte + msg*: ptr byte + msglen*: csize_t + key32*: ptr byte + xonly_pk32*: ptr byte + algo*: ptr byte + algolen*: csize_t + data*: pointer + +const + SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC* = [ 0xda'u8 , 0x6f, 0xb3, 0x8c ] + +type + secp256k1_schnorrsig_extraparams* = object + ## Data structure that contains additional arguments for schnorrsig_sign_custom. + ## + ## Members: + ## magic: set to SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC at initialization + ## and has no other function than making sure the object is + ## initialized. + ## noncefp: pointer to a nonce generation function. If NULL, + ## secp256k1_nonce_function_bip340 is used + ## ndata: pointer to arbitrary data used by the nonce generation function + ## (can be NULL). If it is non-NULL and + ## secp256k1_nonce_function_bip340 is used, then ndata must be a + ## pointer to 32-byte auxiliary randomness as per BIP-340. + ## + magic*: array[4, uint8] + noncefp*: ptr secp256k1_nonce_function_hardened + ndata*: pointer + +proc secp256k1_schnorrsig_sign32*(ctx: ptr secp256k1_context; + sig64: ptr byte; + msg32: ptr byte; + keypair: ptr secp256k1_keypair; + aux_rand32: ptr byte): cint {.secp.} + ## Create a Schnorr signature. + ## + ## Does _not_ strictly follow BIP-340 because it does not verify the resulting + ## signature. Instead, you can manually use secp256k1_schnorrsig_verify and + ## abort if it fails. + ## + ## This function only signs 32-byte messages. If you have messages of a + ## different size (or the same size but without a context-specific tag + ## prefix), it is recommended to create a 32-byte message hash with + ## secp256k1_tagged_sha256 and then sign the hash. Tagged hashing allows + ## providing an context-specific tag for domain separation. This prevents + ## signatures from being valid in multiple contexts by accident. + ## + ## Returns 1 on success, 0 on failure. + ## Args: ctx: pointer to a context object, initialized for signing. + ## Out: sig64: pointer to a 64-byte array to store the serialized signature. + ## In: msg32: the 32-byte message being signed. + ## keypair: pointer to an initialized keypair. + ## aux_rand32: 32 bytes of fresh randomness. While recommended to provide + ## this, it is only supplemental to security and can be NULL. A + ## NULL argument is treated the same as an all-zero one. See + ## BIP-340 "Default Signing" for a full explanation of this + ## argument and for guidance if randomness is expensive. + ## + +proc secp256k1_schnorrsig_sign_custom*( + ctx: ptr secp256k1_context; + sig64: ptr byte; + msg: ptr byte; + msglen: csize_t; + keypair: ptr secp256k1_keypair; + extraparams: ptr secp256k1_schnorrsig_extraparams): cint {.secp.} + ## Create a Schnorr signature with a more flexible API. + ## + ## Same arguments as secp256k1_schnorrsig_sign except that it allows signing + ## variable length messages and accepts a pointer to an extraparams object that + ## allows customizing signing by passing additional arguments. + ## + ## Creates the same signatures as schnorrsig_sign if msglen is 32 and the + ## extraparams.ndata is the same as aux_rand32. + ## + ## In: msg: the message being signed. Can only be NULL if msglen is 0. + ## msglen: length of the message + ## extraparams: pointer to a extraparams object (can be NULL) + ## + +proc secp256k1_schnorrsig_verify*( + ctx: ptr secp256k1_context; + sig64: ptr byte; + msg: ptr byte; + msglen: csize_t; + pubkey: ptr secp256k1_xonly_pubkey): cint {.secp.} + ## Verify a Schnorr signature. + ## + ## Returns: 1: correct signature + ## 0: incorrect signature + ## Args: ctx: a secp256k1 context object, initialized for verification. + ## In: sig64: pointer to the 64-byte signature to verify. + ## msg: the message being verified. Can only be NULL if msglen is 0. + ## msglen: length of the message + ## pubkey: pointer to an x-only public key to verify with (cannot be NULL) + ## + +var secp256k1_nonce_function_bip340*: secp256k1_nonce_function_hardened diff --git a/secp256k1_wrapper/gen.sh b/secp256k1_wrapper/gen.sh index 4857169..3e2c09e 100755 --- a/secp256k1_wrapper/gen.sh +++ b/secp256k1_wrapper/gen.sh @@ -5,7 +5,7 @@ set -e THIS_DIR=$(dirname "$0") cd $THIS_DIR/secp256k1 ./autogen.sh -./configure --enable-module-ecdh --enable-module-recovery --enable-experimental +./configure --enable-module-ecdh --enable-module-recovery --enable-module-extrakeys --enable-module-schnorrsig --enable-experimental make src/ecmult_static_context.h cd - diff --git a/secp256k1_wrapper/libsecp256k1-config.h b/secp256k1_wrapper/libsecp256k1-config.h index 9e5b63f..f5db934 100644 --- a/secp256k1_wrapper/libsecp256k1-config.h +++ b/secp256k1_wrapper/libsecp256k1-config.h @@ -18,13 +18,13 @@ #define ENABLE_MODULE_ECDH 1 /* Define this symbol to enable the extrakeys module */ -/* #undef ENABLE_MODULE_EXTRAKEYS */ +#define ENABLE_MODULE_EXTRAKEYS 1 /* Define this symbol to enable the ECDSA pubkey recovery module */ #define ENABLE_MODULE_RECOVERY 1 /* Define this symbol to enable the schnorrsig module */ -/* #undef ENABLE_MODULE_SCHNORRSIG */ +#define ENABLE_MODULE_SCHNORRSIG 1 /* Define to 1 if you have the header file. */ #define HAVE_DLFCN_H 1 diff --git a/tests/test_secp256k1.nim b/tests/test_secp256k1.nim index bc86660..7d727df 100644 --- a/tests/test_secp256k1.nim +++ b/tests/test_secp256k1.nim @@ -15,6 +15,13 @@ const 1'u8, 0, 0, 0, 0, 0, 0, 0, 1'u8, 0, 0, 0, 0, 0, 0, 0, ]) + msg2 = array[40, byte]([ + 0'u8, 0, 0, 0, 0, 0, 0, 0, + 0'u8, 0, 0, 0, 0, 0, 0, 0, + 0'u8, 0, 0, 0, 0, 0, 0, 0, + 0'u8, 0, 0, 0, 0, 0, 0, 0, + 0'u8, 0, 0, 0, 0, 0, 0, 0, + ]) proc workingRng(data: var openArray[byte]): bool = data[0] += 1 @@ -34,6 +41,8 @@ suite "secp256k1": SkPublicKey.fromRaw(pk.toRaw())[].toHex() == pk.toHex() SkPublicKey.fromRaw(pk.toRawCompressed())[].toHex() == pk.toHex() SkPublicKey.fromHex(pk.toHex())[].toHex() == pk.toHex() + SkXOnlyPublicKey.fromRaw(pk.toXOnly.toRaw())[].toHex() == pk.toXOnly.toHex() + SkXOnlyPublicKey.fromHex(pk.toXOnly.toHex())[].toHex() == pk.toXOnly.toHex() SkSecretKey.random(brokenRng).isErr test "Signatures": @@ -43,6 +52,9 @@ suite "secp256k1": otherPk = SkSecretKey.random(workingRng)[].toPublicKey() sig = sign(sk, msg0) sig2 = signRecoverable(sk, msg0) + sig3 = signSchnorr(sk, msg0, workingRng)[] + sig4 = signSchnorr(sk, cast[array[SkMessageSize, byte]](msg0), workingRng)[] + sig5 = signSchnorr(sk, msg2, workingRng)[] check: verify(sig, msg0, pk) @@ -51,6 +63,9 @@ suite "secp256k1": recover(sig2, msg0)[] == pk recover(sig2, msg1)[] != pk SkSignature.fromDer(sig.toDer())[].toHex() == sig.toHex() + verify(sig3, msg0, pk) + sig3 == sig4 + verify(sig5, msg2, pk) test "Message": check: diff --git a/tests/test_secp256k1_abi.nim b/tests/test_secp256k1_abi.nim index 8a67f7a..bfea18e 100644 --- a/tests/test_secp256k1_abi.nim +++ b/tests/test_secp256k1_abi.nim @@ -30,3 +30,17 @@ suite "ABI tests": addr aPublicKey, cast[ptr byte](addr bSecretKey[0])) == 1 check(data1 == data2) + + test "C-side keypairs should be unchanged when serialized": + var keypair: secp256k1_keypair + var secretKey: array[32, uint8] + var publicKey: secp256k1_xonly_pubkey + var parsed: array[32, byte] + var reflectedPublicKey: secp256k1_xonly_pubkey + secretKey[31] = 1'u8 + let ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN or SECP256K1_CONTEXT_VERIFY) + check secp256k1_keypair_create(ctx, addr keypair, cast[ptr byte](addr secretKey[0])) == 1 + check secp256k1_keypair_xonly_pub(ctx, addr publicKey, nil, addr keypair) == 1 + check secp256k1_xonly_pubkey_serialize(ctx, addr parsed[0], addr publicKey) == 1 + check secp256k1_xonly_pubkey_parse(ctx, addr reflectedPublicKey, addr parsed[0]) == 1 + check publicKey == reflectedPublicKey