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 e050506c..4ddd0f15 100644 --- a/src/commonMain/kotlin/fr/acinq/bitcoin/crypto/musig2/Musig2.kt +++ b/src/commonMain/kotlin/fr/acinq/bitcoin/crypto/musig2/Musig2.kt @@ -4,6 +4,7 @@ import fr.acinq.bitcoin.* import fr.acinq.bitcoin.utils.Either import fr.acinq.secp256k1.Hex import fr.acinq.secp256k1.Secp256k1 +import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmStatic /** @@ -43,12 +44,11 @@ public data class KeyAggCache(val data: ByteVector) { * @return a new (if cache was null) or updated cache, and the aggregated public key */ @JvmStatic - public fun add(pubkeys: List, cache: KeyAggCache? = null): Either> = try { + @JvmOverloads + public fun add(pubkeys: List, cache: KeyAggCache? = null): Pair { val localCache = cache?.data?.toByteArray() ?: ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) val aggkey = Secp256k1.musigPubkeyAgg(pubkeys.map { it.value.toByteArray() }.toTypedArray(), localCache) - Either.Right(Pair(XonlyPublicKey(aggkey.byteVector32()), KeyAggCache(localCache.byteVector()))) - } catch (t: Throwable) { - Either.Left(t) + return Pair(XonlyPublicKey(aggkey.byteVector32()), KeyAggCache(localCache.byteVector())) } } } @@ -69,10 +69,8 @@ public data class Session(val data: ByteVector) { * @param aggCache key aggregation cache * @return a Musig2 partial signature */ - public fun sign(secretNonce: SecretNonce, pk: PrivateKey, aggCache: KeyAggCache): Either = try { - Either.Right(Secp256k1.musigPartialSign(secretNonce.data.toByteArray(), pk.value.toByteArray(), aggCache.data.toByteArray(), toByteArray()).byteVector32()) - } catch (t: Throwable) { - Either.Left(t) + public fun sign(secretNonce: SecretNonce, pk: PrivateKey, aggCache: KeyAggCache): ByteVector32 { + return Secp256k1.musigPartialSign(secretNonce.data.toByteArray(), pk.value.toByteArray(), aggCache.data.toByteArray(), toByteArray()).byteVector32() } /** @@ -107,11 +105,9 @@ public data class Session(val data: ByteVector) { * @return a Musig signing session */ @JvmStatic - public fun build(aggregatedNonce: AggregatedNonce, msg: ByteVector32, cache: KeyAggCache): Either = try { + public fun build(aggregatedNonce: AggregatedNonce, msg: ByteVector32, cache: KeyAggCache): Session { val session = Secp256k1.musigNonceProcess(aggregatedNonce.toByteArray(), msg.toByteArray(), cache.data.toByteArray()) - Either.Right(Session(session.byteVector())) - } catch (t: Throwable) { - Either.Left(t) + return Session(session.byteVector()) } } } 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 589eb6dd..27745917 100644 --- a/src/commonTest/kotlin/fr/acinq/bitcoin/crypto/musig2/Musig2TestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/bitcoin/crypto/musig2/Musig2TestsCommon.kt @@ -21,7 +21,7 @@ class Musig2TestsCommon { tests.jsonObject["valid_test_cases"]!!.jsonArray.forEach { val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int } val expected = XonlyPublicKey(ByteVector32.fromValidHex(it.jsonObject["expected"]!!.jsonPrimitive.content)) - val (aggkey, _) = KeyAggCache.add(keyIndices.map { pubkeys[it] }).right!! + val (aggkey, _) = KeyAggCache.add(keyIndices.map { pubkeys[it] }) assertEquals(expected, aggkey) } tests.jsonObject["error_test_cases"]!!.jsonArray.forEach { @@ -29,7 +29,7 @@ class Musig2TestsCommon { val tweakIndices = it.jsonObject["tweak_indices"]!!.jsonArray.map { it.jsonPrimitive.int } val isXonly = it.jsonObject["is_xonly"]!!.jsonArray.map { it.jsonPrimitive.boolean } assertFails { - var (_, cache) = KeyAggCache.add(keyIndices.map { pubkeys[it] }).right!! + var (_, cache) = KeyAggCache.add(keyIndices.map { pubkeys[it] }) tweakIndices.zip(isXonly).forEach { cache = cache.tweak(tweaks[it.first], it.second).right!!.first } } } @@ -92,13 +92,13 @@ class Musig2TestsCommon { val isXonly = it.jsonObject["is_xonly"]!!.jsonArray.map { it.jsonPrimitive.boolean } assertEquals(AggregatedNonce(it.jsonObject["aggnonce"]!!.jsonPrimitive.content), aggnonce) val cache = run { - var (_, c) = KeyAggCache.add(keyIndices.map { pubkeys[it] }).right!! + var (_, c) = KeyAggCache.add(keyIndices.map { pubkeys[it] }) tweakIndices.zip(isXonly).map { tweaks[it.first] to it.second }.forEach { (tweak, isXonly) -> c = c.tweak(tweak, isXonly).right!!.first } c } - val session = Session.build(aggnonce, msg, cache).right!! + val session = Session.build(aggnonce, msg, cache) val aggsig = session.add(psigIndices.map { psigs[it] }).right!! assertEquals(expected, aggsig) } @@ -111,13 +111,13 @@ class Musig2TestsCommon { val isXonly = it.jsonObject["is_xonly"]!!.jsonArray.map { it.jsonPrimitive.boolean } assertEquals(AggregatedNonce(it.jsonObject["aggnonce"]!!.jsonPrimitive.content), aggnonce) val cache = run { - var (_, c) = KeyAggCache.add(keyIndices.map { pubkeys[it] }).right!! + var (_, c) = KeyAggCache.add(keyIndices.map { pubkeys[it] }) tweakIndices.zip(isXonly).map { tweaks[it.first] to it.second }.forEach { (tweak, isXonly) -> c = c.tweak(tweak, isXonly).right!!.first } c } - val session = Session.build(aggnonce, msg, cache).right!! + val session = Session.build(aggnonce, msg, cache) assertTrue { session.add(psigIndices.map { psigs[it] }).isLeft } @@ -149,15 +149,15 @@ class Musig2TestsCommon { // aggregate public nonces val aggnonce = IndividualNonce.aggregate(pubnonces).right!! val cache = run { - val (_, c) = KeyAggCache.add(pubkeys).right!! + val (_, c) = KeyAggCache.add(pubkeys) val (c1, _) = c.tweak(plainTweak, false).right!! val (c2, _) = c1.tweak(xonlyTweak, true).right!! c2 } - val session = Session.build(aggnonce, msg, cache).right!! + val session = Session.build(aggnonce, msg, cache) // create partial signatures val psigs = privkeys.indices.map { - session.sign(secnonces[it], privkeys[it], cache).right!! + session.sign(secnonces[it], privkeys[it], cache) } // verify partial signatures @@ -171,7 +171,7 @@ class Musig2TestsCommon { // aggregate public keys val aggpub = run { - val (_, c) = KeyAggCache.add(pubkeys).right!! + val (_, c) = KeyAggCache.add(pubkeys) val (c1, _) = c.tweak(plainTweak, false).right!! val (_, p) = c1.tweak(xonlyTweak, true).right!! p @@ -189,7 +189,7 @@ class Musig2TestsCommon { val bobPubKey = bobPrivKey.publicKey() // Alice and Bob exchange public keys and agree on a common aggregated key - val (internalPubKey, cache) = KeyAggCache.add(listOf(alicePubKey, bobPubKey)).right!! + val (internalPubKey, cache) = KeyAggCache.add(listOf(alicePubKey, bobPubKey)) // we use the standard BIP86 tweak val commonPubKey = internalPubKey.outputKey(Crypto.TaprootTweak.NoScriptTweak).first @@ -209,9 +209,9 @@ class Musig2TestsCommon { // we use the same ctx for Alice and Bob, they both know all the public keys that are used here val (cache1, _) = cache.tweak(internalPubKey.tweak(Crypto.TaprootTweak.NoScriptTweak), true).right!! - val session = Session.build(aggnonce, msg, cache1).right!! - val aliceSig = session.sign(aliceNonce.first, alicePrivKey, cache1).right!! - val bobSig = session.sign(bobNonce.first, bobPrivKey, cache1).right!! + val session = Session.build(aggnonce, msg, cache1) + val aliceSig = session.sign(aliceNonce.first, alicePrivKey, cache1) + val bobSig = session.sign(bobNonce.first, bobPrivKey, cache1) session.add(listOf(aliceSig, bobSig)).right!! } @@ -236,7 +236,7 @@ class Musig2TestsCommon { val merkleRoot = scriptTree.hash() // 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 - val (internalPubKey, cache) = KeyAggCache.add(listOf(userPrivateKey.publicKey(), serverPrivateKey.publicKey())).right!! + val (internalPubKey, cache) = KeyAggCache.add(listOf(userPrivateKey.publicKey(), serverPrivateKey.publicKey())) // it is tweaked with the script's merkle root to get the pubkey that will be exposed val pubkeyScript: List = Script.pay2tr(internalPubKey, merkleRoot) @@ -267,16 +267,13 @@ class Musig2TestsCommon { .flatMap { commonNonce -> cache.tweak(internalPubKey.tweak(Crypto.TaprootTweak.ScriptTweak(merkleRoot)), true) .flatMap { (cache1, _) -> - Session.build(commonNonce, txHash, cache1) - .flatMap { session -> - session.sign(userNonce.first, userPrivateKey, cache1) - .flatMap { userSig -> - session.sign(serverNonce.first, serverPrivateKey, cache1) - .flatMap { serverSig -> session.add(listOf(userSig, serverSig)) } - } - } + val session = Session.build(commonNonce, txHash, cache1) + val userSig = session.sign(userNonce.first, userPrivateKey, cache1) + val serverSig = session.sign(serverNonce.first, serverPrivateKey, cache1) + session.add(listOf(userSig, serverSig)) } } + val signedTx = tx.updateWitness(0, ScriptWitness(listOf(commonSig.right!!))) Transaction.correctlySpends(signedTx, swapInTx, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) }