diff --git a/src/commonMain/kotlin/fr/acinq/bitcoin/crypto/musig2/Musig2.kt b/src/commonMain/kotlin/fr/acinq/bitcoin/crypto/musig2/Musig2.kt index 1e5b90f..7802f15 100644 --- a/src/commonMain/kotlin/fr/acinq/bitcoin/crypto/musig2/Musig2.kt +++ b/src/commonMain/kotlin/fr/acinq/bitcoin/crypto/musig2/Musig2.kt @@ -136,22 +136,45 @@ public data class SecretNonce(internal val data: ByteVector) { * All optional arguments exist to enrich the quality of the randomness used, which is critical for security. * * @param sessionId unique session ID. - * @param privateKey (optional) signer's private key. - * @param publicKey signer's public key. + * @param signingKey signer's private key or public key. * @param message (optional) message that will be signed, if already known. * @param keyAggCache (optional) key aggregation cache data from the signing session. * @param extraInput (optional) additional random data. * @return secret nonce and the corresponding public nonce. */ @JvmStatic - public fun generate(sessionId: ByteVector32, privateKey: PrivateKey?, publicKey: PublicKey, message: ByteVector32?, keyAggCache: KeyAggCache?, extraInput: ByteVector32?): Pair { - privateKey?.let { require(it.publicKey() == publicKey) { "if the private key is provided, it must match the public key" } } + public fun generate(sessionId: ByteVector32, signingKey: Either, message: ByteVector32?, keyAggCache: KeyAggCache?, extraInput: ByteVector32?): Pair { + val (privateKey, publicKey) = when (signingKey) { + is Either.Left -> Pair(signingKey.value, signingKey.value.publicKey()) + is Either.Right -> Pair(null, signingKey.value) + } val nonce = Secp256k1.musigNonceGen(sessionId.toByteArray(), privateKey?.value?.toByteArray(), publicKey.value.toByteArray(), message?.toByteArray(), keyAggCache?.toByteArray(), extraInput?.toByteArray()) val secretNonce = SecretNonce(nonce.copyOfRange(0, Secp256k1.MUSIG2_SECRET_NONCE_SIZE)) val publicNonce = IndividualNonce(nonce.copyOfRange(Secp256k1.MUSIG2_SECRET_NONCE_SIZE, Secp256k1.MUSIG2_SECRET_NONCE_SIZE + Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE)) return Pair(secretNonce, publicNonce) } + /** + * Generate a secret nonce to be used in a musig2 signing session. + * This nonce must never be persisted or reused across signing sessions. + * All optional arguments exist to enrich the quality of the randomness used, which is critical for security. + * + * @param sessionId unique session ID. + * @param privateKey (optional) signer's private key. + * @param publicKey signer's public key. + * @param message (optional) message that will be signed, if already known. + * @param keyAggCache (optional) key aggregation cache data from the signing session. + * @param extraInput (optional) additional random data. + * @return secret nonce and the corresponding public nonce. + */ + @Deprecated("Use generate() with an Either instead", ReplaceWith("generate()"), DeprecationLevel.WARNING) + @JvmStatic + public fun generate(sessionId: ByteVector32, privateKey: PrivateKey?, publicKey: PublicKey, message: ByteVector32?, keyAggCache: KeyAggCache?, extraInput: ByteVector32?): Pair { + privateKey?.let { require(it.publicKey() == publicKey) { "if the private key is provided, it must match the public key" } } + val signingKey = privateKey?.let { Either.Left(it) } ?: Either.Right(publicKey) + return generate(sessionId, signingKey, message, keyAggCache, extraInput) + } + /** * Alternative counter-based method for generating nonce. * This nonce must never be persisted or reused across signing sessions. @@ -236,7 +259,20 @@ public object Musig2 { */ @JvmStatic public fun aggregateKeys(publicKeys: List): XonlyPublicKey = KeyAggCache.create(publicKeys).first - + + /** + * @param sessionId a random, unique session ID. + * @param signingKey signer's private key or public key + * @param publicKeys public keys of all participants: callers must verify that all public keys are valid. + * @param message (optional) message that will be signed, if already known. + * @param extraInput (optional) additional random data. + */ + @JvmStatic + public fun generateNonce(sessionId: ByteVector32, signingKey: Either, publicKeys: List, message: ByteVector32?, extraInput: ByteVector32?): Pair { + val (_, keyAggCache) = KeyAggCache.create(publicKeys) + return SecretNonce.generate(sessionId, signingKey, message, keyAggCache, extraInput) + } + /** * @param sessionId a random, unique session ID. * @param privateKey signer's private key @@ -245,10 +281,12 @@ public object Musig2 { * @param message (optional) message that will be signed, if already known. * @param extraInput (optional) additional random data. */ + @Deprecated("Use generateNonce() with an Either instead", ReplaceWith("generateNonce()"), DeprecationLevel.WARNING) @JvmStatic public fun generateNonce(sessionId: ByteVector32, privateKey: PrivateKey?, publicKey: PublicKey, publicKeys: List, message: ByteVector32?, extraInput: ByteVector32?): Pair { - val (_, keyAggCache) = KeyAggCache.create(publicKeys) - return SecretNonce.generate(sessionId, privateKey, publicKey, message, keyAggCache, extraInput) + privateKey?.let { require(it.publicKey() == publicKey) { "if the private key is provided, it must match the public key" } } + val signingKey = privateKey?.let { Either.Left(it) } ?: Either.Right(publicKey) + return generateNonce(sessionId, signingKey, publicKeys, message, extraInput) } /** diff --git a/src/commonTest/kotlin/fr/acinq/bitcoin/crypto/musig2/Musig2TestsCommon.kt b/src/commonTest/kotlin/fr/acinq/bitcoin/crypto/musig2/Musig2TestsCommon.kt index 45e85b0..1ca3116 100644 --- a/src/commonTest/kotlin/fr/acinq/bitcoin/crypto/musig2/Musig2TestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/bitcoin/crypto/musig2/Musig2TestsCommon.kt @@ -1,6 +1,7 @@ package fr.acinq.bitcoin.crypto.musig2 import fr.acinq.bitcoin.* +import fr.acinq.bitcoin.utils.Either import fr.acinq.secp256k1.Hex import kotlinx.serialization.json.* import kotlin.random.Random @@ -76,7 +77,7 @@ class Musig2TestsCommon { val expectedPubnonce = IndividualNonce(it.jsonObject["expected_pubnonce"]!!.jsonPrimitive.content) // secp256k1 only supports signing 32-byte messages (when provided), which excludes some of the test vectors. if (msg == null || msg.size == 32) { - val (secnonce, pubnonce) = SecretNonce.generate(randprime, sk, pk, msg?.byteVector32(), keyagg, extraInput?.byteVector32()) + val (secnonce, pubnonce) = SecretNonce.generate(randprime, sk?.let { Either.Left(it) } ?: Either.Right(pk), msg?.byteVector32(), keyagg, extraInput?.byteVector32()) assertEquals(expectedPubnonce, pubnonce) assertEquals(expectedSecnonce, secnonce) } @@ -278,7 +279,7 @@ class Musig2TestsCommon { } // Generate secret nonces for each participant. - val nonces = privkeys.map { SecretNonce.generate(Random.Default.nextBytes(32).byteVector32(), it, it.publicKey(), message = null, keyAggCache, extraInput = null) } + val nonces = privkeys.map { SecretNonce.generate(Random.Default.nextBytes(32).byteVector32(), Either.Left(it), message = null, keyAggCache, extraInput = null) } val secnonces = nonces.map { it.first } val pubnonces = nonces.map { it.second } @@ -316,8 +317,8 @@ class Musig2TestsCommon { // The first step of a musig2 signing session is to exchange nonces. // If participants are disconnected before the end of the signing session, they must start again with fresh nonces. - val aliceNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), alicePrivKey, alicePrivKey.publicKey(), listOf(alicePubKey, bobPubKey), null, null) - val bobNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), bobPrivKey, bobPrivKey.publicKey(), listOf(alicePubKey, bobPubKey), null, null) + val aliceNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), Either.Left(alicePrivKey), listOf(alicePubKey, bobPubKey), null, null) + val bobNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), Either.Left(bobPrivKey), listOf(alicePubKey, bobPubKey), null, null) // Once they have each other's public nonce, they can produce partial signatures. val publicNonces = listOf(aliceNonce.second, bobNonce.second) @@ -354,8 +355,8 @@ class Musig2TestsCommon { val tx = Transaction(2, listOf(), listOf(TxOut(10_000.sat(), Script.pay2tr(commonPubKey))), 0) val spendingTx = Transaction(2, listOf(TxIn(OutPoint(tx, 0), sequence = 0)), listOf(TxOut(10_000.sat(), Script.pay2wpkh(alicePubKey))), 0) - val aliceNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), alicePrivKey, alicePrivKey.publicKey(),listOf(alicePubKey, bobPubKey), null, null) - val bobNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), bobPrivKey, bobPrivKey.publicKey(), listOf(alicePubKey, bobPubKey), null, null) + val aliceNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), Either.Left(alicePrivKey), listOf(alicePubKey, bobPubKey), null, null) + val bobNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), Either.Left(bobPrivKey), listOf(alicePubKey, bobPubKey), null, null) val publicNonces = listOf(aliceNonce.second, bobNonce.second) 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 { ) // The first step of a musig2 signing session is to exchange nonces. // If participants are disconnected before the end of the signing session, they must start again with fresh nonces. - val userNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), userPrivateKey, userPrivateKey.publicKey(), listOf(userPublicKey, serverPublicKey), null, null) - val serverNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), serverPrivateKey, serverPrivateKey.publicKey(), listOf(userPublicKey, serverPublicKey), null, null) + val userNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), Either.Left(userPrivateKey), listOf(userPublicKey, serverPublicKey), null, null) + val serverNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), Either.Left(serverPrivateKey), listOf(userPublicKey, serverPublicKey), null, null) // Once they have each other's public nonce, they can produce partial signatures. val publicNonces = listOf(userNonce.second, serverNonce.second)