Verify musig2 secret nonces (#108)
* Verify musig2 secret nonces Trying to generate a musig2 partial signature with a secret nonce that was generated with a public key that does not match the signing key's public key will trigger secp256k1's illegal callback (which calls abort()) and crash the application. => Here we verify that the secret nonce matches the signing key before we call secp256k1_musig_partial_sign(). The verification method is a bit hackish (we extract the public key from the secret nonce blob) because secp256k1 does not export the methods we need to do this cleanly.
This commit is contained in:
@@ -217,6 +217,26 @@ public interface Secp256k1 {
|
||||
*/
|
||||
public fun musigNonceProcess(aggnonce: ByteArray, msg32: ByteArray, keyaggCache: ByteArray): ByteArray
|
||||
|
||||
/**
|
||||
* Check that a secret nonce was generated with a public key that matches the private key used for signing.
|
||||
* @param secretnonce secret nonce.
|
||||
* @param pubkey public key for the private key that will be used, with the secret nonce, to generate a partial signature.
|
||||
* @return false if the secret nonce does not match the public key.
|
||||
*/
|
||||
public fun musigNonceValidate(secretnonce: ByteArray, pubkey: ByteArray): Boolean {
|
||||
if (secretnonce.size != MUSIG2_SECRET_NONCE_SIZE) return false
|
||||
if (pubkey.size != 33 && pubkey.size != 65) return false
|
||||
val pk = Secp256k1.pubkeyParse(pubkey)
|
||||
// this is a bit hackish but the secp256k1 library does not export methods to do this cleanly
|
||||
val x = secretnonce.copyOfRange(68, 68 + 32)
|
||||
x.reverse()
|
||||
val y = secretnonce.copyOfRange(68 + 32, 68 + 32 + 32)
|
||||
y.reverse()
|
||||
val pkx = pk.copyOfRange(1, 1 + 32)
|
||||
val pky = pk.copyOfRange(33, 33 + 32)
|
||||
return x.contentEquals(pkx) && y.contentEquals(pky)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a partial signature.
|
||||
*
|
||||
|
||||
@@ -81,7 +81,7 @@ public object Secp256k1Native : Secp256k1 {
|
||||
return serialized.readBytes(Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE)
|
||||
}
|
||||
|
||||
private fun DeferScope.toNat(bytes: ByteArray): CPointer<UByteVar> {
|
||||
private fun DeferScope.toNat(bytes: ByteArray): CPointer<UByteVar> {
|
||||
val ubytes = bytes.asUByteArray()
|
||||
val pinned = ubytes.pin()
|
||||
this.defer { pinned.unpin() }
|
||||
@@ -112,7 +112,7 @@ public object Secp256k1Native : Secp256k1 {
|
||||
}
|
||||
|
||||
public override fun signatureNormalize(sig: ByteArray): Pair<ByteArray, Boolean> {
|
||||
require(sig.size >= 64){ "invalid signature ${Hex.encode(sig)}" }
|
||||
require(sig.size >= 64) { "invalid signature ${Hex.encode(sig)}" }
|
||||
memScoped {
|
||||
val nSig = allocSignature(sig)
|
||||
val isHighS = secp256k1_ecdsa_signature_normalize(ctx, nSig.ptr, nSig.ptr)
|
||||
@@ -307,7 +307,16 @@ public object Secp256k1Native : Secp256k1 {
|
||||
memcpy(n.ptr, toNat(it), Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong())
|
||||
n
|
||||
}
|
||||
secp256k1_musig_nonce_gen(ctx, secnonce.ptr, pubnonce.ptr, toNat(sessionId32), privkey?.let { toNat(it) }, nPubkey.ptr, msg32?.let { toNat(it) },nKeyAggCache?.ptr, extraInput32?.let { toNat(it) }).requireSuccess("secp256k1_musig_nonce_gen() failed")
|
||||
secp256k1_musig_nonce_gen(
|
||||
ctx,
|
||||
secnonce.ptr,
|
||||
pubnonce.ptr,
|
||||
toNat(sessionId32),
|
||||
privkey?.let { toNat(it) },
|
||||
nPubkey.ptr,
|
||||
msg32?.let { toNat(it) },
|
||||
nKeyAggCache?.ptr,
|
||||
extraInput32?.let { toNat(it) }).requireSuccess("secp256k1_musig_nonce_gen() failed")
|
||||
val nPubnonce = allocArray<UByteVar>(Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE)
|
||||
secp256k1_musig_pubnonce_serialize(ctx, nPubnonce, pubnonce.ptr).requireSuccess("secp256k1_musig_pubnonce_serialize failed")
|
||||
secnonce.ptr.readBytes(Secp256k1.MUSIG2_SECRET_NONCE_SIZE) + nPubnonce.readBytes(Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE)
|
||||
@@ -339,7 +348,7 @@ public object Secp256k1Native : Secp256k1 {
|
||||
n
|
||||
}
|
||||
secp256k1_musig_pubkey_agg(ctx, combined.ptr, nKeyAggCache?.ptr, nPubkeys.toCValues(), pubkeys.size.convert()).requireSuccess("secp256k1_musig_nonce_agg() failed")
|
||||
val agg = serializeXonlyPubkey(combined)
|
||||
val agg = serializeXonlyPubkey(combined)
|
||||
keyaggCache?.let { blob -> nKeyAggCache?.let { memcpy(toNat(blob), it.ptr, Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong()) } }
|
||||
return agg
|
||||
}
|
||||
@@ -386,13 +395,14 @@ public object Secp256k1Native : Secp256k1 {
|
||||
memcpy(toNat(session), nSession.ptr, Secp256k1.MUSIG2_PUBLIC_SESSION_SIZE.toULong())
|
||||
return session
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun musigPartialSign(secnonce: ByteArray, privkey: ByteArray, keyaggCache: ByteArray, session: ByteArray): ByteArray {
|
||||
require(secnonce.size == Secp256k1.MUSIG2_SECRET_NONCE_SIZE)
|
||||
require(privkey.size == 32)
|
||||
require(keyaggCache.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
|
||||
require(session.size == Secp256k1.MUSIG2_PUBLIC_SESSION_SIZE)
|
||||
require(musigNonceValidate(secnonce, pubkeyCreate(privkey)))
|
||||
|
||||
memScoped {
|
||||
val nSecnonce = alloc<secp256k1_musig_secnonce>()
|
||||
|
||||
Reference in New Issue
Block a user