Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 45 additions & 7 deletions src/commonMain/kotlin/fr/acinq/bitcoin/crypto/musig2/Musig2.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<SecretNonce, IndividualNonce> {
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<PrivateKey, PublicKey>, message: ByteVector32?, keyAggCache: KeyAggCache?, extraInput: ByteVector32?): Pair<SecretNonce, IndividualNonce> {
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<PrivateKey, PublicKey> instead", ReplaceWith("generate()"), DeprecationLevel.WARNING)
@JvmStatic
public fun generate(sessionId: ByteVector32, privateKey: PrivateKey?, publicKey: PublicKey, message: ByteVector32?, keyAggCache: KeyAggCache?, extraInput: ByteVector32?): Pair<SecretNonce, IndividualNonce> {
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.
Expand Down Expand Up @@ -236,7 +259,20 @@ public object Musig2 {
*/
@JvmStatic
public fun aggregateKeys(publicKeys: List<PublicKey>): 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<PrivateKey, PublicKey>, publicKeys: List<PublicKey>, message: ByteVector32?, extraInput: ByteVector32?): Pair<SecretNonce, IndividualNonce> {
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
Expand All @@ -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<PrivateKey, PublicKey> instead", ReplaceWith("generateNonce()"), DeprecationLevel.WARNING)
@JvmStatic
public fun generateNonce(sessionId: ByteVector32, privateKey: PrivateKey?, publicKey: PublicKey, publicKeys: List<PublicKey>, message: ByteVector32?, extraInput: ByteVector32?): Pair<SecretNonce, IndividualNonce> {
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)
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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 }

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down