diff --git a/pom.xml b/pom.xml index 4d0cdbb9..0ff8465a 100644 --- a/pom.xml +++ b/pom.xml @@ -177,7 +177,7 @@ fr.acinq.bitcoin bitcoin-kmp-jvm - 0.23.0 + 0.24.0 fr.acinq.secp256k1 diff --git a/src/main/scala/fr/acinq/bitcoin/scalacompat/Musig2.scala b/src/main/scala/fr/acinq/bitcoin/scalacompat/Musig2.scala index dc9578b4..3f7ffb59 100644 --- a/src/main/scala/fr/acinq/bitcoin/scalacompat/Musig2.scala +++ b/src/main/scala/fr/acinq/bitcoin/scalacompat/Musig2.scala @@ -20,11 +20,29 @@ object Musig2 { /** * @param sessionId a random, unique session ID. - * @param privateKey signer's private key. + * @param signingKey either the 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_opt (optional) message that will be signed, if already known. + * @param extraInput_opt (optional) additional random data. */ - def generateNonce(sessionId: ByteVector32, privateKey: PrivateKey, publicKeys: Seq[PublicKey]): (SecretNonce, IndividualNonce) = { - val nonce = fr.acinq.bitcoin.crypto.musig2.Musig2.generateNonce(sessionId, privateKey, publicKeys.map(scala2kmp).asJava) + def generateNonce(sessionId: ByteVector32, signingKey: Either[PrivateKey, PublicKey], publicKeys: Seq[PublicKey], message_opt: Option[ByteVector32], extraInput_opt: Option[ByteVector32]): (SecretNonce, IndividualNonce) = { + val (privateKey, publicKey) = signingKey match { + case Left(priv) => (scala2kmp(priv), priv.publicKey) + case Right(pub) => (null, pub) + } + val nonce = fr.acinq.bitcoin.crypto.musig2.Musig2.generateNonce(sessionId, privateKey, publicKey, publicKeys.map(scala2kmp).asJava, message_opt.map(scala2kmp).orNull, extraInput_opt.map(scala2kmp).orNull) + (nonce.getFirst, nonce.getSecond) + } + + /** + * @param nonRepeatingCounter non-repeating counter that must never be reused with the same private key. + * @param privateKey signer's private key. + * @param publicKeys public keys of all participants: callers must verify that all public keys are valid. + * @param message_opt (optional) message that will be signed, if already known. + * @param extraInput_opt (optional) additional random data. + */ + def generateNonceWithCounter(nonRepeatingCounter: Long, privateKey: PrivateKey, publicKeys: Seq[PublicKey], message_opt: Option[ByteVector32], extraInput_opt: Option[ByteVector32]): (SecretNonce, IndividualNonce) = { + val nonce = fr.acinq.bitcoin.crypto.musig2.Musig2.generateNonceWithCounter(nonRepeatingCounter, privateKey, publicKeys.map(scala2kmp).asJava, message_opt.map(scala2kmp).orNull, extraInput_opt.map(scala2kmp).orNull) (nonce.getFirst, nonce.getSecond) } diff --git a/src/test/scala/fr/acinq/bitcoin/scalacompat/Musig2Spec.scala b/src/test/scala/fr/acinq/bitcoin/scalacompat/Musig2Spec.scala index 278eb0cd..9311bd19 100644 --- a/src/test/scala/fr/acinq/bitcoin/scalacompat/Musig2Spec.scala +++ b/src/test/scala/fr/acinq/bitcoin/scalacompat/Musig2Spec.scala @@ -2,6 +2,7 @@ package fr.acinq.bitcoin.scalacompat import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey import fr.acinq.bitcoin.{ScriptFlags, ScriptTree, SigHash} +import fr.acinq.secp256k1.Hex import org.scalatest.FunSuite import scodec.bits.{ByteVector, HexStringSyntax} @@ -26,8 +27,8 @@ class Musig2Spec extends FunSuite { // 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 (aliceSecretNonce, alicePublicNonce) = Musig2.generateNonce(ByteVector32(ByteVector(Random.nextBytes(32))), alicePrivKey, Seq(alicePubKey, bobPubKey)) - val (bobSecretNonce, bobPublicNonce) = Musig2.generateNonce(ByteVector32(ByteVector(Random.nextBytes(32))), bobPrivKey, Seq(alicePubKey, bobPubKey)) + val (aliceSecretNonce, alicePublicNonce) = Musig2.generateNonce(ByteVector32(ByteVector(Random.nextBytes(32))), Left(alicePrivKey), Seq(alicePubKey, bobPubKey), None, None) + val (bobSecretNonce, bobPublicNonce) = Musig2.generateNonce(ByteVector32(ByteVector(Random.nextBytes(32))), Right(bobPrivKey.publicKey), Seq(alicePubKey, bobPubKey), None, None) // Once they have each other's public nonce, they can produce partial signatures. val publicNonces = Seq(alicePublicNonce, bobPublicNonce) @@ -79,8 +80,8 @@ class Musig2Spec extends FunSuite { ) // 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 (userSecretNonce, userPublicNonce) = Musig2.generateNonce(ByteVector32(ByteVector(Random.nextBytes(32))), userPrivateKey, Seq(userPublicKey, serverPublicKey)) - val (serverSecretNonce, serverPublicNonce) = Musig2.generateNonce(ByteVector32(ByteVector(Random.nextBytes(32))), serverPrivateKey, Seq(userPublicKey, serverPublicKey)) + val (userSecretNonce, userPublicNonce) = Musig2.generateNonce(ByteVector32(ByteVector(Random.nextBytes(32))), Left(userPrivateKey), Seq(userPublicKey, serverPublicKey), None, None) + val (serverSecretNonce, serverPublicNonce) = Musig2.generateNonce(ByteVector32(ByteVector(Random.nextBytes(32))), Right(serverPrivateKey.publicKey), Seq(userPublicKey, serverPublicKey), None, None) // Once they have each other's public nonce, they can produce partial signatures. val publicNonces = Seq(userPublicNonce, serverPublicNonce) @@ -110,4 +111,9 @@ class Musig2Spec extends FunSuite { } } + test("generate nonce with counter") { + val sk = PrivateKey(ByteVector.fromValidHex("EEC1CB7D1B7254C5CAB0D9C61AB02E643D464A59FE6C96A7EFE871F07C5AEF54")) + val (_, pubnonce) = Musig2.generateNonceWithCounter(0, sk, Seq(sk.publicKey), None, None) + assert(pubnonce.getData.contentEquals(Hex.decode("0271efb262c0535e921efacacd30146fa93f193689e4974d5348fa9d909d90000702a049680ef3f6acfb12320297df31d3a634214491cbeebacef5acdf13f8f61cc2"))) + } }