@@ -29,14 +29,14 @@ class Musig2TestsCommon {
2929 val keyIndices = it.jsonObject[" key_indices" ]!! .jsonArray.map { it.jsonPrimitive.int }
3030 val expected = XonlyPublicKey (ByteVector32 .fromValidHex(it.jsonObject[" expected" ]!! .jsonPrimitive.content))
3131 val ctx = Musig2 .keyAgg(keyIndices.map { pubkeys[it] })
32- assertEquals(expected, ctx.Q . xOnly())
32+ assertEquals(expected, ctx.xOnly())
3333 }
3434 tests.jsonObject[" error_test_cases" ]!! .jsonArray.forEach {
3535 val keyIndices = it.jsonObject[" key_indices" ]!! .jsonArray.map { it.jsonPrimitive.int }
3636 val tweakIndices = it.jsonObject[" tweak_indices" ]!! .jsonArray.map { it.jsonPrimitive.int }
3737 val isXonly = it.jsonObject[" is_xonly" ]!! .jsonArray.map { it.jsonPrimitive.boolean }
3838 assertFails {
39- var ctx = Musig2 .keyAgg (keyIndices.map { pubkeys[it] })
39+ var ctx = KeyAggCtx (keyIndices.map { pubkeys[it] })
4040 tweakIndices.zip(isXonly).forEach { ctx = ctx.tweak(tweaks[it.first], it.second) }
4141 }
4242 }
@@ -267,7 +267,7 @@ class Musig2TestsCommon {
267267 }
268268
269269 // aggregate public keys
270- val aggpub = Musig2 .keyAgg (pubkeys)
270+ val aggpub = KeyAggCtx (pubkeys)
271271 .tweak(plainTweak, false )
272272 .tweak(xonlyTweak, true )
273273
@@ -277,51 +277,45 @@ class Musig2TestsCommon {
277277
278278 @Test
279279 fun `use musig2 to replace multisig 2-of-2` () {
280+ val random = Random .Default
280281 val alicePrivKey = PrivateKey (ByteArray (32 ) { 1 })
281282 val alicePubKey = alicePrivKey.publicKey()
282283 val bobPrivKey = PrivateKey (ByteArray (32 ) { 2 })
283284 val bobPubKey = bobPrivKey.publicKey()
284285
285- // Alice and Bob exchange public keys and agree on a common aggregated key
286- val internalPubKey = Musig2 .keyAgg(listOf (alicePubKey, bobPubKey)).Q .xOnly()
287- // we use the standard BIP86 tweak
288- val commonPubKey = internalPubKey.outputKey(Crypto .TaprootTweak .NoScriptTweak ).first
289-
290- // this tx sends to a standard p2tr(commonPubKey) script
291- val tx = Transaction (2 , listOf (), listOf (TxOut (Satoshi (10000 ), Script .pay2tr(commonPubKey))), 0 )
286+ // Alice and Bob exchange public keys and agree on a common aggregated key.
287+ val aggregatedKey = Musig2 .keyAgg(listOf (alicePubKey, bobPubKey)).xOnly()
288+ // This tx sends to a taproot script that doesn't contain any script path.
289+ val tx = Transaction (2 , listOf (), listOf (TxOut (Satoshi (10000 ), Script .pay2tr(aggregatedKey, scripts = null ))), 0 )
292290
293291 // this is how Alice and Bob would spend that tx
294292 val spendingTx = Transaction (2 , listOf (TxIn (OutPoint (tx, 0 ), sequence = 0 )), listOf (TxOut (Satoshi (10000 ), Script .pay2wpkh(alicePubKey))), 0 )
295-
296- val commonSig = run {
297- val random = Random .Default
298- val aliceNonce = SecretNonce .generate(alicePrivKey, alicePubKey, commonPubKey, null , null , random.nextBytes(32 ).byteVector32())
299- val bobNonce = SecretNonce .generate(bobPrivKey, bobPubKey, commonPubKey, null , null , random.nextBytes(32 ).byteVector32())
300-
301- val aggnonce = IndividualNonce .aggregate(listOf (aliceNonce.publicNonce(), bobNonce.publicNonce()))
302- val msg = Transaction .hashForSigningTaprootKeyPath(spendingTx, 0 , listOf (tx.txOut[0 ]), SigHash .SIGHASH_DEFAULT )
303-
304- // we use the same ctx for Alice and Bob, they both know all the public keys that are used here
305- val ctx = SessionCtx (
306- aggnonce,
307- listOf (alicePubKey, bobPubKey),
308- listOf (Pair (internalPubKey.tweak(Crypto .TaprootTweak .NoScriptTweak ), true )),
309- msg
310- )
311- val aliceSig = ctx.sign(aliceNonce, alicePrivKey)!!
312- val bobSig = ctx.sign(bobNonce, bobPrivKey)!!
313- ctx.partialSigAgg(listOf (aliceSig, bobSig))!!
293+ val sig = run {
294+ // The first step of a musig2 signing session is to exchange nonces.
295+ // If participants are disconnected before the end of the signing session, they must start again with fresh nonces.
296+ val aliceNonce = SecretNonce .generate(alicePrivKey, aggregatedKey, random.nextBytes(32 ).byteVector32())
297+ val bobNonce = SecretNonce .generate(bobPrivKey, aggregatedKey, random.nextBytes(32 ).byteVector32())
298+
299+ // Once they have each other's public nonce, they can produce partial signatures.
300+ val publicNonces = listOf (aliceNonce.publicNonce(), bobNonce.publicNonce())
301+ val aliceSig = Musig2 .signTaprootInput(alicePrivKey, spendingTx, 0 , tx.txOut, listOf (alicePubKey, bobPubKey), aliceNonce, publicNonces, scriptTree = null )!!
302+ val bobSig = Musig2 .signTaprootInput(bobPrivKey, spendingTx, 0 , tx.txOut, listOf (alicePubKey, bobPubKey), bobNonce, publicNonces, scriptTree = null )!!
303+
304+ // Once they have each other's partial signature, they can aggregate them into a valid signature.
305+ Musig2 .aggregateTaprootSignatures(listOf (aliceSig, bobSig), spendingTx, 0 , tx.txOut, listOf (alicePubKey, bobPubKey), publicNonces, scriptTree = null )!!
314306 }
315307
316- // this tx looks like any other tx that spends a p2tr output, with a single signature
317- val signedSpendingTx = spendingTx.updateWitness(0 , ScriptWitness ( listOf (commonSig) ))
308+ // This tx looks like any other tx that spends a p2tr output, with a single signature.
309+ val signedSpendingTx = spendingTx.updateWitness(0 , Script .witnessKeyPathPay2tr(sig ))
318310 Transaction .correctlySpends(signedSpendingTx, tx, ScriptFlags .STANDARD_SCRIPT_VERIFY_FLAGS )
319311 }
320312
321313 @Test
322314 fun `swap-in-potentiam example with musig2 and taproot` () {
323315 val userPrivateKey = PrivateKey (ByteArray (32 ) { 1 })
316+ val userPublicKey = userPrivateKey.publicKey()
324317 val serverPrivateKey = PrivateKey (ByteArray (32 ) { 2 })
318+ val serverPublicKey = serverPrivateKey.publicKey()
325319 val userRefundPrivateKey = PrivateKey (ByteArray (32 ) { 3 })
326320 val refundDelay = 25920
327321
@@ -333,8 +327,8 @@ class Musig2TestsCommon {
333327 val scriptTree = ScriptTree .Leaf (0 , redeemScript)
334328
335329 // the internal pubkey is the musig2 aggregation of the user's and server's public keys: it does not depend upon the user's refund's key
336- val internalPubKey = Musig2 .keyAgg(listOf (userPrivateKey.publicKey(), serverPrivateKey.publicKey())). Q .xOnly()
337- val pubkeyScript = Script .pay2tr(internalPubKey , scriptTree)
330+ val aggregatedKey = Musig2 .keyAgg(listOf (userPrivateKey.publicKey(), serverPrivateKey.publicKey())).xOnly()
331+ val pubkeyScript = Script .pay2tr(aggregatedKey , scriptTree)
338332
339333 val swapInTx = Transaction (
340334 version = 2 ,
@@ -348,27 +342,22 @@ class Musig2TestsCommon {
348342 val tx = Transaction (
349343 version = 2 ,
350344 txIn = listOf (TxIn (OutPoint (swapInTx, 0 ), sequence = TxIn .SEQUENCE_FINAL )),
351- txOut = listOf (TxOut (Satoshi (10000 ), Script .pay2wpkh(userPrivateKey.publicKey() ))),
345+ txOut = listOf (TxOut (Satoshi (10000 ), Script .pay2wpkh(userPublicKey ))),
352346 lockTime = 0
353347 )
354- // this is the beginning of an interactive musig2 signing session. if user and server are disconnected before they have exchanged partial
355- // signatures they will have to start again with fresh nonces
356- val userNonce = SecretNonce .generate(userPrivateKey, userPrivateKey.publicKey(), internalPubKey, null , null , random.nextBytes(32 ).byteVector32())
357- val serverNonce = SecretNonce .generate(serverPrivateKey, serverPrivateKey.publicKey(), internalPubKey, null , null , random.nextBytes(32 ).byteVector32())
358-
359- val txHash = Transaction .hashForSigningTaprootKeyPath(tx, 0 , swapInTx.txOut, SigHash .SIGHASH_DEFAULT )
360- val commonNonce = IndividualNonce .aggregate(listOf (userNonce.publicNonce(), serverNonce.publicNonce()))
361- val ctx = SessionCtx (
362- commonNonce,
363- listOf (userPrivateKey.publicKey(), serverPrivateKey.publicKey()),
364- listOf (Pair (internalPubKey.tweak(Crypto .TaprootTweak .ScriptTweak (scriptTree)), true )),
365- txHash
366- )
367-
368- val userSig = ctx.sign(userNonce, userPrivateKey)!!
369- val serverSig = ctx.sign(serverNonce, serverPrivateKey)!!
370- val commonSig = ctx.partialSigAgg(listOf (userSig, serverSig))!!
371- val signedTx = tx.updateWitness(0 , Script .witnessKeyPathPay2tr(commonSig))
348+ // The first step of a musig2 signing session is to exchange nonces.
349+ // If participants are disconnected before the end of the signing session, they must start again with fresh nonces.
350+ val userNonce = SecretNonce .generate(userPrivateKey, aggregatedKey, random.nextBytes(32 ).byteVector32())
351+ val serverNonce = SecretNonce .generate(serverPrivateKey, aggregatedKey, random.nextBytes(32 ).byteVector32())
352+
353+ // Once they have each other's public nonce, they can produce partial signatures.
354+ val publicNonces = listOf (userNonce.publicNonce(), serverNonce.publicNonce())
355+ val userSig = Musig2 .signTaprootInput(userPrivateKey, tx, 0 , swapInTx.txOut, listOf (userPublicKey, serverPublicKey), userNonce, publicNonces, scriptTree)!!
356+ val serverSig = Musig2 .signTaprootInput(serverPrivateKey, tx, 0 , swapInTx.txOut, listOf (userPublicKey, serverPublicKey), serverNonce, publicNonces, scriptTree)!!
357+
358+ // Once they have each other's partial signature, they can aggregate them into a valid signature.
359+ val sig = Musig2 .aggregateTaprootSignatures(listOf (userSig, serverSig), tx, 0 , swapInTx.txOut, listOf (userPublicKey, serverPublicKey), publicNonces, scriptTree)!!
360+ val signedTx = tx.updateWitness(0 , Script .witnessKeyPathPay2tr(sig))
372361 Transaction .correctlySpends(signedTx, swapInTx, ScriptFlags .STANDARD_SCRIPT_VERIFY_FLAGS )
373362 }
374363
@@ -381,7 +370,7 @@ class Musig2TestsCommon {
381370 lockTime = 0
382371 )
383372 val sig = Crypto .signTaprootScriptPath(userRefundPrivateKey, tx, 0 , swapInTx.txOut, SigHash .SIGHASH_DEFAULT , scriptTree.hash())
384- val witness = Script .witnessScriptPathPay2tr(internalPubKey , scriptTree, ScriptWitness (listOf (sig)), scriptTree)
373+ val witness = Script .witnessScriptPathPay2tr(aggregatedKey , scriptTree, ScriptWitness (listOf (sig)), scriptTree)
385374 val signedTx = tx.updateWitness(0 , witness)
386375 Transaction .correctlySpends(signedTx, swapInTx, ScriptFlags .STANDARD_SCRIPT_VERIFY_FLAGS )
387376 }
0 commit comments