Skip to content

Commit d98b426

Browse files
committed
Export method to generate musig2 nonce from non-repeating counter
1 parent 45fe463 commit d98b426

File tree

2 files changed

+32
-7
lines changed

2 files changed

+32
-7
lines changed

src/commonMain/kotlin/fr/acinq/bitcoin/crypto/musig2/Musig2.kt

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import fr.acinq.bitcoin.utils.flatMap
66
import fr.acinq.bitcoin.utils.getOrElse
77
import fr.acinq.secp256k1.Hex
88
import fr.acinq.secp256k1.Secp256k1
9-
import kotlin.jvm.JvmOverloads
109
import kotlin.jvm.JvmStatic
1110

1211
/**
@@ -152,6 +151,26 @@ public data class SecretNonce(internal val data: ByteVector) {
152151
val publicNonce = IndividualNonce(nonce.copyOfRange(Secp256k1.MUSIG2_SECRET_NONCE_SIZE, Secp256k1.MUSIG2_SECRET_NONCE_SIZE + Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE))
153152
return Pair(secretNonce, publicNonce)
154153
}
154+
155+
/**
156+
* Alternative counter-based method for generating nonce.
157+
* This nonce must never be persisted or reused across signing sessions.
158+
* All optional arguments exist to enrich the quality of the randomness used, which is critical for security.
159+
*
160+
* @param nonRepeatingCounter non-repeating counter that must never be reused with the same private key.
161+
* @param privateKey signer's private key.
162+
* @param message (optional) message that will be signed, if already known.
163+
* @param keyAggCache (optional) key aggregation cache data from the signing session.
164+
* @param extraInput (optional) additional random data.
165+
* @return secret nonce and the corresponding public nonce.
166+
*/
167+
@JvmStatic
168+
public fun generateWithCounter(nonRepeatingCounter: ULong, privateKey: PrivateKey, message: ByteVector32?, keyAggCache: KeyAggCache?, extraInput: ByteVector32?): Pair<SecretNonce, IndividualNonce> {
169+
val nonce = Secp256k1.musigNonceGenCounter(nonRepeatingCounter, privateKey.value.toByteArray(), message?.toByteArray(), keyAggCache?.toByteArray(), extraInput?.toByteArray())
170+
val secretNonce = SecretNonce(nonce.copyOfRange(0, Secp256k1.MUSIG2_SECRET_NONCE_SIZE))
171+
val publicNonce = IndividualNonce(nonce.copyOfRange(Secp256k1.MUSIG2_SECRET_NONCE_SIZE, Secp256k1.MUSIG2_SECRET_NONCE_SIZE + Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE))
172+
return Pair(secretNonce, publicNonce)
173+
}
155174
}
156175
}
157176

@@ -295,7 +314,7 @@ public object Musig2 {
295314
* @return true if the partial signature is valid.
296315
*/
297316
@JvmStatic
298-
public fun verifyTaprootSignature(
317+
public fun verify(
299318
partialSig: ByteVector32,
300319
nonce: IndividualNonce,
301320
publicKey: PublicKey,

src/commonTest/kotlin/fr/acinq/bitcoin/crypto/musig2/Musig2TestsCommon.kt

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package fr.acinq.bitcoin.crypto.musig2
22

33
import fr.acinq.bitcoin.*
4-
import fr.acinq.bitcoin.reference.TransactionTestsCommon
54
import fr.acinq.secp256k1.Hex
65
import kotlinx.serialization.json.*
76
import kotlin.random.Random
@@ -83,6 +82,13 @@ class Musig2TestsCommon {
8382
}
8483
}
8584

85+
@Test
86+
fun `generate secret nonce from counter`() {
87+
val privateKey = PrivateKey.fromHex("EEC1CB7D1B7254C5CAB0D9C61AB02E643D464A59FE6C96A7EFE871F07C5AEF54")
88+
val nonce = SecretNonce.generateWithCounter(0UL, privateKey, null, null, null)
89+
assertEquals(ByteVector.fromHex("03A5B9B6907942EACDDA49A366016EC2E62404A1BF4AB6D4DB82067BC3ADF086D7033205DB9EB34D5C7CE02848CAC68A83ED73E3883477F563F23CE9A11A7721EC64"), nonce.second.data)
90+
}
91+
8692
@Test
8793
fun `aggregate nonces`() {
8894
val tests = TestHelpers.readResourceAsJson("musig2/nonce_agg_vectors.json")
@@ -301,11 +307,11 @@ class Musig2TestsCommon {
301307

302308
val aliceSig = Musig2.signTaprootInput(alicePrivKey, spendingTx, 0, listOf(tx.txOut[0]), listOf(alicePubKey, bobPubKey), aliceNonce.first, publicNonces, scriptTree = null).right
303309
assertNotNull(aliceSig)
304-
assertTrue(Musig2.verifyTaprootSignature(aliceSig, aliceNonce.second, alicePubKey, spendingTx, 0, listOf(tx.txOut[0]), listOf(alicePubKey, bobPubKey), publicNonces, scriptTree = null))
310+
assertTrue(Musig2.verify(aliceSig, aliceNonce.second, alicePubKey, spendingTx, 0, listOf(tx.txOut[0]), listOf(alicePubKey, bobPubKey), publicNonces, scriptTree = null))
305311

306312
val bobSig = Musig2.signTaprootInput(bobPrivKey, spendingTx, 0, listOf(tx.txOut[0]), listOf(alicePubKey, bobPubKey), bobNonce.first, publicNonces, scriptTree = null).right
307313
assertNotNull(bobSig)
308-
assertTrue(Musig2.verifyTaprootSignature(bobSig, bobNonce.second, bobPubKey, spendingTx, 0, listOf(tx.txOut[0]), listOf(alicePubKey, bobPubKey), publicNonces, scriptTree = null))
314+
assertTrue(Musig2.verify(bobSig, bobNonce.second, bobPubKey, spendingTx, 0, listOf(tx.txOut[0]), listOf(alicePubKey, bobPubKey), publicNonces, scriptTree = null))
309315

310316
// Once they have each other's partial signature, they can aggregate them into a valid signature.
311317
val aggregateSig = Musig2.aggregateTaprootSignatures(listOf(aliceSig, bobSig), spendingTx, 0, listOf(tx.txOut[0]), listOf(alicePubKey, bobPubKey), publicNonces, scriptTree = null).right
@@ -359,11 +365,11 @@ class Musig2TestsCommon {
359365
val publicNonces = listOf(userNonce.second, serverNonce.second)
360366
val userSig = Musig2.signTaprootInput(userPrivateKey, tx, 0, swapInTx.txOut, listOf(userPublicKey, serverPublicKey), userNonce.first, publicNonces, scriptTree).right
361367
assertNotNull(userSig)
362-
assertTrue(Musig2.verifyTaprootSignature(userSig, userNonce.second, userPublicKey, tx, 0, swapInTx.txOut, listOf(userPublicKey, serverPublicKey), publicNonces, scriptTree))
368+
assertTrue(Musig2.verify(userSig, userNonce.second, userPublicKey, tx, 0, swapInTx.txOut, listOf(userPublicKey, serverPublicKey), publicNonces, scriptTree))
363369

364370
val serverSig = Musig2.signTaprootInput(serverPrivateKey, tx, 0, swapInTx.txOut, listOf(userPublicKey, serverPublicKey), serverNonce.first, publicNonces, scriptTree).right
365371
assertNotNull(serverSig)
366-
assertTrue(Musig2.verifyTaprootSignature(serverSig, serverNonce.second, serverPublicKey, tx, 0, swapInTx.txOut, listOf(userPublicKey, serverPublicKey), publicNonces, scriptTree))
372+
assertTrue(Musig2.verify(serverSig, serverNonce.second, serverPublicKey, tx, 0, swapInTx.txOut, listOf(userPublicKey, serverPublicKey), publicNonces, scriptTree))
367373

368374
// Once they have each other's partial signature, they can aggregate them into a valid signature.
369375
val aggregateSig = Musig2.aggregateTaprootSignatures(listOf(userSig, serverSig), tx, 0, swapInTx.txOut, listOf(userPublicKey, serverPublicKey), publicNonces, scriptTree).right

0 commit comments

Comments
 (0)