Musig2 Implementation (#4418)

* An initial (not yet working) implementation with test

* Added custom (non-bip-340) verification for now

* Made KeySet a case class

* Got MuSig2 working with BIP340 verification passing

* Responds to Ben's review

* Fixed hash tags and added parital signature verification

* Added point multiplication that allows infinity and did some refactoring

* Refactored type defs into case classes

* Added tests for more signers and fixed single-party bug

* Added key aggregation test vectors from BIP

* Added nonce generation test vectors from BIP

* Added nonce aggregation test vectors from BIP

* Made nonce aggregation test vectors pass by having MultiNoncePub wrap SecpPoints

* Added remaining static test vectors from BIP

* Implements tweaking support and adds tests, including all of the remaining BIP tests

* Added factory objects for nonce types

* Refactored things into multiple files with renaming and restructuring

* Some minor renaming

* Introduced ParityMultiplier ADT to remove unneccesary computations

* Added scaladocs

* Added messages to invariants

* Fixed a typo

* Nonce generation now takes a SchnorrPublicKey instead of raw bytes

* Made point multiplication more robust

* Responded to Ben nits

* Added musig.md
This commit is contained in:
Nadav Kohen 2022-07-06 12:59:13 -05:00 committed by GitHub
parent eae16a52f8
commit ae0962d7ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1429 additions and 1 deletions

View File

@ -0,0 +1,628 @@
package org.bitcoins.crypto.musig
import org.bitcoins.crypto.musig.MuSigUtil._
import org.bitcoins.crypto._
import org.scalacheck.Gen
import scodec.bits.ByteVector
class MuSigTest extends BitcoinSCryptoTest {
behavior of "MuSig2 Implementation"
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
generatorDrivenConfigNewCode
it should "work for a single party" in {
forAll(CryptoGenerators.privateKey, NumberGenerator.bytevector(32)) {
case (privKey, msg) =>
val pubKey = privKey.publicKey
val noncePriv: MuSigNoncePriv = MuSigNoncePriv.gen()
val noncePub: MuSigNoncePub = noncePriv.toPublicNonces
val keySet = KeySet(pubKey.schnorrPublicKey)
val aggMuSigNoncePub = MuSigNoncePub.aggregate(Vector(noncePub))
assert(aggMuSigNoncePub == noncePub)
val (aggNonce, s) =
sign(noncePriv, aggMuSigNoncePub, privKey, msg, keySet)
assert(
partialSigVerify(s,
noncePub,
aggMuSigNoncePub,
pubKey.schnorrPublicKey,
keySet,
msg))
val sig = signAgg(Vector(s), aggNonce)
assert(sig == SchnorrDigitalSignature(aggNonce.schnorrNonce, s))
val aggPub = keySet.aggPubKey
assert(
aggPub.schnorrPublicKey == pubKey
.multiply(keySet.keyAggCoef(pubKey.schnorrPublicKey))
.schnorrPublicKey)
assert(aggPub.schnorrPublicKey.verify(msg, sig))
}
}
it should "work for two parties" in {
forAll(CryptoGenerators.privateKey,
CryptoGenerators.privateKey,
NumberGenerator.bytevector(32)) { case (priv1, priv2, msg) =>
val pub1 = priv1.publicKey
val noncePriv1: MuSigNoncePriv = MuSigNoncePriv.gen()
val noncePub1: MuSigNoncePub = noncePriv1.toPublicNonces
val pub2 = priv2.publicKey
val noncePriv2: MuSigNoncePriv = MuSigNoncePriv.gen()
val noncePub2: MuSigNoncePub = noncePriv2.toPublicNonces
val keySet: KeySet = KeySet(pub1.schnorrPublicKey, pub2.schnorrPublicKey)
val aggMuSigNoncePub =
MuSigNoncePub.aggregate(Vector(noncePub1, noncePub2))
val (aggNonce1, s1) =
sign(noncePriv1, aggMuSigNoncePub, priv1, msg, keySet)
val (aggNonce2, s2) =
sign(noncePriv2, aggMuSigNoncePub, priv2, msg, keySet)
assert(aggNonce1 == aggNonce2)
assert(
partialSigVerify(s1,
noncePub1,
aggMuSigNoncePub,
pub1.schnorrPublicKey,
keySet,
msg))
assert(
partialSigVerify(s2,
noncePub2,
aggMuSigNoncePub,
pub2.schnorrPublicKey,
keySet,
msg))
val sig = signAgg(Vector(s1, s2), aggNonce1)
val aggPub = keySet.aggPubKey
assert(aggPub.schnorrPublicKey.verify(msg, sig))
}
}
it should "work for more parties" in {
val privKeysGen: Gen[Vector[ECPrivateKey]] = Gen
.choose[Int](2, 20)
.flatMap(n => Gen.listOfN(n, CryptoGenerators.privateKey))
.map(_.toVector)
forAll(
privKeysGen,
NumberGenerator.bytevector(32)
) { case (privKeysUnsorted, msg) =>
val keySet: KeySet = KeySet(privKeysUnsorted.map(_.schnorrPublicKey))
val privKeys = keySet.keys.map(pubKey =>
privKeysUnsorted.find(_.schnorrPublicKey == pubKey).get)
val noncePrivs = privKeys.map(_ => MuSigNoncePriv.gen())
val noncePubs = noncePrivs.map(_.toPublicNonces)
val aggMuSigNoncePub = MuSigNoncePub.aggregate(noncePubs)
val partialSigs: Vector[(ECPublicKey, FieldElement)] =
privKeys.zipWithIndex.map { case (privKey, i) =>
sign(noncePrivs(i), aggMuSigNoncePub, privKey, msg, keySet)
}
// All aggregate nonces are the same
assert(partialSigs.map(_._1).forall(_ == partialSigs.head._1))
// All partial sigs are valid
assert(partialSigs.map(_._2).zipWithIndex.forall { case (s, i) =>
partialSigVerify(s,
noncePubs(i),
aggMuSigNoncePub,
privKeys(i).schnorrPublicKey,
keySet,
msg)
})
val sig = signAgg(partialSigs.map(_._2), partialSigs.head._1)
val aggPub = keySet.aggPubKey
assert(aggPub.schnorrPublicKey.verify(msg, sig))
}
}
it should "work with tweaks" in {
val privKeysGen: Gen[Vector[ECPrivateKey]] = Gen
.choose[Int](2, 20)
.flatMap(n => Gen.listOfN(n, CryptoGenerators.privateKey))
.map(_.toVector)
val tweaksGen: Gen[Vector[MuSigTweak]] = Gen
.choose[Int](0, 10)
.flatMap(n =>
Gen.listOfN(n,
CryptoGenerators.fieldElement.flatMap(x =>
NumberGenerator.bool.map((x, _)))))
.map(_.toVector)
.map(_.map { case (x, b) => MuSigTweak(x, b) })
forAll(
privKeysGen,
NumberGenerator.bytevector(32),
tweaksGen
) { case (privKeysUnsorted, msg, tweaks) =>
val keySet: KeySet =
KeySet(privKeysUnsorted.map(_.schnorrPublicKey), tweaks)
val privKeys = keySet.keys.map(pubKey =>
privKeysUnsorted.find(_.schnorrPublicKey == pubKey).get)
val noncePrivs = privKeys.map(_ => MuSigNoncePriv.gen())
val noncePubs = noncePrivs.map(_.toPublicNonces)
val aggMuSigNoncePub = MuSigNoncePub.aggregate(noncePubs)
val partialSigs: Vector[(ECPublicKey, FieldElement)] =
privKeys.zipWithIndex.map { case (privKey, i) =>
sign(noncePrivs(i), aggMuSigNoncePub, privKey, msg, keySet)
}
// All aggregate nonces are the same
assert(partialSigs.map(_._1).forall(_ == partialSigs.head._1))
// All partial sigs are valid
assert(partialSigs.map(_._2).zipWithIndex.forall { case (s, i) =>
partialSigVerify(s,
noncePubs(i),
aggMuSigNoncePub,
privKeys(i).schnorrPublicKey,
keySet,
msg)
})
val sig = signAgg(partialSigs.map(_._2), aggMuSigNoncePub, keySet, msg)
val aggPub = keySet.aggPubKey
assert(aggPub.schnorrPublicKey.verify(msg, sig))
}
}
// https://github.com/jonasnick/bips/blob/263a765a77e20efe883ed3b28dc155a0d8c7d61a/bip-musig2/reference.py#L391
it should "pass key aggregation test vectors" in {
val inputs = Vector(
"F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
"3590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66"
).map(SchnorrPublicKey.fromHex)
val expected =
Vector(
"E5830140512195D74C8307E39637CBE5FB730EBEAB80EC514CF88A877CEEEE0B",
"D70CD69A2647F7390973DF48CBFA2CCC407B8B2D60B08C5F1641185C7998A290",
"81A8B093912C9E481408D09776CEFB48AEB8B65481B6BAAFB3C5810106717BEB",
"2EB18851887E7BDC5E830E89B19DDBC28078F1FA88AAD0AD01CA06FE4F80210B"
).map(SchnorrPublicKey.fromHex)
// Vector 1
assert(UnsortedKeySet(inputs).aggPubKey.schnorrPublicKey == expected(0))
// Vector 2
assert(
UnsortedKeySet(inputs.reverse).aggPubKey.schnorrPublicKey == expected(1))
// Vector 3
assert(
UnsortedKeySet(
Vector.fill(3)(inputs(0))).aggPubKey.schnorrPublicKey == expected(2))
// Vector 4
assert(
UnsortedKeySet(
Vector(inputs(0),
inputs(0),
inputs(1),
inputs(1))).aggPubKey.schnorrPublicKey == expected(3))
// The following errors must be handled by the caller as we can't even represent them
// Vector 5
assertThrows[IllegalArgumentException](
SchnorrPublicKey.fromHex(
"0000000000000000000000000000000000000000000000000000000000000005"))
// Vector 6
assertThrows[IllegalArgumentException](
SchnorrPublicKey.fromHex(
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30"))
// Vector 7
assertThrows[IllegalArgumentException](
MuSigTweak(
FieldElement(
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"),
isXOnlyT = true))
// Vector 8
val schnorrG = CryptoParams.getG.schnorrPublicKey
val keySetNoTweak = KeySet(schnorrG)
val coeff = keySetNoTweak.keyAggCoef(schnorrG).negate
val keySet =
keySetNoTweak.withTweaks(Vector(MuSigTweak(coeff, isXOnlyT = false)))
assertThrows[Exception](keySet.aggPubKey)
}
// https://github.com/jonasnick/bips/blob/263a765a77e20efe883ed3b28dc155a0d8c7d61a/bip-musig2/reference.py#L436
it should "pass nonce generation test vectors" in {
val rand = ByteVector.fill(32)(0)
val msg = Some(ByteVector.fill(32)(1))
val sk = Some(ECPrivateKey(ByteVector.fill(32)(2)))
val aggpk = Some(SchnorrPublicKey(ByteVector.fill(32)(7)))
val extraIn = Some(ByteVector.fill(32)(8))
val expected = Vector(
"E8F2E103D86800F19A4E97338D371CB885DB2F19D08C0BD205BBA9B906C971D0" ++
"D786A17718AAFAD6DE025DDDD99DC823E2DFC1AE1DDFE920888AD53FFF423FC4",
"8A633F5EECBDB690A6BE4921426F41BE78D509DC1CE894C1215844C0E4C6DE7A" ++
"BC9A5BE0A3BF3FE312CCB7E4817D2CB17A7CEA8382B73A99A583E323387B3C32",
"7B3B5A002356471AF0E961DE2549C121BD0D48ABCEEDC6E034BDDF86AD3E0A18" ++
"7ECEE674CEF7364B0BC4BEEFB8B66CAD89F98DE2F8C5A5EAD5D1D1E4BD7D04CD"
).map(MuSigNoncePriv.fromHex)
val nonce1 = MuSigNoncePriv.genInternal(rand, sk, aggpk, msg, extraIn)
// Vector 1
assert(nonce1 == expected(0))
val nonce2 =
MuSigNoncePriv.genInternal(rand, sk, aggpk, msgOpt = None, extraIn)
// Vector 2
assert(nonce2 == expected(1))
val nonce3 = MuSigNoncePriv.genInternal(rand)
// Vector 3
assert(nonce3 == expected(2))
}
// https://github.com/jonasnick/bips/blob/263a765a77e20efe883ed3b28dc155a0d8c7d61a/bip-musig2/reference.py#L461
it should "pass nonce aggregation test vectors" in {
val pnonce = Vector(
"020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E666" ++
"03BA47FBC1834437B3212E89A84D8425E7BF12E0245D98262268EBDCB385D50641",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A6" ++
"0248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833"
).map(MuSigNoncePub.fromHex)
val expected = MuSigNoncePub(
"035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B" ++
"024725377345BDE0E9C33AF3C43C0A29A9249F2F2956FA8CFEB55C8573D0262DC8"
)
// Vector 1
assert(MuSigNoncePub.aggregate(pnonce) == expected)
// The following errors must be handled by the caller as we can't even represent them
// Vector 2
assertThrows[IllegalArgumentException](
MuSigNoncePub(
"04FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A6" ++
"0248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833"))
// Vector 3
assertThrows[IllegalArgumentException](
MuSigNoncePub(
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A6" ++
"0248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B831"))
// Vector 4
assertThrows[IllegalArgumentException](
MuSigNoncePub(
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A6" ++
"02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30"))
// Vector 5
val g = CryptoParams.getG.toPoint
val negG = g.multiply(FieldElement.orderMinusOne)
val pnonce1 = MuSigNoncePub(Vector(pnonce.head.pubNonces.head, g))
val pnonce2 = MuSigNoncePub(Vector(pnonce.last.pubNonces.head, negG))
val expected5 =
MuSigNoncePub(expected.bytes.take(33) ++ MuSigNoncePub.infPtBytes)
assert(MuSigNoncePub.aggregate(Vector(pnonce1, pnonce2)) == expected5)
}
// https://github.com/jonasnick/bips/blob/263a765a77e20efe883ed3b28dc155a0d8c7d61a/bip-musig2/reference.py#L504
it should "pass signing and verification test vectors" in {
val remotePubKeys = Vector(
"F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"
).map(SchnorrPublicKey.fromHex)
val privNonce = MuSigNoncePriv(
"508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61" ++
"FA27FD49B1D50085B481285E1CA205D55C82CC1B31FF5CD54A489829355901F7")
val pubNonces = Vector(
"0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA" ++
"0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
"0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" ++
"0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE93" ++
"03E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046",
"0237C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA" ++
"0387BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480"
).map(MuSigNoncePub.fromHex)
assert(privNonce.toPublicNonces == pubNonces.head)
val aggNonce = MuSigNoncePub(
"028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61" ++
"037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9")
assert(aggNonce == MuSigNoncePub.aggregate(pubNonces.take(3)))
val sk = ECPrivateKey(
"7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671")
val pk = sk.schnorrPublicKey
val msg = ByteVector.fromValidHex(
"F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF")
val expected = Vector(
"68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B",
"2DF67BFFF18E3DE797E13C6475C963048138DAEC5CB20A357CECA7C8424295EA",
"0D5B651E6DE34A29A12DE7A8B4183B4AE6A7F7FBE15CDCAFA4A3D1BCAABC7517",
"8D5E0407FB4756EEBCD86264C32D792EE36EEB69E952BBB30B8E41BEBC4D22FA"
).map(FieldElement.fromHex)
// Vector 1
val keySet1 = UnsortedKeySet(Vector(pk, remotePubKeys(0), remotePubKeys(1)))
assert(sign(privNonce, aggNonce, sk, msg, keySet1)._2 == expected(0))
// Vector 2
val keySet2 = UnsortedKeySet(Vector(remotePubKeys(0), pk, remotePubKeys(1)))
assert(sign(privNonce, aggNonce, sk, msg, keySet2)._2 == expected(1))
// Vector 3
val keySet3 = UnsortedKeySet(Vector(remotePubKeys(0), remotePubKeys(1), pk))
assert(sign(privNonce, aggNonce, sk, msg, keySet3)._2 == expected(2))
// Vector 4
val infAggNonce =
MuSigNoncePub.aggregate(Vector(pubNonces(0), pubNonces(3)))
assert(infAggNonce.pubNonces.forall(_ == SecpPointInfinity))
val keySet4 = UnsortedKeySet(Vector(pk, remotePubKeys(0)))
assert(sign(privNonce, infAggNonce, sk, msg, keySet4)._2 == expected(3))
// The following errors must be handled by the caller as we can't even represent them
// Vector 5, 17
assertThrows[IllegalArgumentException](
SchnorrPublicKey(
"0000000000000000000000000000000000000000000000000000000000000007"))
// Vector 6
assertThrows[IllegalArgumentException](
MuSigNoncePub(
"048465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61" ++
"037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9"))
// Vector 7
assertThrows[IllegalArgumentException](
MuSigNoncePub(
"028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61" ++
"020000000000000000000000000000000000000000000000000000000000000009"))
// Vector 8
assertThrows[IllegalArgumentException](
MuSigNoncePub(
"028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61" ++
"02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30"))
// Verification test vectors
// Vector 9
assert(
partialSigVerify(expected(0),
pubNonces.take(3),
keySet1,
msg,
signerIndex = 0))
// Vector 10
assert(
partialSigVerify(expected(1),
Vector(pubNonces(1), pubNonces(0), pubNonces(2)),
keySet2,
msg,
signerIndex = 1))
// Vector 11
assert(
partialSigVerify(expected(2),
Vector(pubNonces(1), pubNonces(2), pubNonces(0)),
keySet3,
msg,
signerIndex = 2))
// Vector 12
assert(
partialSigVerify(expected(3),
Vector(pubNonces(0), pubNonces(3)),
keySet4,
msg,
signerIndex = 0))
// Vector 13
val badSig = FieldElement(
"97AC833ADCB1AFA42EBF9E0725616F3C9A0D5B614F6FE283CEAAA37A8FFAF406")
assert(!partialSigVerify(badSig, pubNonces, keySet1, msg, signerIndex = 0))
// Vector 14
assert(
!partialSigVerify(expected(0),
pubNonces.take(3),
keySet1,
msg,
signerIndex = 1))
// The following errors must be handled by the caller as we can't even represent them
// Vector 15
assertThrows[IllegalArgumentException](
FieldElement(
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"))
// Vector 16
assertThrows[IllegalArgumentException](
MuSigNoncePub(
"020000000000000000000000000000000000000000000000000000000000000009" ++
pubNonces.head.bytes.drop(33).toHex))
}
// https://github.com/jonasnick/bips/blob/musig2/bip-musig2/reference.py#L629
it should "pass tweak test vectors" in {
val pubKeys = Vector(
"F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"
).map(SchnorrPublicKey.fromHex)
val secnonce = MuSigNoncePriv(
"508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61" ++
"FA27FD49B1D50085B481285E1CA205D55C82CC1B31FF5CD54A489829355901F7")
val pnonce = Vector(
"0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA" ++
"0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
"0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" ++
"0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE93" ++
"03E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046"
).map(MuSigNoncePub.fromHex)
val aggnonce = MuSigNoncePub(
"028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61" ++
"037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9")
val sk = ECPrivateKey(
"7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671")
val msg = ByteVector.fromValidHex(
"F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF")
val tweaks = Vector(
"E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB",
"AE2EA797CC0FE72AC5B97B97F3C6957D7E4199A167A58EB08BCAFFDA70AC0455",
"F52ECBC565B3D8BEA2DFD5B75A4F457E54369809322E4120831626F290FA87E0",
"1969AD73CC177FA0B4FCED6DF1F7BF9907E665FDE9BA196A74FED0A3CF5AEF9D"
).map(FieldElement.fromHex)
val expected = Vector(
"5E24C7496B565DEBC3B9639E6F1304A21597F9603D3AB05B4913641775E1375B",
"78408DDCAB4813D1394C97D493EF1084195C1D4B52E63ECD7BC5991644E44DDD",
"C3A829A81480E36EC3AB052964509A94EBF34210403D16B226A6F16EC85B7357",
"8C4473C6A382BD3C4AD7BE59818DA5ED7CF8CEC4BC21996CFDA08BB4316B8BC7"
).map(FieldElement.fromHex)
val pk = sk.schnorrPublicKey
val keySet = UnsortedKeySet(Vector(pubKeys(0), pubKeys(1), pk))
val pnonces = Vector(pnonce(1), pnonce(2), pnonce(0))
// Vector 1
val tweaks1 = Vector(MuSigTweak(tweaks(0), isXOnlyT = true))
val keySet1 = keySet.withTweaks(tweaks1)
assert(sign(secnonce, aggnonce, sk, msg, keySet1)._2 == expected(0))
assert(
partialSigVerify(expected(0), pnonces, keySet1, msg, signerIndex = 2))
// Vector 2
val tweaks2 = Vector(MuSigTweak(tweaks(0), isXOnlyT = false))
val keySet2 = keySet.withTweaks(tweaks2)
assert(sign(secnonce, aggnonce, sk, msg, keySet2)._2 == expected(1))
assert(
partialSigVerify(expected(1), pnonces, keySet2, msg, signerIndex = 2))
// Vector 3
val tweaks3 = Vector(MuSigTweak(tweaks(0), isXOnlyT = false),
MuSigTweak(tweaks(1), isXOnlyT = true))
val keySet3 = keySet.withTweaks(tweaks3)
assert(sign(secnonce, aggnonce, sk, msg, keySet3)._2 == expected(2))
assert(
partialSigVerify(expected(2), pnonces, keySet3, msg, signerIndex = 2))
// Vector 4
val tweaks4 = Vector(MuSigTweak(tweaks(0), isXOnlyT = true),
MuSigTweak(tweaks(1), isXOnlyT = false),
MuSigTweak(tweaks(2), isXOnlyT = true),
MuSigTweak(tweaks(3), isXOnlyT = false))
val keySet4 = keySet.withTweaks(tweaks4)
assert(sign(secnonce, aggnonce, sk, msg, keySet4)._2 == expected(3))
assert(
partialSigVerify(expected(3), pnonces, keySet4, msg, signerIndex = 2))
// The following error must be handled by the caller as we can't even represent it
// Vector 5
assertThrows[IllegalArgumentException](
MuSigTweak(
FieldElement(
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"),
isXOnlyT = false))
}
// https://github.com/jonasnick/bips/blob/263a765a77e20efe883ed3b28dc155a0d8c7d61a/bip-musig2/reference.py#L702
it should "pass signature aggregation test vectors" in {
val pubKeys = Vector(
"487D1B83B41B4CBBD07A111F1BBC7BDC8864CFEF5DBF96E46E51C68399B0BEF6",
"4795C22501BF534BC478FF619407A7EC9E8D8883646D69BD43A0728944EA802F",
"0F5BE837F3AB7E7FEFF1FAA44D673C2017206AE836D2C7893CDE4ACB7D55EDEB",
"0FD453223E444FCA91FB5310990AE8A0C5DAA14D2A4C8944E1C0BC80C30DF682"
).map(SchnorrPublicKey.fromHex)
val aggNonce = Vector(
"024FA51009A56F0D6DF737131CE1FBBD833797AF3B4FE6BF0D68F4D49F68B0947E" ++
"0248FB3BB9191F0CFF13806A3A2F1429C23012654FCE4E41F7EC9169EAA6056B21",
"023B11E63E2460E5E0F1561BB700FEA95B991DD9CA2CBBE92A3960641FA7469F67" ++
"02CA4CD38375FE8BEB857C770807225BFC7D712F42BA896B83FC71138E56409B21",
"03F98BEAA32B8A38FE3797C4E813DC9CE05ADBE32200035FB37EB0A030B735E9B6" ++
"030E6118EC98EA2BA7A358C2E38E7E13E63681EEB683E067061BF7D52DCF08E615",
"026491FBCFD47148043A0F7310E62EF898C10F2D0376EE6B232EAAD36F3C2E29E3" ++
"03020CB17D168908E2904DE2EB571CD232CA805A6981D0F86CDBBD2F12BD91F6D0",
"000000000000000000000000000000000000000000000000000000000000000000" ++
"000000000000000000000000000000000000000000000000000000000000000000"
).map(MuSigNoncePub.fromHex)
val msg = ByteVector.fromValidHex(
"599C67EA410D005B9DA90817CF03ED3B1C868E4DA4EDF00A5880B0082C237869")
val tweaks = Vector(
"B511DA492182A91B0FFB9A98020D55F260AE86D7ECBD0399C7383D59A5F2AF7C",
"A815FE049EE3C5AAB66310477FBC8BCCCAC2F3395F59F921C364ACD78A2F48DC",
"75448A87274B056468B977BE06EB1E9F657577B7320B0A3376EA51FD420D18A8"
).map(FieldElement.fromHex)
val psig = Vector(
"E5C1CBD6E7E89FE9EE30D5F3B6D06B9C218846E4A1DEF4EE851410D51ABBD850",
"9BC470F7F1C9BC848BDF179B0023282FFEF40908E0EF88459784A4355FC86D0C",
"D5D8A09929BA264B2F5DF15ACA1CF2DEFA47C048DF0C3232E965FFE2F2831B1D",
"A915197503C1051EA77DC91F01C3A0E60BFD64473BD536CB613F9645BD61C843",
"99A144D7076A128022134E036B8BDF33811F7EAED9A1E48549B46D8A63D64DC9",
"716A72A0C1E531EBB4555C8E29FD35C796F4F231C3B039193D7E8D7AEFBDF5F7",
"06B6DD04BC0F1EF740916730AD7DAC794255B161221719765BDE9686A26633DC",
"BF6D85D4930062726EBC6EBB184AFD68DBB3FED159C501989690A62600D6FBAB"
).map(FieldElement.fromHex)
val expected = Vector(
"4006D4D069F3B51E968762FF8074153E278E5BCD221AABE0743CA001B77E79F5" ++
"81863CCED9B25C6E7A0FED8EB6F393CD65CD7306D385DCF85CC6567DAA4E041B",
"98BCD40DFD94B47A3DA37D7B78EB6CCE8ABEACA23C3ADE6F4678902410EB35C6" ++
"7EEDBA0E2D7B2B69D6DBBA79CBE093C64B9647A96B98C8C28AD3379BDFAEA21F",
"3741FEDCCDD7508B58DCB9A780FF5D97452EC8C0448D8C97004EA7175C14F200" ++
"7A54D1DE356EBA6719278436EF111DFA8F1B832368371B9B7A25001709039679",
"F4B3DA3CF0D0F7CF5C1840593BF1A1A415DA341619AE848F2210696DC8C75125" ++
"40962C84EF7F0CEC491065F2D577213CF10E8A63D153297361B3B172BE27B61F"
).map(SchnorrDigitalSignature.fromHex)
// Vector 1
val keySet1 = UnsortedKeySet(pubKeys.take(2))
val sig1 = signAgg(psig.take(2), aggNonce(0), keySet1, msg)
assert(sig1 == expected(0))
assert(keySet1.aggPubKey.schnorrPublicKey.verify(msg, sig1))
// Vector 2
val keySet2 = UnsortedKeySet(Vector(pubKeys(0), pubKeys(2)))
val sig2 = signAgg(psig.slice(2, 4), aggNonce(1), keySet2, msg)
assert(sig2 == expected(1))
assert(keySet2.aggPubKey.schnorrPublicKey.verify(msg, sig2))
// Vector 3
val tweaks3 = Vector(MuSigTweak(tweaks(0), isXOnlyT = false))
val keySet3 = UnsortedKeySet(Vector(pubKeys(0), pubKeys(2)), tweaks3)
val sig3 = signAgg(psig.slice(4, 6), aggNonce(2), keySet3, msg)
assert(sig3 == expected(2))
assert(keySet3.aggPubKey.schnorrPublicKey.verify(msg, sig3))
// Vector 4
val tweaks4 = Vector(MuSigTweak(tweaks(0), isXOnlyT = true),
MuSigTweak(tweaks(1), isXOnlyT = false),
MuSigTweak(tweaks(2), isXOnlyT = true))
val keySet4 = UnsortedKeySet(Vector(pubKeys(0), pubKeys(3)), tweaks4)
val sig4 = signAgg(psig.slice(6, 8), aggNonce(3), keySet4, msg)
assert(sig4 == expected(3))
assert(keySet4.aggPubKey.schnorrPublicKey.verify(msg, sig4))
// The following error must be handled by the caller as we can't even represent it
// Vector 5
assertThrows[IllegalArgumentException](
FieldElement(
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"))
}
}

View File

@ -211,6 +211,26 @@ trait CryptoRuntime {
def tweakMultiply(publicKey: ECPublicKey, tweak: FieldElement): ECPublicKey
def tweakMultiply(point: SecpPoint, tweak: FieldElement): SecpPoint = {
point match {
case SecpPointInfinity => SecpPointInfinity
case pt: SecpPointFinite =>
val pub = pt.toPublicKey
Try(tweakMultiply(pub, tweak)) match {
case Failure(err) =>
/* If tweak*pub = 0, then (tweak - 1)*pub + pub = 0
* so (tweak - 1)*pub = -pub. Otherwise err is unrelated.
* (Noting that it is impossible to have
* tweak*pub = 0 = (tweak - 1)*pub for pub != 0)
*/
val pubNeg = tweakMultiply(pub, tweak.subtract(FieldElement.one))
if (pubNeg == pub.negate) SecpPointInfinity
else throw err
case Success(multKey) => multKey.toPoint
}
}
}
def add(pk1: ECPrivateKey, pk2: ECPrivateKey): ECPrivateKey =
pk1.fieldElement.add(pk2.fieldElement).toPrivateKey

View File

@ -398,6 +398,10 @@ case class ECPublicKey(private val _bytes: ByteVector)
def toXOnly: XOnlyPubKey = XOnlyPubKey(bytes.drop(1))
def parity: KeyParity = KeyParity.fromByte(bytes.head)
def negate: ECPublicKey = {
ECPublicKey.fromBytes(parity.negate.bytes ++ bytes.drop(1))
}
}
object ECPublicKey extends Factory[ECPublicKey] {

View File

@ -5,6 +5,13 @@ import scodec.bits.ByteVector
sealed trait KeyParity extends NetworkElement {
def isOdd: Boolean = this == OddParity
def isEven: Boolean = this == EvenParity
def negate: KeyParity = {
this match {
case EvenParity => OddParity
case OddParity => EvenParity
}
}
}
object KeyParity extends Factory[KeyParity] {

View File

@ -23,3 +23,10 @@ trait NetworkElement extends Any {
/** The byte representation of the NetworkElement in little endian */
def bytesLE: ByteVector = bytes.reverse
}
object NetworkElement {
val lexicographicalOrdering: Ordering[NetworkElement] = {
Ordering.by[NetworkElement, String](_.hex)
}
}

View File

@ -11,6 +11,17 @@ sealed trait SecpPoint extends NetworkElement {
def add(point: SecpPoint): SecpPoint = {
CryptoUtil.add(this, point)
}
def multiply(fieldElement: FieldElement): SecpPoint = {
CryptoUtil.tweakMultiply(this, fieldElement)
}
def negate: SecpPoint = {
this match {
case SecpPointInfinity => SecpPointInfinity
case p: SecpPointFinite => p.toPublicKey.negate.toPoint
}
}
}
/** The point at infinity, this is the secp256k1 group identity element meaning

View File

@ -0,0 +1,116 @@
package org.bitcoins.crypto.musig
import org.bitcoins.crypto.{
ECPublicKey,
FieldElement,
NetworkElement,
SchnorrPublicKey
}
import scodec.bits.ByteVector
/** Represents an ordered set of MuSig signers and their tweaks.
* This is the data required to (non-interactively) compute the aggPubKey.
*/
trait KeySet {
def keys: Vector[SchnorrPublicKey]
def tweaks: Vector[MuSigTweak]
def withTweaks(newTweaks: Vector[MuSigTweak]): KeySet = {
require(tweaks.isEmpty, "withTweaks is not meant for replacing tweaks")
this match {
case ks: LexicographicKeySet => ks.copy(tweaks = newTweaks)
case ks: UnsortedKeySet => ks.copy(tweaks = newTweaks)
}
}
def length: Int = keys.length
def apply(i: Int): SchnorrPublicKey = {
keys(i)
}
lazy val serialize: ByteVector = {
keys.map(_.bytes).reduce(_ ++ _)
}
/** Returns the coefficient of the given key in the aggPubKey sum */
def keyAggCoef(key: SchnorrPublicKey): FieldElement = {
if (secondKeyOpt.contains(key)) FieldElement.one
else {
val listHashBytes = MuSigUtil.aggListHash(serialize)
val bytes = MuSigUtil.aggCoefHash(listHashBytes ++ key.bytes)
FieldElement(new java.math.BigInteger(1, bytes.toArray))
}
}
private lazy val computeAggPubKeyAndTweakContext: (
ECPublicKey,
MuSigTweakContext) = {
val untweakedAggPubKey = keys
.map { key =>
val coef = keyAggCoef(key)
key.publicKey.multiply(coef)
}
.reduce(_.add(_))
tweaks.foldLeft((untweakedAggPubKey, MuSigTweakContext.empty)) {
case ((pubKeySoFar, context), tweak) =>
context.applyTweak(tweak, pubKeySoFar)
}
}
/** The aggregate public key that represents the n-of-n signers */
lazy val aggPubKey: ECPublicKey = computeAggPubKeyAndTweakContext._1
/** Accumulated tweak information */
lazy val tweakContext: MuSigTweakContext =
computeAggPubKeyAndTweakContext._2
/** The first key different from the keys.head,
* optimized MuSig2 allows this key to have coefficient 1
*/
lazy val secondKeyOpt: Option[SchnorrPublicKey] = {
keys.find(_ != keys.head)
}
}
object KeySet {
def apply(keys: Vector[SchnorrPublicKey]): LexicographicKeySet = {
val sortedKeys = keys.sorted(NetworkElement.lexicographicalOrdering)
LexicographicKeySet(sortedKeys)
}
def apply(keys: SchnorrPublicKey*): LexicographicKeySet = {
KeySet(keys.toVector)
}
def apply(
keys: Vector[SchnorrPublicKey],
tweaks: Vector[MuSigTweak]): LexicographicKeySet = {
val sortedKeys = keys.sorted(NetworkElement.lexicographicalOrdering)
LexicographicKeySet(sortedKeys, tweaks)
}
}
/** The default way of ordering a KeySet is lexicographically */
case class LexicographicKeySet(
override val keys: Vector[SchnorrPublicKey],
override val tweaks: Vector[MuSigTweak] = Vector.empty)
extends KeySet {
keys.init.zip(keys.tail).foreach { case (key1, key2) =>
require(key1.hex.compareTo(key2.hex) <= 0,
"Keys must be sorted lexicographically")
}
}
/** This represents an arbitrary KeySet, for use in tests.
* If you have a non-lexicographical order, extend KeySet.
*/
case class UnsortedKeySet(
override val keys: Vector[SchnorrPublicKey],
override val tweaks: Vector[MuSigTweak] = Vector.empty)
extends KeySet

View File

@ -0,0 +1,112 @@
package org.bitcoins.crypto.musig
import org.bitcoins.crypto._
import scodec.bits.ByteVector
/** Wraps the ephemeral private keys making up a MuSig2 nonce */
case class MuSigNoncePriv(privNonces: Vector[ECPrivateKey])
extends NetworkElement {
require(privNonces.length == MuSigUtil.nonceNum,
s"Exactly ${MuSigUtil.nonceNum} keys are expected, found $privNonces")
def toPublicNonces: MuSigNoncePub = {
MuSigNoncePub(privNonces.map(_.publicKey.toPoint))
}
def toFieldElements: Vector[FieldElement] = {
privNonces.map(_.fieldElement)
}
def length: Int = privNonces.length
override def bytes: ByteVector = {
privNonces.map(_.bytes).reduce(_ ++ _)
}
def negate: MuSigNoncePriv = {
MuSigNoncePriv(privNonces.map(_.negate))
}
/** Collapses this into a single ephemeral private key */
def sumToKey(b: FieldElement): FieldElement = {
MuSigUtil.nonceSum[FieldElement](toFieldElements,
b,
_.add(_),
_.multiply(_),
FieldElement.zero)
}
}
object MuSigNoncePriv extends Factory[MuSigNoncePriv] {
override def fromBytes(bytes: ByteVector): MuSigNoncePriv = {
val privs = bytes.toArray
.grouped(32)
.toVector
.map(ByteVector(_))
.map(ECPrivateKey.fromBytes)
MuSigNoncePriv(privs)
}
/** Generates a MuSigNoncePriv given 32 bytes of entropy from preRand,
* and possibly some other sources, as specified in the BIP.
*/
def genInternal(
preRand: ByteVector,
privKeyOpt: Option[ECPrivateKey] = None,
aggPubKeyOpt: Option[SchnorrPublicKey] = None,
msgOpt: Option[ByteVector] = None,
extraInOpt: Option[ByteVector] = None): MuSigNoncePriv = {
require(preRand.length == 32,
s"32 bytes of entropy must be provided, found $preRand")
require(msgOpt.forall(msg => msg.length == 32),
s"The message to be signed must be 32 bytes, found $msgOpt")
require(
extraInOpt.forall(_.length <= 4294967295L),
"extraIn too long, its length must be represented by at most four bytes")
def serializeWithLen(
bytesOpt: Option[ByteVector],
lengthSize: Int = 1): ByteVector = {
bytesOpt match {
case Some(bytes) =>
ByteVector.fromLong(bytes.length, lengthSize) ++ bytes
case None => ByteVector.fromLong(0, lengthSize)
}
}
val rand = privKeyOpt match {
case Some(privKey) => MuSigUtil.auxHash(preRand).xor(privKey.bytes)
case None => preRand
}
val aggPubKeyBytes = serializeWithLen(aggPubKeyOpt.map(_.bytes))
val msgBytes = serializeWithLen(msgOpt)
val extraInBytes = serializeWithLen(extraInOpt, lengthSize = 4)
val dataBytes = rand ++ aggPubKeyBytes ++ msgBytes ++ extraInBytes
val privNonceKeys = 0.until(MuSigUtil.nonceNum).toVector.map { index =>
val indexByte = ByteVector.fromByte(index.toByte)
val noncePreBytes = MuSigUtil.nonHash(dataBytes ++ indexByte)
val noncePreNum = new java.math.BigInteger(1, noncePreBytes.toArray)
FieldElement(noncePreNum).toPrivateKey
}
MuSigNoncePriv(privNonceKeys)
}
/** Generates 32 bytes of entropy and contructs a MuSigNoncePriv from this,
* and possibly some other sources, as specified in the BIP.
*/
def gen(
privKeyOpt: Option[ECPrivateKey] = None,
aggPubKeyOpt: Option[SchnorrPublicKey] = None,
msgOpt: Option[ByteVector] = None,
extraInOpt: Option[ByteVector] = None): MuSigNoncePriv = {
val preRand = CryptoUtil.randomBytes(32)
genInternal(preRand, privKeyOpt, aggPubKeyOpt, msgOpt, extraInOpt)
}
}

View File

@ -0,0 +1,66 @@
package org.bitcoins.crypto.musig
import org.bitcoins.crypto._
import scodec.bits.ByteVector
/** Wraps the ephemeral points making up a MuSig2 nonce */
case class MuSigNoncePub(pubNonces: Vector[SecpPoint]) extends NetworkElement {
require(pubNonces.length == MuSigUtil.nonceNum,
s"Exactly ${MuSigUtil.nonceNum} keys are expected, found $pubNonces")
def apply(i: Int): SecpPoint = {
pubNonces(i)
}
def length: Int = pubNonces.length
override def bytes: ByteVector = {
pubNonces
.map {
case SecpPointInfinity => MuSigNoncePub.infPtBytes
case p: SecpPointFinite => p.toPublicKey.bytes
}
.reduce(_ ++ _)
}
/** Collapses this into a single ephemeral public key */
def sumToKey(b: FieldElement): ECPublicKey = {
MuSigUtil.nonceSum[SecpPoint](pubNonces,
b,
_.add(_),
_.multiply(_),
SecpPointInfinity) match {
case SecpPointInfinity => CryptoParams.getG
case p: SecpPointFinite => p.toPublicKey
}
}
}
object MuSigNoncePub extends Factory[MuSigNoncePub] {
/** In the BIP, the point at infinity is serialized as 33 0x00 bytes */
val infPtBytes: ByteVector = ByteVector.low(33)
override def fromBytes(bytes: ByteVector): MuSigNoncePub = {
val pubs =
bytes.toArray
.grouped(33)
.toVector
.map(ByteVector(_))
.map { b =>
if (b == infPtBytes) SecpPointInfinity
else ECPublicKey.fromBytes(b).toPoint
}
MuSigNoncePub(pubs)
}
/** Sums the given nonces and returns the aggregate MuSigNoncePub */
def aggregate(nonces: Vector[MuSigNoncePub]): MuSigNoncePub = {
val aggNonceKeys = 0.until(MuSigUtil.nonceNum).toVector.map { i =>
nonces.map(multiNonce => multiNonce(i)).reduce(_.add(_))
}
MuSigNoncePub(aggNonceKeys)
}
}

View File

@ -0,0 +1,10 @@
package org.bitcoins.crypto.musig
import org.bitcoins.crypto.{ECPublicKey, FieldElement}
/** Used to tweak a MuSig aggregate public key, as defined here
* https://github.com/jonasnick/bips/blob/musig2/bip-musig2.mediawiki#tweaking-definition
*/
case class MuSigTweak(tweak: FieldElement, isXOnlyT: Boolean) {
def point: ECPublicKey = tweak.getPublicKey
}

View File

@ -0,0 +1,32 @@
package org.bitcoins.crypto.musig
import org.bitcoins.crypto.{ECPublicKey, FieldElement, OddParity}
/** Represents the total tweak sum and net parity multiplier
* after applying all tweaks
*/
case class MuSigTweakContext(
parityAcc: ParityMultiplier,
tweakAcc: FieldElement) {
/** Adds tweak to tweakAcc and aggPubKey changing parityAcc if necessary */
def applyTweak(
tweak: MuSigTweak,
aggPubKey: ECPublicKey): (ECPublicKey, MuSigTweakContext) = {
val parityMult =
if (tweak.isXOnlyT && aggPubKey.parity == OddParity) Neg
else Pos
val newAggPubKey = parityMult.modify(aggPubKey).add(tweak.point)
val newParityAcc = parityAcc.multiply(parityMult)
val newTweakAcc = parityMult.modify(tweakAcc).add(tweak.tweak)
(newAggPubKey, MuSigTweakContext(newParityAcc, newTweakAcc))
}
}
object MuSigTweakContext {
/** The MuSigTweakContext for when there are no tweaks */
val empty: MuSigTweakContext =
MuSigTweakContext(Pos, FieldElement.zero)
}

View File

@ -0,0 +1,21 @@
package org.bitcoins.crypto.musig
import org.bitcoins.crypto.{EvenParity, FieldElement, KeyParity, OddParity}
/** The data required to apply the net tweak during
* MuSig signature aggregation
*/
case class MuSigTweakData(
context: MuSigTweakContext,
aggPubKeyParity: KeyParity,
e: FieldElement) {
def additiveTweak: FieldElement = {
val g = aggPubKeyParity match {
case EvenParity => FieldElement.one
case OddParity => FieldElement.orderMinusOne
}
e.multiply(g).multiply(context.tweakAcc)
}
}

View File

@ -0,0 +1,170 @@
package org.bitcoins.crypto.musig
import org.bitcoins.crypto._
import scodec.bits.ByteVector
// TODO test against secp256k1-zkp someday
/** Contains constants, hash functions, and signing/verification functionality for MuSig */
object MuSigUtil {
val nonceNum: Int = 2
def aggListHash(bytes: ByteVector): ByteVector = {
CryptoUtil.taggedSha256(bytes, "KeyAgg list").bytes
}
def aggCoefHash(bytes: ByteVector): ByteVector = {
CryptoUtil.taggedSha256(bytes, "KeyAgg coefficient").bytes
}
def nonHash(bytes: ByteVector): ByteVector = {
CryptoUtil.taggedSha256(bytes, "MuSig/nonce").bytes
}
def nonCoefHash(bytes: ByteVector): ByteVector = {
CryptoUtil.taggedSha256(bytes, "MuSig/noncecoef").bytes
}
def auxHash(bytes: ByteVector): ByteVector = {
CryptoUtil.taggedSha256(bytes, "MuSig/aux").bytes
}
/** nonces(0) + nonces(1)*b + nonces(2)*b*b + ... */
private[musig] def nonceSum[T](
nonces: Vector[T],
b: FieldElement,
add: (T, T) => T,
multiply: (T, FieldElement) => T,
identity: T): T = {
nonces
.foldLeft((FieldElement.one, identity)) { case ((pow, sumSoFar), nonce) =>
val prod = multiply(nonce, pow)
(pow.multiply(b), add(sumSoFar, prod))
}
._2
}
/** Generates a MuSig partial signature, accompanied by the aggregate R value */
def sign(
noncePriv: MuSigNoncePriv,
aggNoncePub: MuSigNoncePub,
privKey: ECPrivateKey,
message: ByteVector,
keySet: KeySet): (ECPublicKey, FieldElement) = {
val pubKey = privKey.publicKey
val coef = keySet.keyAggCoef(pubKey.schnorrPublicKey)
val SigningSession(b, aggNonce, e) =
SigningSession(aggNoncePub, keySet, message)
val adjustedNoncePriv = aggNonce.parity match {
case EvenParity => noncePriv
case OddParity => noncePriv.negate
}
val gp = ParityMultiplier.fromParity(pubKey.parity)
val g = ParityMultiplier.fromParity(keySet.aggPubKey.parity)
val adjustedPrivKey = gp
.multiply(g)
.multiply(keySet.tweakContext.parityAcc)
.modify(privKey.fieldElement)
val privNonceSum = adjustedNoncePriv.sumToKey(b)
val s = adjustedPrivKey.multiply(e).multiply(coef).add(privNonceSum)
require(partialSigVerify(s,
noncePriv.toPublicNonces,
pubKey.schnorrPublicKey,
keySet,
b,
aggNonce,
e),
"Failed verification when generating signature.")
(aggNonce, s)
}
def partialSigVerify(
partialSig: FieldElement,
pubNonces: Vector[MuSigNoncePub],
keySet: KeySet,
message: ByteVector,
signerIndex: Int): Boolean = {
require(signerIndex >= 0 && signerIndex < keySet.length,
s"Invalid signer index $signerIndex for ${keySet.length} signers")
partialSigVerify(partialSig,
pubNonces(signerIndex),
MuSigNoncePub.aggregate(pubNonces),
keySet(signerIndex),
keySet,
message)
}
def partialSigVerify(
partialSig: FieldElement,
noncePub: MuSigNoncePub,
aggNoncePub: MuSigNoncePub,
pubKey: SchnorrPublicKey,
keySet: KeySet,
message: ByteVector): Boolean = {
val SigningSession(b, aggNonce, e) =
SigningSession(aggNoncePub, keySet, message)
partialSigVerify(partialSig, noncePub, pubKey, keySet, b, aggNonce, e)
}
def partialSigVerify(
partialSig: FieldElement,
noncePub: MuSigNoncePub,
pubKey: SchnorrPublicKey,
keySet: KeySet,
b: FieldElement,
aggNonce: ECPublicKey,
e: FieldElement): Boolean = {
val nonceSum = noncePub.sumToKey(b)
val nonceSumAdjusted = aggNonce.parity match {
case EvenParity => nonceSum
case OddParity => nonceSum.negate
}
val g = ParityMultiplier.fromParity(keySet.aggPubKey.parity)
val aggKeyParity = g.multiply(keySet.tweakContext.parityAcc).toParity
val aggKey = pubKey.toXOnly.publicKey(aggKeyParity)
val a = keySet.keyAggCoef(pubKey)
partialSig.getPublicKey == nonceSumAdjusted.add(
aggKey.multiply(e.multiply(a)))
}
/** Aggregates MuSig partial signatures into a BIP340 SchnorrDigitalSignature */
def signAgg(
sVals: Vector[FieldElement],
aggNoncePub: MuSigNoncePub,
keySet: KeySet,
message: ByteVector): SchnorrDigitalSignature = {
val SigningSession(_, aggNonce, e) =
SigningSession(aggNoncePub, keySet, message)
val tweakData =
MuSigTweakData(keySet.tweakContext, keySet.aggPubKey.parity, e)
signAgg(sVals, aggNonce, Some(tweakData))
}
/** Aggregates MuSig partial signatures into a BIP340 SchnorrDigitalSignature */
def signAgg(
sVals: Vector[FieldElement],
aggPubNonce: ECPublicKey,
tweakDataOpt: Option[MuSigTweakData] = None): SchnorrDigitalSignature = {
val sSum = sVals.reduce(_.add(_))
val s = tweakDataOpt match {
case Some(tweakData) => sSum.add(tweakData.additiveTweak)
case None => sSum
}
SchnorrDigitalSignature(aggPubNonce.schnorrNonce, s)
}
}

View File

@ -0,0 +1,65 @@
package org.bitcoins.crypto.musig
import org.bitcoins.crypto.{
ECPublicKey,
EvenParity,
FieldElement,
KeyParity,
OddParity
}
/** Represents either FieldElement.one or FieldElement.orderMinusOne.
* Using this ADT rather than those actual FieldElements saves computation
* including some unnecessary point multiplications.
*
* In general there is a correspondence between Pos <-> EvenParity and Neg <-> OddParity,
* this is because in general x-only keys are assumed to be even and need to be negated
* if they are meant to be used as odd keys.
*/
sealed trait ParityMultiplier {
def modify(fieldElement: FieldElement): FieldElement = {
this match {
case Pos => fieldElement
case Neg => fieldElement.negate
}
}
def modify(pubKey: ECPublicKey): ECPublicKey = {
this match {
case Pos => pubKey
case Neg => pubKey.negate
}
}
/** Combines two ParityMultiplier into a single one representing their net modification */
def multiply(other: ParityMultiplier): ParityMultiplier = {
(this, other) match {
case (Pos, Pos) | (Neg, Neg) => Pos
case (Pos, Neg) | (Neg, Pos) => Neg
}
}
def toParity: KeyParity = {
this match {
case Pos => EvenParity
case Neg => OddParity
}
}
}
object ParityMultiplier {
def fromParity(keyParity: KeyParity): ParityMultiplier = {
keyParity match {
case EvenParity => Pos
case OddParity => Neg
}
}
}
/** Represents FieldElement.one */
case object Pos extends ParityMultiplier
/** Represents FieldElement.orderMinusOne */
case object Neg extends ParityMultiplier

View File

@ -0,0 +1,61 @@
package org.bitcoins.crypto.musig
import org.bitcoins.crypto.{
CryptoUtil,
ECPublicKey,
FieldElement,
SchnorrPublicKey
}
import scodec.bits.ByteVector
/** The data relevant to computing and verifying MuSig partial signatures */
case class SigningSession(
b: FieldElement,
aggNonce: ECPublicKey,
e: FieldElement)
object SigningSession {
def computeB(
aggNoncePub: MuSigNoncePub,
keySet: KeySet,
message: ByteVector): FieldElement = {
val aggPubKey = keySet.aggPubKey.schnorrPublicKey
val bHash =
MuSigUtil.nonCoefHash(aggNoncePub.bytes ++ aggPubKey.bytes ++ message)
FieldElement(new java.math.BigInteger(1, bHash.toArray))
}
def computeE(
aggPubKey: SchnorrPublicKey,
aggNonce: ECPublicKey,
message: ByteVector): FieldElement = {
val eBytes = CryptoUtil
.sha256SchnorrChallenge(
aggNonce.schnorrNonce.bytes ++ aggPubKey.bytes ++ message)
.bytes
FieldElement(new java.math.BigInteger(1, eBytes.toArray))
}
def getSessionValues(
aggNoncePub: MuSigNoncePub,
keySet: KeySet,
message: ByteVector): SigningSession = {
val aggPubKey = keySet.aggPubKey.schnorrPublicKey
val b = computeB(aggNoncePub, keySet, message)
val aggNonce = aggNoncePub.sumToKey(b)
val e = computeE(aggPubKey, aggNonce, message)
SigningSession(b, aggNonce, e)
}
def apply(
aggNoncePub: MuSigNoncePub,
keySet: KeySet,
message: ByteVector): SigningSession = {
getSessionValues(aggNoncePub, keySet, message)
}
}

94
docs/crypto/musig.md Normal file
View File

@ -0,0 +1,94 @@
---
id: musig
title: MuSig
---
Bitcoin-S now has support for [MuSig](https://github.com/jonasnick/bips/blob/musig2/bip-musig2.mediawiki).
This module contains classes representing public `KeySet`s, MuSig nonces, and MuSig aggregate key tweaks, as well as utility functions for all MuSig computations.
The functions for aggregating key data are:
* `aggPubKey`
* This is a member of `KeySet` and returns the aggregate public key for this set of signers, including the tweaks provided. In most uses, a subsequent call to `schnorrPublicKey` is required for Bitcoin applications.
* `MuSigNoncePub.aggregate`
* Given a `Vector[MuSigNoncePub]` of the signer's nonces, returns the aggregate `MuSigNoncePub`. This aggregation can be done before the message, or even the `KeySet` is known.
The functions for signing and verification are:
* `MuSigUtil.sign`
* This function generates a MuSig partial signature using a private key and `MuSigNoncePriv`. This consists of a pair `(R, s)` where `R` is the aggregate nonce key (same for all signers) and `s` is the actual partial signature that needs to be shared.
* `MuSigUtil.partialSigVerify`
* This function validates a single partial signature against that signer's public key and `MuSigNoncePub`.
* `MuSigUtil.signAgg`
* This function aggregates all of the `s` values into a single valid `SchnorrDigitalSignature` (using the aggregate nonce key `R`).
Note that no new function is required for aggregate verification as `SchnorrPublicKey`'s `verify` function is to be used.
Lastly, it should be mentioned that `MuSigNoncePriv`s must be constructed using either `MuSigNoncePriv.gen` or `MuSigNoncePriv.genInternal` (the latter should only be used with 32 bytes of secure random entropy). These generation functions take as input any context information that is available at nonce generation time, namely your signing key, aggregate public key, message, and any extra bytes you may have available. Including these optional inputs improves the security of nonce generation (which must be absolutely secure).
The following code shows a two-party MuSig execution:
```scala mdoc:invisible
import org.bitcoins.crypto.ECPrivateKey
import org.bitcoins.crypto.musig._
val alicePrivKey = ECPrivateKey.freshPrivateKey
val alicePubKey = alicePrivKey.schnorrPublicKey
val bobPrivKey = ECPrivateKey.freshPrivateKey
val bobPubKey = bobPrivKey.schnorrPublicKey
val msg = scodec.bits.ByteVector.fill(32)(7)
val tweaks = Vector.empty[MuSigTweak]
```
```scala mdoc:compile-only
// Alice and Bob generate and exchange nonce data (new nonces for every sig)
val aliceNoncePriv = MuSigNoncePriv.gen()
val aliceNonce = aliceNoncePriv.toPublicNonces // Alice sends this to Bob
val bobNoncePriv = MuSigNoncePriv.gen()
val bobNonce = bobNoncePriv.toPublicNonces // Bob sends this to Alice
// The aggregate musig nonce can be computed from Alice's and Bob's
val aggMuSigNonce = MuSigNoncePub.aggregate(Vector(aliceNonce, bobNonce))
// Any party can (non-interactively) compute the aggregate public key
val pubKeys = Vector(alicePubKey, bobPubKey)
val keySet = KeySet(pubKeys, tweaks) // This is where you put MuSigTweaks
val aggPubKey = keySet.aggPubKey.schnorrPublicKey
// Alice generates a partial signature for the message
val (aliceR, aliceSig) =
MuSigUtil.sign(aliceNoncePriv, aggMuSigNonce, alicePrivKey, msg, keySet)
// Bob generates a partial signature for the message
val (bobR, bobSig) =
MuSigUtil.sign(bobNoncePriv, aggMuSigNonce, bobPrivKey, msg, keySet)
require(aliceR == bobR)
val R = aliceR
// Alice and Bob exchange and verify each other's sigs (s values)
require(
MuSigUtil.partialSigVerify(aliceSig,
aliceNonce,
aggMuSigNonce,
alicePubKey,
keySet,
msg))
require(
MuSigUtil.partialSigVerify(bobSig,
bobNonce,
aggMuSigNonce,
bobPubKey,
keySet,
msg))
// In the case that the aggregator is not Alice or Bob, R can be computed as follows
val R2 = SigningSession(aggMuSigNonce, keySet, msg).aggNonce
require(R2 == R)
// Finally, any party can aggregate the partial signatures
val sig = MuSigUtil.signAgg(Vector(aliceSig, bobSig), R)
// This signature can now be validated as a normal BIP340 Schnorr signature
require(aggPubKey.verify(msg, sig))
```

View File

@ -74,6 +74,9 @@
"crypto/crypto-intro": {
"title": "Crypto Module"
},
"crypto/musig": {
"title": "MuSig"
},
"crypto/sign": {
"title": "Sign API"
},

View File

@ -36,7 +36,8 @@
"Crypto Module": [
"crypto/crypto-intro",
"crypto/sign",
"crypto/adaptor-signatures"
"crypto/adaptor-signatures",
"crypto/musig"
],
"Fee Provider": [
"fee-provider/fee-provider"