Skip to content

Commit 45fe463

Browse files
committed
Add a helper method to verify partial musig2 signatures
1 parent 0427b3d commit 45fe463

File tree

3 files changed

+39
-1
lines changed

3 files changed

+39
-1
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ plugins {
1313
val currentOs = org.gradle.internal.os.OperatingSystem.current()
1414

1515
group = "fr.acinq.bitcoin"
16-
version = "0.22.2"
16+
version = "0.22.3-SNAPSHOT"
1717

1818
repositories {
1919
google()

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package fr.acinq.bitcoin.crypto.musig2
33
import fr.acinq.bitcoin.*
44
import fr.acinq.bitcoin.utils.Either
55
import fr.acinq.bitcoin.utils.flatMap
6+
import fr.acinq.bitcoin.utils.getOrElse
67
import fr.acinq.secp256k1.Hex
78
import fr.acinq.secp256k1.Secp256k1
89
import kotlin.jvm.JvmOverloads
@@ -279,6 +280,36 @@ public object Musig2 {
279280
return taprootSession(tx, inputIndex, inputs, publicKeys, publicNonces, scriptTree).map { it.sign(secretNonce, privateKey) }
280281
}
281282

283+
/**
284+
* Verify a partial musig2 signature.
285+
286+
* @param partialSig partial musig2 signature.
287+
* @param nonce public nonce matching the secret nonce used to generate the signature.
288+
* @param publicKey public key for the private key used to generate the signature.
289+
* @param tx transaction spending the target taproot input.
290+
* @param inputIndex index of the taproot input to spend.
291+
* @param inputs all inputs of the spending transaction.
292+
* @param publicKeys public keys of all participants of the musig2 session: callers must verify that all public keys are valid.
293+
* @param publicNonces public nonces of all participants of the musig2 session.
294+
* @param scriptTree tapscript tree of the taproot input, if it has script paths.
295+
* @return true if the partial signature is valid.
296+
*/
297+
@JvmStatic
298+
public fun verifyTaprootSignature(
299+
partialSig: ByteVector32,
300+
nonce: IndividualNonce,
301+
publicKey: PublicKey,
302+
tx: Transaction,
303+
inputIndex: Int,
304+
inputs: List<TxOut>,
305+
publicKeys: List<PublicKey>,
306+
publicNonces: List<IndividualNonce>,
307+
scriptTree: ScriptTree?
308+
): Boolean {
309+
val session = taprootSession(tx, inputIndex, inputs, publicKeys, publicNonces, scriptTree)
310+
return session.map { it.verify(partialSig, nonce, publicKey) }.getOrElse { false }
311+
}
312+
282313
/**
283314
* Aggregate partial musig2 signatures into a valid schnorr signature for the given taproot input key path.
284315
*

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,10 +298,14 @@ class Musig2TestsCommon {
298298

299299
// Once they have each other's public nonce, they can produce partial signatures.
300300
val publicNonces = listOf(aliceNonce.second, bobNonce.second)
301+
301302
val aliceSig = Musig2.signTaprootInput(alicePrivKey, spendingTx, 0, listOf(tx.txOut[0]), listOf(alicePubKey, bobPubKey), aliceNonce.first, publicNonces, scriptTree = null).right
302303
assertNotNull(aliceSig)
304+
assertTrue(Musig2.verifyTaprootSignature(aliceSig, aliceNonce.second, alicePubKey, spendingTx, 0, listOf(tx.txOut[0]), listOf(alicePubKey, bobPubKey), publicNonces, scriptTree = null))
305+
303306
val bobSig = Musig2.signTaprootInput(bobPrivKey, spendingTx, 0, listOf(tx.txOut[0]), listOf(alicePubKey, bobPubKey), bobNonce.first, publicNonces, scriptTree = null).right
304307
assertNotNull(bobSig)
308+
assertTrue(Musig2.verifyTaprootSignature(bobSig, bobNonce.second, bobPubKey, spendingTx, 0, listOf(tx.txOut[0]), listOf(alicePubKey, bobPubKey), publicNonces, scriptTree = null))
305309

306310
// Once they have each other's partial signature, they can aggregate them into a valid signature.
307311
val aggregateSig = Musig2.aggregateTaprootSignatures(listOf(aliceSig, bobSig), spendingTx, 0, listOf(tx.txOut[0]), listOf(alicePubKey, bobPubKey), publicNonces, scriptTree = null).right
@@ -355,8 +359,11 @@ class Musig2TestsCommon {
355359
val publicNonces = listOf(userNonce.second, serverNonce.second)
356360
val userSig = Musig2.signTaprootInput(userPrivateKey, tx, 0, swapInTx.txOut, listOf(userPublicKey, serverPublicKey), userNonce.first, publicNonces, scriptTree).right
357361
assertNotNull(userSig)
362+
assertTrue(Musig2.verifyTaprootSignature(userSig, userNonce.second, userPublicKey, tx, 0, swapInTx.txOut, listOf(userPublicKey, serverPublicKey), publicNonces, scriptTree))
363+
358364
val serverSig = Musig2.signTaprootInput(serverPrivateKey, tx, 0, swapInTx.txOut, listOf(userPublicKey, serverPublicKey), serverNonce.first, publicNonces, scriptTree).right
359365
assertNotNull(serverSig)
366+
assertTrue(Musig2.verifyTaprootSignature(serverSig, serverNonce.second, serverPublicKey, tx, 0, swapInTx.txOut, listOf(userPublicKey, serverPublicKey), publicNonces, scriptTree))
360367

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

0 commit comments

Comments
 (0)