diff --git a/secp256k1.nim b/secp256k1.nim index 5d4433f..61f1615 100644 --- a/secp256k1.nim +++ b/secp256k1.nim @@ -54,6 +54,9 @@ 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) @@ -80,7 +83,8 @@ type SkKeyPair* = object ## Representation of private/public keys pair. - data: secp256k1_keypair + seckey*: SkSecretKey + pubkey*: SkPublicKey SkSignature* {.requiresInit.} = object ## Representation of non-recoverable signature. @@ -90,6 +94,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 @@ -391,58 +399,29 @@ 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 pubkey*(kp: SkKeyPair): SkPublicKey = - var key {.noinit.}: secp256k1_pubkey - let res = secp256k1_keypair_pub(secp256k1_context_no_precomp, addr key, addr kp.data) - doAssert res == 1, "Can't fail, per documentation" - SkPublicKey(data: key) - -func seckey*(kp: SkKeyPair): SkSecretKey = - var key {.noinit.}: array[SkRawSecretKeySize, byte] - let res = secp256k1_keypair_sec(secp256k1_context_no_precomp, key.baseAddr, addr kp.data) - doAssert res == 1, "Can't fail, per documentation" - SkSecretKey(data: key) - -func `pubkey=`*(kp: var SkKeyPair, sk: SkPublicKey) {.deprecated: "Set the seckey instead".} = discard - -func `seckey=`*(kp: var SkKeyPair, sk: SkSecretKey) = - var newKp: SkKeyPair - let res = secp256k1_keypair_create(getContext(), addr newKp.data, sk.data.baseAddr) - if res == 1: - kp = newKp - else: - #TODO: Raise an exception? Old behaviour would just set the secret key to an invalid key. - discard - proc random*(T: type SkKeyPair, rng: Rng): SkResult[T] = ## Generates new random key pair. let seckey = ? SkSecretKey.random(rng) - var keypair {.noinit.}: secp256k1_keypair - let res = secp256k1_keypair_create(getContext(), addr keypair, addr seckey.data[0]) - doAssert res == 1, "Can't fail, only fails if secret key is invalid but it was freshly generated." - ok(T( - data: keypair + seckey: seckey, + pubkey: seckey.toPublicKey() )) proc random*(T: type SkKeyPair, rng: FoolproofRng): T = ## Generates new random key pair. let seckey = SkSecretKey.random(rng) - var keypair {.noinit.}: secp256k1_keypair - let res = secp256k1_keypair_create(getContext(), addr keypair, addr seckey.data[0]) - doAssert res == 1, "Can't fail, only fails if secret key is invalid but it was freshly generated." - T( - data: keypair + seckey: seckey, + pubkey: seckey.toPublicKey() ) func `==`*(lhs, rhs: SkPublicKey): bool = @@ -457,11 +436,6 @@ func `==`*(lhs, rhs: SkRecoverableSignature): bool = ## Compare Secp256k1 `recoverable signature` objects for equality. CT.isEqual(lhs.toRaw(), rhs.toRaw()) -func `==`*(lhs, rhs: SkKeyPair): bool = - ## Compare Secp256k1 `keypair` objects for equality. - lhs.pubkey == rhs.pubkey and - lhs.seckey == rhs.seckey - 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 @@ -480,10 +454,54 @@ func signRecoverable*(key: SkSecretKey, msg: SkMessage): SkRecoverableSignature doAssert res == 1, "cannot create recoverable signature, key invalid?" SkRecoverableSignature(data: data) +func signSchnorr*(key: SkSecretKey, msg: SkMessage): SkSchnorrSignature = + ## Sign message `msg` using private key `key` and return signature object. + var kp: secp256k1_keypair + let res = secp256k1_keypair_create( + getContext(), addr kp, key.data.baseAddr) + doAssert res == 1, "cannot create keypair, key invalid?" + + var data {.noinit.}: array[SkRawSchnorrSignatureSize, byte] + let res2 = secp256k1_schnorrsig_sign32( + getContext(), data.baseAddr, msg.baseAddr, addr kp, nil) + doAssert res2 == 1, "cannot create signature, key invalid?" + SkSchnorrSignature(data: data) + +func signSchnorr*(key: SkSecretKey, msg: openArray[byte]): SkSchnorrSignature = + ## Sign message `msg` using private key `key` and return signature object. + var kp: secp256k1_keypair + let res = secp256k1_keypair_create( + getContext(), addr kp, key.data.baseAddr) + doAssert res == 1, "cannot create keypair, key invalid?" + + var data {.noinit.}: array[SkRawSchnorrSignatureSize, byte] + let res2 = secp256k1_schnorrsig_sign_custom( + getContext(), data.baseAddr, msg.baseAddr, csize_t msg.len, addr kp, nil) + doAssert res2 == 1, "cannot create signature, key invalid?" + SkSchnorrSignature(data: data) + 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 | openArray[byte], pubkey: SkPublicKey): bool = + var xonlyPk: secp256k1_xonly_pubkey + let res = secp256k1_xonly_pubkey_from_pubkey( + secp256k1_context_no_precomp, addr xonlyPk, nil, addr pubkey.data) + doAssert res == 1, "cannot get xonly pubkey from pubkey, key invalid?" + + secp256k1_schnorrsig_verify( + getContext(), addr sig.data[0], msg.baseAddr, csize_t SkMessageSize, addr xonlyPk) == 1 + +func verify*(sig: SkSchnorrSignature, msg: openArray[byte], pubkey: SkPublicKey): bool = + var xonlyPk: secp256k1_xonly_pubkey + let res = secp256k1_xonly_pubkey_from_pubkey( + secp256k1_context_no_precomp, addr xonlyPk, nil, addr pubkey.data) + doAssert res == 1, "cannot get xonly pubkey from pubkey, key invalid?" + + secp256k1_schnorrsig_verify( + getContext(), addr sig.data[0], msg.baseAddr, csize_t msg.len, addr xonlyPk) == 1 + func recover*(sig: SkRecoverableSignature, msg: SkMessage): SkResult[SkPublicKey] = var data {.noinit.}: secp256k1_pubkey if secp256k1_ecdsa_recover( @@ -519,12 +537,6 @@ func clear*(v: var SkSecretKey) = ## result in undefined behaviour or Defect burnMem(v.data) -func clear*(v: var SkKeyPair) = - ## Wipe and clear memory of Secp256k1 `keypair`. - ## After calling this function, the key is invalid and using it elsewhere will - ## result in undefined behaviour or Defect - burnMem(v.data) - func clear*(v: var SkEcdhSecret) = ## Wipe and clear memory of ECDH `shared secret`. ## After calling this function, the key is invalid and using it elsewhere will diff --git a/secp256k1/abi.nim b/secp256k1/abi.nim index 62c0caf..d35f500 100644 --- a/secp256k1/abi.nim +++ b/secp256k1/abi.nim @@ -358,28 +358,10 @@ type 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; @@ -401,55 +383,12 @@ 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.} - ## Tweak an x-only public key by adding the generator multiplied with tweak32 - ## to it. - ## - ## Note that the resulting point can not in general be represented by an x-only - ## pubkey because it may have an odd Y coordinate. Instead, the output_pubkey - ## is a normal secp256k1_pubkey. - ## - ## Returns: 0 if the arguments are invalid or the resulting public key would be - ## invalid (only when the tweak is the negation of the corresponding - ## secret key). 1 otherwise. - ## - ## Args: ctx: pointer to a context object initialized for verification. - ## Out: output_pubkey: pointer to a public key to store the result. Will be set - ## to an invalid value if this function returns 0. - ## In: internal_pubkey: pointer to an x-only pubkey to apply the tweak to. - ## tweak32: pointer to a 32-byte tweak. If the tweak is invalid - ## according to secp256k1_ec_seckey_verify, this function - ## returns 0. For uniformly random 32-byte arrays the - ## chance of being invalid is negligible (around 1 in 2^128). - ## 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.} - ## Checks that a tweaked pubkey is the result of calling - ## secp256k1_xonly_pubkey_tweak_add with internal_pubkey and tweak32. - ## - ## The tweaked pubkey is represented by its 32-byte x-only serialization and - ## its pk_parity, which can both be obtained by converting the result of - ## tweak_add to a secp256k1_xonly_pubkey. - ## - ## Note that this alone does _not_ verify that the tweaked pubkey is a - ## commitment. If the tweak is not chosen in a specific way, the tweaked pubkey - ## can easily be the result of a different internal_pubkey and tweak. - ## - ## Returns: 0 if the arguments are invalid or the tweaked pubkey is not the - ## result of tweaking the internal_pubkey with tweak32. 1 otherwise. - ## Args: ctx: pointer to a context object initialized for verification. - ## In: tweaked_pubkey32: pointer to a serialized xonly_pubkey. - ## tweaked_pk_parity: the parity of the tweaked pubkey (whose serialization - ## is passed in as tweaked_pubkey32). This must match the - ## pk_parity value that is returned when calling - ## secp256k1_xonly_pubkey with the tweaked pubkey, or - ## this function will fail. - ## internal_pubkey: pointer to an x-only public key object to apply the tweak to. - ## tweak32: pointer to a 32-byte tweak. - ## proc secp256k1_keypair_create*(ctx: ptr secp256k1_context; keypair: ptr secp256k1_keypair; @@ -466,97 +405,24 @@ proc secp256k1_keypair_create*(ctx: ptr secp256k1_context; proc secp256k1_keypair_sec*(ctx: ptr secp256k1_context; seckey: ptr byte; keypair: ptr secp256k1_keypair): cint {.secp.} - ## Get the secret key from a keypair. - ## - ## Returns: 1 always. - ## Args: ctx: pointer to a context object. - ## Out: seckey: pointer to a 32-byte buffer for the secret key. - ## In: keypair: pointer to a keypair. - ## proc secp256k1_keypair_pub*(ctx: ptr secp256k1_context; pubkey: ptr secp256k1_pubkey; keypair: ptr secp256k1_keypair): cint {.secp.} - ## Get the public key from a keypair. - ## - ## Returns: 1 always. - ## Args: ctx: pointer to a context object. - ## Out: pubkey: pointer to a pubkey object. If 1 is returned, it is set to - ## the keypair public key. If not, it's set to an invalid value. - ## In: keypair: pointer to a keypair. - ## proc secp256k1_keypair_xonly_pub*(ctx: ptr secp256k1_context; pubkey: ptr secp256k1_xonly_pubkey; pk_parity: ptr cint; keypair: ptr secp256k1_keypair): cint {.secp.} - ## Get the x-only public key from a keypair. - ## - ## This is the same as calling secp256k1_keypair_pub and then - ## secp256k1_xonly_pubkey_from_pubkey. - ## - ## Returns: 1 always. - ## Args: ctx: pointer to a context object. - ## Out: pubkey: pointer to an xonly_pubkey object. If 1 is returned, it is set - ## to the keypair public key after converting it to an - ## xonly_pubkey. If not, it's set to an invalid value. - ## pk_parity: Ignored if NULL. Otherwise, pointer to an integer that will be set to the - ## pk_parity argument of secp256k1_xonly_pubkey_from_pubkey. - ## In: keypair: pointer to a keypair. - ## proc secp256k1_keypair_xonly_tweak_add*(ctx: ptr secp256k1_context; keypair: ptr secp256k1_keypair; tweak32: ptr byte): cint {.secp.} - ## Tweak a keypair by adding tweak32 to the secret key and updating the public - ## key accordingly. - ## - ## Calling this function and then secp256k1_keypair_pub results in the same - ## public key as calling secp256k1_keypair_xonly_pub and then - ## secp256k1_xonly_pubkey_tweak_add. - ## - ## Returns: 0 if the arguments are invalid or the resulting keypair would be - ## invalid (only when the tweak is the negation of the keypair's - ## secret key). 1 otherwise. - ## - ## Args: ctx: pointer to a context object initialized for verification. - ## In/Out: keypair: pointer to a keypair to apply the tweak to. Will be set to - ## an invalid value if this function returns 0. - ## In: tweak32: pointer to a 32-byte tweak. If the tweak is invalid according - ## to secp256k1_ec_seckey_verify, this function returns 0. For - ## uniformly random 32-byte arrays the chance of being invalid - ## is negligible (around 1 in 2^128). - ## ## Schnorrsig interface follows type secp256k1_nonce_function_hardened* {.bycopy.} = object - ## A pointer to a function to deterministically generate a nonce. - ## - ## Same as secp256k1_nonce function with the exception of accepting an - ## additional pubkey argument and not requiring an attempt argument. The pubkey - ## argument can protect signature schemes with key-prefixed challenge hash - ## inputs against reusing the nonce when signing with the wrong precomputed - ## pubkey. - ## - ## Returns: 1 if a nonce was successfully generated. 0 will cause signing to - ## return an error. - ## Out: nonce32: pointer to a 32-byte array to be filled by the function - ## In: msg: the message being verified. Is NULL if and only if msglen - ## is 0. - ## msglen: the length of the message - ## key32: pointer to a 32-byte secret key (will not be NULL) - ## xonly_pk32: the 32-byte serialized xonly pubkey corresponding to key32 - ## (will not be NULL) - ## algo: pointer to an array describing the signature - ## algorithm (will not be NULL) - ## algolen: the length of the algo array - ## data: arbitrary data pointer that is passed through - ## - ## Except for test cases, this function should compute some cryptographic hash of - ## the message, the key, the pubkey, the algorithm description, and data. - ## nonce32*: ptr byte msg*: ptr byte msglen*: csize_t @@ -571,19 +437,6 @@ const 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*: secp256k1_nonce_function_hardened ndata*: pointer diff --git a/tests/test_secp256k1.nim b/tests/test_secp256k1.nim index 7794e22..9502634 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]([ + 1'u8, 0, 0, 0, 0, 0, 0, 0, + 1'u8, 0, 0, 0, 0, 0, 0, 0, + 1'u8, 0, 0, 0, 0, 0, 0, 0, + 1'u8, 0, 0, 0, 0, 0, 0, 0, + 1'u8, 0, 0, 0, 0, 0, 0, 0, + ]) proc workingRng(data: var openArray[byte]): bool = data[0] += 1 @@ -47,6 +54,8 @@ suite "secp256k1": otherPk = SkSecretKey.random(workingRng)[].toPublicKey() sig = sign(sk, msg0) sig2 = signRecoverable(sk, msg0) + sig3 = signSchnorr(sk, msg0) + sig4 = signSchnorr(sk, msg2) check: verify(sig, msg0, pk) @@ -55,6 +64,8 @@ suite "secp256k1": recover(sig2, msg0)[] == pk recover(sig2, msg1)[] != pk SkSignature.fromDer(sig.toDer())[].toHex() == sig.toHex() + verify(sig3, msg0, pk) + verify(sig4, msg2, pk) test "Message": check: diff --git a/tests/test_secp256k1_abi.nim b/tests/test_secp256k1_abi.nim index 0ba2fec..8a67f7a 100644 --- a/tests/test_secp256k1_abi.nim +++ b/tests/test_secp256k1_abi.nim @@ -30,33 +30,3 @@ suite "ABI tests": addr aPublicKey, cast[ptr byte](addr bSecretKey[0])) == 1 check(data1 == data2) - - test "Schnorr signatures": - var keypair: secp256k1_keypair - var secretKey: array[32, uint8] - var publicKey: secp256k1_xonly_pubkey - var data: array[32, byte] - var sig: array[64, byte] - var sig2: array[64, byte] - 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_schnorrsig_sign32(ctx, addr sig[0], addr data[0], addr keypair, nil) == 1 - check secp256k1_schnorrsig_sign_custom(ctx, addr sig2[0], addr data[0], csize_t data.len, addr keypair, nil) == 1 - check sig == sig2 - check secp256k1_schnorrsig_verify(ctx, addr sig[0], addr data[0], csize_t data.len, addr publicKey) == 1 - - test "Multikeys 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