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
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ plugins {
val currentOs = org.gradle.internal.os.OperatingSystem.current()

group = "fr.acinq.bitcoin"
version = "0.23.0"
version = "0.24.0"

repositories {
google()
Expand Down
26 changes: 21 additions & 5 deletions src/commonMain/kotlin/fr/acinq/bitcoin/crypto/musig2/Musig2.kt
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ public data class SecretNonce(internal val data: ByteVector) {
* @return secret nonce and the corresponding public nonce.
*/
@JvmStatic
public fun generateWithCounter(nonRepeatingCounter: ULong, privateKey: PrivateKey, message: ByteVector32?, keyAggCache: KeyAggCache?, extraInput: ByteVector32?): Pair<SecretNonce, IndividualNonce> {
val nonce = Secp256k1.musigNonceGenCounter(nonRepeatingCounter, privateKey.value.toByteArray(), message?.toByteArray(), keyAggCache?.toByteArray(), extraInput?.toByteArray())
public fun generateWithCounter(nonRepeatingCounter: Long, privateKey: PrivateKey, message: ByteVector32?, keyAggCache: KeyAggCache?, extraInput: ByteVector32?): Pair<SecretNonce, IndividualNonce> {
val nonce = Secp256k1.musigNonceGenCounter(nonRepeatingCounter.toULong(), privateKey.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)
Expand Down Expand Up @@ -236,16 +236,32 @@ public object Musig2 {
*/
@JvmStatic
public fun aggregateKeys(publicKeys: List<PublicKey>): XonlyPublicKey = KeyAggCache.create(publicKeys).first

/**
* @param sessionId a random, unique session ID.
* @param privateKey signer's private key
* @param publicKey signer's 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, privateKey: PrivateKey?, publicKey: PublicKey, publicKeys: List<PublicKey>, message: ByteVector32?, extraInput: ByteVector32?): Pair<SecretNonce, IndividualNonce> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to have a default null value for the optional parameters to simplify callers (message: ByteVector32? = null, extraInput: ByteVector32? = null)? Or do we rather want to strongly encourage providing those arguments and thus force callers to explicitly set them to null?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional parameters are not understood by Scala so in Eclair we would have to provide them all anyway, but we could have another generateNonce() methods without these parameters.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional parameters are not understood by Scala

Damn, I didn't realize that! It's probably not worth having explicit methods without those, let's just keep the existing code to encourage people to provide as much randomness as possible 👍

val (_, keyAggCache) = KeyAggCache.create(publicKeys)
return SecretNonce.generate(sessionId, privateKey, publicKey, message, keyAggCache, extraInput)
}

/**
* @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 (optional) message that will be signed, if already known.
* @param extraInput (optional) additional random data.
*/
@JvmStatic
public fun generateNonce(sessionId: ByteVector32, privateKey: PrivateKey, publicKeys: List<PublicKey>): Pair<SecretNonce, IndividualNonce> {
public fun generateNonceWithCounter(nonRepeatingCounter: Long, privateKey: PrivateKey, publicKeys: List<PublicKey>, message: ByteVector32?, extraInput: ByteVector32?): Pair<SecretNonce, IndividualNonce> {
val (_, keyAggCache) = KeyAggCache.create(publicKeys)
return SecretNonce.generate(sessionId, privateKey, privateKey.publicKey(), message = null, keyAggCache, extraInput = null)
return SecretNonce.generateWithCounter(nonRepeatingCounter, privateKey, message, keyAggCache, extraInput)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,21 +87,21 @@ class Musig2TestsCommon {
fun `generate secret nonce from counter`() {
val privateKey = PrivateKey.fromHex("EEC1CB7D1B7254C5CAB0D9C61AB02E643D464A59FE6C96A7EFE871F07C5AEF54")
run {
val nonce = SecretNonce.generateWithCounter(0UL, privateKey, null, null, null)
val nonce = SecretNonce.generateWithCounter(0L, privateKey, null, null, null)
assertEquals(ByteVector.fromHex("03A5B9B6907942EACDDA49A366016EC2E62404A1BF4AB6D4DB82067BC3ADF086D7033205DB9EB34D5C7CE02848CAC68A83ED73E3883477F563F23CE9A11A7721EC64"), nonce.second.data)

val nonce1 = SecretNonce.generateWithCounter(0UL, privateKey, null, null, null)
val nonce1 = SecretNonce.generateWithCounter(0L, privateKey, null, null, null)
assertEquals(nonce, nonce1)

val nonce2 = SecretNonce.generateWithCounter(0UL, PrivateKey.fromHex("EEC1CB7D1B7254C5CAB0D9C61AB02E643D464A59FE6C96A7EFE871F07C5AEF55"), null, null, null)
val nonce2 = SecretNonce.generateWithCounter(0L, PrivateKey.fromHex("EEC1CB7D1B7254C5CAB0D9C61AB02E643D464A59FE6C96A7EFE871F07C5AEF55"), null, null, null)
assertNotEquals(nonce, nonce2)
}
run {
val nonce = SecretNonce.generateWithCounter(0UL, privateKey, ByteVector32.fromValidHex("380CD17A198FC3DAD3B7DA7492941F46976F2702FF7C66F24F472036AF1DA3F9"), null, null)
val nonce = SecretNonce.generateWithCounter(0L, privateKey, ByteVector32.fromValidHex("380CD17A198FC3DAD3B7DA7492941F46976F2702FF7C66F24F472036AF1DA3F9"), null, null)
assertEquals(ByteVector.fromHex("0390B0553BA461A5BAC3F72BB86338D5FE8BB833ED7A21D3E21498C068D9A6E802020D641B37264FD22AC5E2F9FB868BAB49EB02FCB81AEC247FFD057DE37E1CB173"), nonce.second.data)
}
run {
val nonce = SecretNonce.generateWithCounter(1UL, privateKey, null, null, null)
val nonce = SecretNonce.generateWithCounter(1L, privateKey, null, null, null)
assertEquals(ByteVector.fromHex("0340A08273BBC9ED0A2BFBDBDAFCCB43073865643593988841F67E665864767047037844A24EC0B763CE73F8252445DDDDFB7CD10498D796AD7217B841882A3A9961"), nonce.second.data)
}
}
Expand Down Expand Up @@ -316,8 +316,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, listOf(alicePubKey, bobPubKey))
val bobNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), bobPrivKey, listOf(alicePubKey, bobPubKey))
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)

// 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 +354,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, listOf(alicePubKey, bobPubKey))
val bobNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), bobPrivKey, listOf(alicePubKey, bobPubKey))
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 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 +411,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, listOf(userPublicKey, serverPublicKey))
val serverNonce = Musig2.generateNonce(Random.Default.nextBytes(32).byteVector32(), serverPrivateKey, listOf(userPublicKey, serverPublicKey))
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)

// Once they have each other's public nonce, they can produce partial signatures.
val publicNonces = listOf(userNonce.second, serverNonce.second)
Expand Down