Skip to content

Commit 3d4d2cf

Browse files
committed
Simplify musig2 nonce generation API (no functional changes)
We copied secp256k1's API and passed a private key (which could be null) and a public key, it's cleaner to pass an Either<PrivateKey, PublicKey> and matches what we do in Eclair.
1 parent 74641a7 commit 3d4d2cf

File tree

2 files changed

+54
-15
lines changed

2 files changed

+54
-15
lines changed

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

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,22 +136,45 @@ public data class SecretNonce(internal val data: ByteVector) {
136136
* All optional arguments exist to enrich the quality of the randomness used, which is critical for security.
137137
*
138138
* @param sessionId unique session ID.
139-
* @param privateKey (optional) signer's private key.
140-
* @param publicKey signer's public key.
139+
* @param signingKey signer's private key or public key.
141140
* @param message (optional) message that will be signed, if already known.
142141
* @param keyAggCache (optional) key aggregation cache data from the signing session.
143142
* @param extraInput (optional) additional random data.
144143
* @return secret nonce and the corresponding public nonce.
145144
*/
146145
@JvmStatic
147-
public fun generate(sessionId: ByteVector32, privateKey: PrivateKey?, publicKey: PublicKey, message: ByteVector32?, keyAggCache: KeyAggCache?, extraInput: ByteVector32?): Pair<SecretNonce, IndividualNonce> {
148-
privateKey?.let { require(it.publicKey() == publicKey) { "if the private key is provided, it must match the public key" } }
146+
public fun generate(sessionId: ByteVector32, signingKey: Either<PrivateKey, PublicKey>, message: ByteVector32?, keyAggCache: KeyAggCache?, extraInput: ByteVector32?): Pair<SecretNonce, IndividualNonce> {
147+
val (privateKey, publicKey) = when (signingKey) {
148+
is Either.Left -> Pair(signingKey.value, signingKey.value.publicKey())
149+
is Either.Right -> Pair(null, signingKey.value)
150+
}
149151
val nonce = Secp256k1.musigNonceGen(sessionId.toByteArray(), privateKey?.value?.toByteArray(), publicKey.value.toByteArray(), message?.toByteArray(), keyAggCache?.toByteArray(), extraInput?.toByteArray())
150152
val secretNonce = SecretNonce(nonce.copyOfRange(0, Secp256k1.MUSIG2_SECRET_NONCE_SIZE))
151153
val publicNonce = IndividualNonce(nonce.copyOfRange(Secp256k1.MUSIG2_SECRET_NONCE_SIZE, Secp256k1.MUSIG2_SECRET_NONCE_SIZE + Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE))
152154
return Pair(secretNonce, publicNonce)
153155
}
154156

157+
/**
158+
* Generate a secret nonce to be used in a musig2 signing session.
159+
* This nonce must never be persisted or reused across signing sessions.
160+
* All optional arguments exist to enrich the quality of the randomness used, which is critical for security.
161+
*
162+
* @param sessionId unique session ID.
163+
* @param privateKey (optional) signer's private key.
164+
* @param publicKey signer's public key.
165+
* @param message (optional) message that will be signed, if already known.
166+
* @param keyAggCache (optional) key aggregation cache data from the signing session.
167+
* @param extraInput (optional) additional random data.
168+
* @return secret nonce and the corresponding public nonce.
169+
*/
170+
@Deprecated("Use generate() with an Either<PrivateKey, PublicKey> instead", ReplaceWith("generate()"), DeprecationLevel.WARNING)
171+
@JvmStatic
172+
public fun generate(sessionId: ByteVector32, privateKey: PrivateKey?, publicKey: PublicKey, message: ByteVector32?, keyAggCache: KeyAggCache?, extraInput: ByteVector32?): Pair<SecretNonce, IndividualNonce> {
173+
privateKey?.let { require(it.publicKey() == publicKey) { "if the private key is provided, it must match the public key" } }
174+
val signingKey = privateKey?.let { Either.Left(it) } ?: Either.Right(publicKey)
175+
return generate(sessionId, signingKey, message, keyAggCache, extraInput)
176+
}
177+
155178
/**
156179
* Alternative counter-based method for generating nonce.
157180
* This nonce must never be persisted or reused across signing sessions.
@@ -236,7 +259,20 @@ public object Musig2 {
236259
*/
237260
@JvmStatic
238261
public fun aggregateKeys(publicKeys: List<PublicKey>): XonlyPublicKey = KeyAggCache.create(publicKeys).first
239-
262+
263+
/**
264+
* @param sessionId a random, unique session ID.
265+
* @param signingKey signer's private key or public key
266+
* @param publicKeys public keys of all participants: callers must verify that all public keys are valid.
267+
* @param message (optional) message that will be signed, if already known.
268+
* @param extraInput (optional) additional random data.
269+
*/
270+
@JvmStatic
271+
public fun generateNonce(sessionId: ByteVector32, signingKey: Either<PrivateKey, PublicKey>, publicKeys: List<PublicKey>, message: ByteVector32?, extraInput: ByteVector32?): Pair<SecretNonce, IndividualNonce> {
272+
val (_, keyAggCache) = KeyAggCache.create(publicKeys)
273+
return SecretNonce.generate(sessionId, signingKey, message, keyAggCache, extraInput)
274+
}
275+
240276
/**
241277
* @param sessionId a random, unique session ID.
242278
* @param privateKey signer's private key
@@ -245,10 +281,12 @@ public object Musig2 {
245281
* @param message (optional) message that will be signed, if already known.
246282
* @param extraInput (optional) additional random data.
247283
*/
284+
@Deprecated("Use generateNonce() with an Either<PrivateKey, PublicKey> instead", ReplaceWith("generateNonce()"), DeprecationLevel.WARNING)
248285
@JvmStatic
249286
public fun generateNonce(sessionId: ByteVector32, privateKey: PrivateKey?, publicKey: PublicKey, publicKeys: List<PublicKey>, message: ByteVector32?, extraInput: ByteVector32?): Pair<SecretNonce, IndividualNonce> {
250-
val (_, keyAggCache) = KeyAggCache.create(publicKeys)
251-
return SecretNonce.generate(sessionId, privateKey, publicKey, message, keyAggCache, extraInput)
287+
privateKey?.let { require(it.publicKey() == publicKey) { "if the private key is provided, it must match the public key" } }
288+
val signingKey = privateKey?.let { Either.Left(it) } ?: Either.Right(publicKey)
289+
return generateNonce(sessionId, signingKey, publicKeys, message, extraInput)
252290
}
253291

254292
/**

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

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

33
import fr.acinq.bitcoin.*
4+
import fr.acinq.bitcoin.utils.Either
45
import fr.acinq.secp256k1.Hex
56
import kotlinx.serialization.json.*
67
import kotlin.random.Random
@@ -76,7 +77,7 @@ class Musig2TestsCommon {
7677
val expectedPubnonce = IndividualNonce(it.jsonObject["expected_pubnonce"]!!.jsonPrimitive.content)
7778
// secp256k1 only supports signing 32-byte messages (when provided), which excludes some of the test vectors.
7879
if (msg == null || msg.size == 32) {
79-
val (secnonce, pubnonce) = SecretNonce.generate(randprime, sk, pk, msg?.byteVector32(), keyagg, extraInput?.byteVector32())
80+
val (secnonce, pubnonce) = SecretNonce.generate(randprime, sk?.let { Either.Left(it) } ?: Either.Right(pk), msg?.byteVector32(), keyagg, extraInput?.byteVector32())
8081
assertEquals(expectedPubnonce, pubnonce)
8182
assertEquals(expectedSecnonce, secnonce)
8283
}
@@ -278,7 +279,7 @@ class Musig2TestsCommon {
278279
}
279280

280281
// Generate secret nonces for each participant.
281-
val nonces = privkeys.map { SecretNonce.generate(Random.Default.nextBytes(32).byteVector32(), it, it.publicKey(), message = null, keyAggCache, extraInput = null) }
282+
val nonces = privkeys.map { SecretNonce.generate(Random.Default.nextBytes(32).byteVector32(), Either.Left(it), message = null, keyAggCache, extraInput = null) }
282283
val secnonces = nonces.map { it.first }
283284
val pubnonces = nonces.map { it.second }
284285

@@ -316,8 +317,8 @@ class Musig2TestsCommon {
316317

317318
// The first step of a musig2 signing session is to exchange nonces.
318319
// If participants are disconnected before the end of the signing session, they must start again with fresh nonces.
319-
val aliceNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), alicePrivKey, alicePrivKey.publicKey(), listOf(alicePubKey, bobPubKey), null, null)
320-
val bobNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), bobPrivKey, bobPrivKey.publicKey(), listOf(alicePubKey, bobPubKey), null, null)
320+
val aliceNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), Either.Left(alicePrivKey), listOf(alicePubKey, bobPubKey), null, null)
321+
val bobNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), Either.Left(bobPrivKey), listOf(alicePubKey, bobPubKey), null, null)
321322

322323
// Once they have each other's public nonce, they can produce partial signatures.
323324
val publicNonces = listOf(aliceNonce.second, bobNonce.second)
@@ -354,8 +355,8 @@ class Musig2TestsCommon {
354355
val tx = Transaction(2, listOf(), listOf(TxOut(10_000.sat(), Script.pay2tr(commonPubKey))), 0)
355356
val spendingTx = Transaction(2, listOf(TxIn(OutPoint(tx, 0), sequence = 0)), listOf(TxOut(10_000.sat(), Script.pay2wpkh(alicePubKey))), 0)
356357

357-
val aliceNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), alicePrivKey, alicePrivKey.publicKey(),listOf(alicePubKey, bobPubKey), null, null)
358-
val bobNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), bobPrivKey, bobPrivKey.publicKey(), listOf(alicePubKey, bobPubKey), null, null)
358+
val aliceNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), Either.Left(alicePrivKey), listOf(alicePubKey, bobPubKey), null, null)
359+
val bobNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), Either.Left(bobPrivKey), listOf(alicePubKey, bobPubKey), null, null)
359360
val publicNonces = listOf(aliceNonce.second, bobNonce.second)
360361

361362
val aliceSig = Musig2.signTaprootInput(alicePrivKey, spendingTx, 0, listOf(tx.txOut[0]), listOf(alicePubKey, bobPubKey), aliceNonce.first, publicNonces, scriptTree = null).right
@@ -411,8 +412,8 @@ class Musig2TestsCommon {
411412
)
412413
// The first step of a musig2 signing session is to exchange nonces.
413414
// If participants are disconnected before the end of the signing session, they must start again with fresh nonces.
414-
val userNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), userPrivateKey, userPrivateKey.publicKey(), listOf(userPublicKey, serverPublicKey), null, null)
415-
val serverNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), serverPrivateKey, serverPrivateKey.publicKey(), listOf(userPublicKey, serverPublicKey), null, null)
415+
val userNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), Either.Left(userPrivateKey), listOf(userPublicKey, serverPublicKey), null, null)
416+
val serverNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), Either.Left(serverPrivateKey), listOf(userPublicKey, serverPublicKey), null, null)
416417

417418
// Once they have each other's public nonce, they can produce partial signatures.
418419
val publicNonces = listOf(userNonce.second, serverNonce.second)

0 commit comments

Comments
 (0)