Rework patch. Implement Schnorr signing and undo breaking changes.

This commit is contained in:
Gruruya 2023-04-03 23:42:03 -04:00
parent 7a36c249f8
commit e75a1e2c82
4 changed files with 69 additions and 223 deletions

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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