From 3376655b9cf5abd242b778304ae05cbfaeb4522a Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Fri, 24 Jun 2022 14:27:13 +0200 Subject: [PATCH 01/11] btcec/schnorr/musig2: XOR rand with secret key This commit XORs the secret key (if a secret key is specified) with the random bytes as per MuSig2 Spec (https://github.com/jonasnick/bips/blob/musig2/bip-musig2.mediawiki#nonce-generation-1) --- btcec/schnorr/musig2/nonces.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/btcec/schnorr/musig2/nonces.go b/btcec/schnorr/musig2/nonces.go index 5a7fbd4b3d..d22f18cd56 100644 --- a/btcec/schnorr/musig2/nonces.go +++ b/btcec/schnorr/musig2/nonces.go @@ -31,7 +31,7 @@ var ( // NonceGenTag is used to generate the value (from a set of required an // optional field) that will be used as the part of the secret nonce. - NonceGenTag = []byte("Musig/nonce") + NonceGenTag = []byte("MuSig/nonce") byteOrder = binary.BigEndian ) @@ -270,6 +270,16 @@ func GenNonces(options ...NonceGenOption) (*Nonces, error) { return nil, err } + // If the options contain a secret key, we XOR it with with the tagged + // random bytes. + if len(opts.secretKey) == 32 { + taggedHash := chainhash.TaggedHash(NonceAuxTag, randBytes[:]) + + for i := 0; i < chainhash.HashSize; i++ { + randBytes[i] = opts.secretKey[i] ^ taggedHash[i] + } + } + // Using our randomness and the set of optional params, generate our // two secret nonces: k1 and k2. k1, err := genNonceAuxBytes(randBytes[:], 1, opts) From 4ad819e7af8210737c86215f97decb85b9429d18 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Fri, 24 Jun 2022 14:28:32 +0200 Subject: [PATCH 02/11] btcec/schnorr/musig2: Update to MuSig 0.3.0 This commit changes the i's in GenNonces to 0 and 1 as per https://github.com/jonasnick/bips/blob/musig2/bip-musig2.mediawiki#change-log 0.3 --- btcec/schnorr/musig2/nonces.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/btcec/schnorr/musig2/nonces.go b/btcec/schnorr/musig2/nonces.go index d22f18cd56..9600c45b1a 100644 --- a/btcec/schnorr/musig2/nonces.go +++ b/btcec/schnorr/musig2/nonces.go @@ -282,11 +282,11 @@ func GenNonces(options ...NonceGenOption) (*Nonces, error) { // Using our randomness and the set of optional params, generate our // two secret nonces: k1 and k2. - k1, err := genNonceAuxBytes(randBytes[:], 1, opts) + k1, err := genNonceAuxBytes(randBytes[:], 0, opts) if err != nil { return nil, err } - k2, err := genNonceAuxBytes(randBytes[:], 2, opts) + k2, err := genNonceAuxBytes(randBytes[:], 1, opts) if err != nil { return nil, err } From 478a2f78c4a26ea3b8fc9b0bcee4dd23adf4e3df Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Fri, 29 Jul 2022 10:16:35 +0200 Subject: [PATCH 03/11] btcec/schnorr/musig2: Add nonce generation testcases This commit adds the testcases specified under version 0.3.1 from https://github.com/jonasnick/bips/blob/musig2/bip-musig2.mediawiki#change-log and the fixes from https://github.com/jonasnick/bips/commit/79438fd604d327e252d011712e7a4e9588a4584f --- btcec/schnorr/musig2/musig2_test.go | 85 +++++++++++++++++++++++++++++ btcec/schnorr/musig2/nonces.go | 14 +++++ 2 files changed, 99 insertions(+) diff --git a/btcec/schnorr/musig2/musig2_test.go b/btcec/schnorr/musig2/musig2_test.go index c9ae74d7be..309718ca67 100644 --- a/btcec/schnorr/musig2/musig2_test.go +++ b/btcec/schnorr/musig2/musig2_test.go @@ -825,3 +825,88 @@ func TestMuSigEarlyNonce(t *testing.T) { t.Fatalf("final sig is invalid!") } } + +// TestMusig2NonceGenTestVectors tests the nonce generation function with +// the testvectors defined in the Musig2 BIP. +func TestMusig2NonceGenTestVectors(t *testing.T) { + t.Parallel() + + msg := bytes.Repeat([]byte{0x01}, 32) + sk := bytes.Repeat([]byte{0x02}, 32) + aggpk := bytes.Repeat([]byte{0x07}, 32) + extra_in := bytes.Repeat([]byte{0x08}, 32) + + testCases := []struct { + opts nonceGenOpts + expectedNonce string + }{ + { + opts: nonceGenOpts{ + randReader: &memsetRandReader{i: 0}, + secretKey: sk[:], + combinedKey: aggpk[:], + auxInput: extra_in[:], + msg: msg[:], + }, + expectedNonce: "E8F2E103D86800F19A4E97338D371CB885DB2" + + "F19D08C0BD205BBA9B906C971D0D786A17718AAFAD6D" + + "E025DDDD99DC823E2DFC1AE1DDFE920888AD53FFF423FC4", + }, + { + opts: nonceGenOpts{ + randReader: &memsetRandReader{i: 0}, + secretKey: sk[:], + combinedKey: aggpk[:], + auxInput: extra_in[:], + msg: nil, + }, + expectedNonce: "8A633F5EECBDB690A6BE4921426F41BE78D50" + + "9DC1CE894C1215844C0E4C6DE7ABC9A5BE0A3BF3FE31" + + "2CCB7E4817D2CB17A7CEA8382B73A99A583E323387B3C32", + }, + { + opts: nonceGenOpts{ + randReader: &memsetRandReader{i: 0}, + secretKey: nil, + combinedKey: nil, + auxInput: nil, + msg: nil, + }, + expectedNonce: "7B3B5A002356471AF0E961DE2549C121BD0D4" + + "8ABCEEDC6E034BDDF86AD3E0A187ECEE674CEF7364B0" + + "BC4BEEFB8B66CAD89F98DE2F8C5A5EAD5D1D1E4BD7D04CD", + }, + } + + for _, testCase := range testCases { + nonce, err := GenNonces(withCustomOptions(testCase.opts)) + if err != nil { + t.Fatalf("err gen nonce aux bytes %v", err) + } + + expectedBytes, _ := hex.DecodeString(testCase.expectedNonce) + if !bytes.Equal(nonce.SecNonce[:], expectedBytes) { + + t.Fatalf("nonces don't match: expected %x, got %x", + expectedBytes, nonce.SecNonce[:]) + } + } + +} + +type memsetRandReader struct { + i int +} + +func (mr *memsetRandReader) Read(buf []byte) (n int, err error) { + for i := range buf { + buf[i] = byte(mr.i) + } + return len(buf), nil +} + +func memsetLoop(a []byte, v uint8) { + for i := range a { + a[i] = byte(v) + } +} diff --git a/btcec/schnorr/musig2/nonces.go b/btcec/schnorr/musig2/nonces.go index 9600c45b1a..0ba100229a 100644 --- a/btcec/schnorr/musig2/nonces.go +++ b/btcec/schnorr/musig2/nonces.go @@ -175,6 +175,20 @@ func WithNonceAuxInput(aux []byte) NonceGenOption { } } +// withCustomOptions allows a caller to pass a complete set of custom +// nonceGenOpts, without needing to create custom and checked structs such as +// *btcec.PrivateKey. This is mainly used to match the testcases provided by +// the MuSig2 BIP. +func withCustomOptions(customOpts nonceGenOpts) NonceGenOption { + return func(o *nonceGenOpts) { + o.randReader = customOpts.randReader + o.secretKey = customOpts.secretKey + o.combinedKey = customOpts.combinedKey + o.msg = customOpts.msg + o.auxInput = customOpts.auxInput + } +} + // lengthWriter is a function closure that allows a caller to control how the // length prefix of a byte slice is written. type lengthWriter func(w io.Writer, b []byte) error From 1b85a60b6df59ffb02b21ab92d4d1af9ccfca77d Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Wed, 29 Jun 2022 18:29:55 +0200 Subject: [PATCH 04/11] btcec/schnorr/musig2: Add AggregateKeys testvectors This commit adds the testvectors from https://github.com/jonasnick/bips/commit/20f60b0f37c47b147e01816c934f149d139cb905 to the testcases --- btcec/btcec.go | 15 +++ btcec/schnorr/musig2/keys.go | 9 +- btcec/schnorr/musig2/musig2_test.go | 173 +++++++++++++++++++++++++--- 3 files changed, 177 insertions(+), 20 deletions(-) diff --git a/btcec/btcec.go b/btcec/btcec.go index efde8d6a81..f85baba8c7 100644 --- a/btcec/btcec.go +++ b/btcec/btcec.go @@ -39,3 +39,18 @@ type CurveParams = secp.CurveParams func Params() *CurveParams { return secp.Params() } + +// Generator returns the public key at the Generator Point. +func Generator() *PublicKey { + var ( + result JacobianPoint + k secp.ModNScalar + ) + + k.SetInt(1) + ScalarBaseMultNonConst(&k, &result) + + result.ToAffine() + + return NewPublicKey(&result.X, &result.Y) +} diff --git a/btcec/schnorr/musig2/keys.go b/btcec/schnorr/musig2/keys.go index e61a22f2e5..8c86c624fb 100644 --- a/btcec/schnorr/musig2/keys.go +++ b/btcec/schnorr/musig2/keys.go @@ -26,6 +26,10 @@ var ( // ErrTweakedKeyIsInfinity is returned if while tweaking a key, we end // up with the point at infinity. ErrTweakedKeyIsInfinity = fmt.Errorf("tweaked key is infinity point") + + // ErrTweakedKeyOverflows is returned if a tweaking key is larger than + // 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141. + ErrTweakedKeyOverflows = fmt.Errorf("tweaked key is to large") ) // sortableKeys defines a type of slice of public keys that implements the sort @@ -286,7 +290,10 @@ func tweakKey(keyJ btcec.JacobianPoint, parityAcc btcec.ModNScalar, tweak [32]by // Next, map the tweak into a mod n integer so we can use it for // manipulations below. tweakInt := new(btcec.ModNScalar) - tweakInt.SetBytes(&tweak) + overflows := tweakInt.SetBytes(&tweak) + if overflows == 1 { + return keyJ, parityAcc, tweakAcc, ErrTweakedKeyOverflows + } // Next, we'll compute: Q_i = g*Q + t*G, where g is our parityFactor and t // is the tweakInt above. We'll space things out a bit to make it easier to diff --git a/btcec/schnorr/musig2/musig2_test.go b/btcec/schnorr/musig2/musig2_test.go index 309718ca67..a032618bb9 100644 --- a/btcec/schnorr/musig2/musig2_test.go +++ b/btcec/schnorr/musig2/musig2_test.go @@ -16,6 +16,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/decred/dcrd/dcrec/secp256k1/v4" ) var ( @@ -26,14 +27,50 @@ var ( key3Bytes, _ = hex.DecodeString("3590A94E768F8E1815C2F24B4D80A8E3149" + "316C3518CE7B7AD338368D038CA66") - testKeys = [][]byte{key1Bytes, key2Bytes, key3Bytes} - - keyCombo1, _ = hex.DecodeString("E5830140512195D74C8307E39637CBE5FB730EBEAB80EC514CF88A877CEEEE0B") - keyCombo2, _ = hex.DecodeString("D70CD69A2647F7390973DF48CBFA2CCC407B8B2D60B08C5F1641185C7998A290") - keyCombo3, _ = hex.DecodeString("81A8B093912C9E481408D09776CEFB48AEB8B65481B6BAAFB3C5810106717BEB") - keyCombo4, _ = hex.DecodeString("2EB18851887E7BDC5E830E89B19DDBC28078F1FA88AAD0AD01CA06FE4F80210B") + invalidPk1, _ = hex.DecodeString("00000000000000000000000000000000" + + "00000000000000000000000000000005") + invalidPk2, _ = hex.DecodeString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + "FFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30") + invalidTweak, _ = hex.DecodeString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE" + + "BAAEDCE6AF48A03BBFD25E8CD0364141") + + testKeys = [][]byte{key1Bytes, key2Bytes, key3Bytes, invalidPk1, + invalidPk2} + + keyCombo1, _ = hex.DecodeString("E5830140512195D74C8307E39637CBE5FB73" + + "0EBEAB80EC514CF88A877CEEEE0B") + keyCombo2, _ = hex.DecodeString("D70CD69A2647F7390973DF48CBFA2CCC407B" + + "8B2D60B08C5F1641185C7998A290") + keyCombo3, _ = hex.DecodeString("81A8B093912C9E481408D09776CEFB48AEB8" + + "B65481B6BAAFB3C5810106717BEB") + keyCombo4, _ = hex.DecodeString("2EB18851887E7BDC5E830E89B19DDBC28078" + + "F1FA88AAD0AD01CA06FE4F80210B") ) +// getInfinityTweak returns a tweak that, when tweaking the Generator, triggers +// the ErrTweakedKeyIsInfinity error. +func getInfinityTweak() KeyTweakDesc { + generator := btcec.Generator() + + keySet := []*btcec.PublicKey{generator} + + keysHash := keyHashFingerprint(keySet, true) + uniqueKeyIndex := secondUniqueKeyIndex(keySet, true) + + n := &btcec.ModNScalar{} + + n.SetByteSlice(invalidTweak) + + coeff := aggregationCoefficient( + keySet, generator, keysHash, uniqueKeyIndex, + ).Negate().Add(n) + + return KeyTweakDesc{ + Tweak: coeff.Bytes(), + IsXOnly: false, + } +} + const ( keyAggTestVectorName = "key_agg_vectors.json" @@ -44,8 +81,10 @@ var dumpJson = flag.Bool("dumpjson", false, "if true, a JSON version of the "+ "test vectors will be written to the cwd") type jsonKeyAggTestCase struct { - Keys []string `json:"keys"` - ExpectedKey string `json:"expected_key"` + Keys []string `json:"keys"` + Tweaks []jsonTweak `json:"tweaks"` + ExpectedKey string `json:"expected_key"` + ExpectedError string `json:"expected_error"` } // TestMuSig2KeyAggTestVectors tests that this implementation of musig2 key @@ -56,8 +95,11 @@ func TestMuSig2KeyAggTestVectors(t *testing.T) { var jsonCases []jsonKeyAggTestCase testCases := []struct { - keyOrder []int - expectedKey []byte + keyOrder []int + explicitKeys []*btcec.PublicKey + tweaks []KeyTweakDesc + expectedKey []byte + expectedError error }{ // Keys in backwards lexicographical order. { @@ -82,18 +124,58 @@ func TestMuSig2KeyAggTestVectors(t *testing.T) { keyOrder: []int{0, 0, 1, 1}, expectedKey: keyCombo4, }, + + // Invalid public key. + { + keyOrder: []int{0, 3}, + expectedError: secp256k1.ErrPubKeyNotOnCurve, + }, + + // Public key exceeds field size. + { + keyOrder: []int{0, 4}, + expectedError: secp256k1.ErrPubKeyXTooBig, + }, + + // Tweak is out of range. + { + keyOrder: []int{0, 1}, + tweaks: []KeyTweakDesc{ + KeyTweakDesc{ + Tweak: to32ByteSlice(invalidTweak), + IsXOnly: true, + }, + }, + expectedError: ErrTweakedKeyOverflows, + }, + + // Intermediate tweaking result is point at infinity. + { + explicitKeys: []*secp256k1.PublicKey{btcec.Generator()}, + tweaks: []KeyTweakDesc{ + getInfinityTweak(), + }, + expectedError: ErrTweakedKeyIsInfinity, + }, } for i, testCase := range testCases { testName := fmt.Sprintf("%v", testCase.keyOrder) t.Run(testName, func(t *testing.T) { var ( - keys []*btcec.PublicKey - strKeys []string + keys []*btcec.PublicKey + strKeys []string + strTweaks []jsonTweak + jsonError string ) for _, keyIndex := range testCase.keyOrder { keyBytes := testKeys[keyIndex] pub, err := schnorr.ParsePubKey(keyBytes) - if err != nil { + + switch { + case testCase.expectedError != nil && + errors.Is(err, testCase.expectedError): + return + case err != nil: t.Fatalf("unable to parse pubkeys: %v", err) } @@ -101,15 +183,59 @@ func TestMuSig2KeyAggTestVectors(t *testing.T) { strKeys = append(strKeys, hex.EncodeToString(keyBytes)) } - jsonCases = append(jsonCases, jsonKeyAggTestCase{ - Keys: strKeys, - ExpectedKey: hex.EncodeToString(testCase.expectedKey), - }) + for _, explicitKey := range testCase.explicitKeys { + keys = append(keys, explicitKey) + strKeys = append( + strKeys, + hex.EncodeToString( + explicitKey.SerializeCompressed(), + )) + } + + for _, tweak := range testCase.tweaks { + strTweaks = append( + strTweaks, + jsonTweak{ + Tweak: hex.EncodeToString( + tweak.Tweak[:], + ), + XOnly: tweak.IsXOnly, + }) + } + + if testCase.expectedError != nil { + jsonError = testCase.expectedError.Error() + } + + jsonCases = append( + jsonCases, + jsonKeyAggTestCase{ + Keys: strKeys, + Tweaks: strTweaks, + ExpectedKey: hex.EncodeToString( + testCase.expectedKey), + ExpectedError: jsonError, + }) uniqueKeyIndex := secondUniqueKeyIndex(keys, false) - combinedKey, _, _, _ := AggregateKeys( - keys, false, WithUniqueKeyIndex(uniqueKeyIndex), + opts := []KeyAggOption{WithUniqueKeyIndex(uniqueKeyIndex)} + if len(testCase.tweaks) > 0 { + opts = append(opts, WithKeyTweaks(testCase.tweaks...)) + } + + combinedKey, _, _, err := AggregateKeys( + keys, false, opts..., ) + + switch { + case testCase.expectedError != nil && + errors.Is(err, testCase.expectedError): + return + + case err != nil: + t.Fatalf("case #%v, got error %v", i, err) + } + combinedKeyBytes := schnorr.SerializePubKey(combinedKey.FinalKey) if !bytes.Equal(combinedKeyBytes, testCase.expectedKey) { t.Fatalf("case: #%v, invalid aggregation: "+ @@ -910,3 +1036,12 @@ func memsetLoop(a []byte, v uint8) { a[i] = byte(v) } } + +func to32ByteSlice(input []byte) [32]byte { + if len(input) != 32 { + panic("input byte slice has invalid length") + } + var output [32]byte + copy(output[:], input) + return output +} From 8b59e7aa6bee3f8c8db496eb2bc2df20c526f3ec Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Fri, 29 Jul 2022 10:32:35 +0200 Subject: [PATCH 05/11] btcec/schnorr/musig2: Add AggregateNonce testvectors This commit adds the testvectors from https://github.com/jonasnick/bips/commit/0ec2aefdaaabcb48704ab4718373a05e148e920d to the testcases --- btcec/schnorr/musig2/musig2_test.go | 156 ++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/btcec/schnorr/musig2/musig2_test.go b/btcec/schnorr/musig2/musig2_test.go index a032618bb9..8b3a267b3c 100644 --- a/btcec/schnorr/musig2/musig2_test.go +++ b/btcec/schnorr/musig2/musig2_test.go @@ -74,6 +74,8 @@ func getInfinityTweak() KeyTweakDesc { const ( keyAggTestVectorName = "key_agg_vectors.json" + nonceAggTestVectorName = "nonce_agg_vectors.json" + signTestVectorName = "sign_vectors.json" ) @@ -1020,6 +1022,142 @@ func TestMusig2NonceGenTestVectors(t *testing.T) { } +var ( + pNonce1, _ = hex.DecodeString("020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E666" + + "03BA47FBC1834437B3212E89A84D8425E7BF12E0245D98262268EBDCB385D50641") + pNonce2, _ = hex.DecodeString("03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A6" + + "0248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833") + + expectedNonce, _ = hex.DecodeString("035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B" + + "024725377345BDE0E9C33AF3C43C0A29A9249F2F2956FA8CFEB55C8573D0262DC8") + + invalidNonce1, _ = hex.DecodeString("04FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A6" + "0248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833") + invalidNonce2, _ = hex.DecodeString("03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A6" + "0248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B831") + invalidNonce3, _ = hex.DecodeString("03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A6" + "02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30") +) + +type jsonNonceAggTestCase struct { + Nonces []string `json:"nonces"` + ExpectedNonce string `json:"expected_key"` + ExpectedError string `json:"expected_error"` +} + +func TestMusig2AggregateNoncesTestVectors(t *testing.T) { + t.Parallel() + + var jsonCases []jsonNonceAggTestCase + + testCases := []struct { + nonces [][]byte + expectedNonce []byte + expectedError error + }{ + // Vector 1: Valid. + { + nonces: [][]byte{pNonce1, pNonce2}, + expectedNonce: expectedNonce, + }, + + // Vector 2: Public nonce from signer 1 is invalid due wrong + // tag, 0x04, inthe first half. + { + nonces: [][]byte{pNonce1, invalidNonce1}, + expectedError: secp256k1.ErrPubKeyInvalidFormat, + }, + + // Vector 3: Public nonce from signer 0 is invalid because the + // second half does not correspond to an X coordinate. + { + nonces: [][]byte{invalidNonce2, pNonce2}, + expectedError: secp256k1.ErrPubKeyNotOnCurve, + }, + + // Vector 4: Public nonce from signer 0 is invalid because + // second half exceeds field size. + { + nonces: [][]byte{invalidNonce3, pNonce2}, + expectedError: secp256k1.ErrPubKeyXTooBig, + }, + + // Vector 5: Sum of second points encoded in the nonces would + // be point at infinity, therefore set sum to base point G. + { + nonces: [][]byte{ + append( + append([]byte{}, pNonce1[0:33]...), + getGBytes()..., + ), + append( + append([]byte{}, pNonce2[0:33]...), + getNegGBytes()..., + ), + }, + expectedNonce: append( + append([]byte{}, expectedNonce[0:33]...), + getGBytes()..., + ), + }, + } + for i, testCase := range testCases { + testName := fmt.Sprintf("Vector %v", i+1) + t.Run(testName, func(t *testing.T) { + var ( + nonces [][66]byte + strNonces []string + jsonError string + ) + for _, nonce := range testCase.nonces { + nonces = append(nonces, toPubNonceSlice(nonce)) + strNonces = append(strNonces, hex.EncodeToString(nonce)) + } + + if testCase.expectedError != nil { + jsonError = testCase.expectedError.Error() + } + + jsonCases = append(jsonCases, jsonNonceAggTestCase{ + Nonces: strNonces, + ExpectedNonce: hex.EncodeToString(expectedNonce), + ExpectedError: jsonError, + }) + + aggregatedNonce, err := AggregateNonces(nonces) + + switch { + case testCase.expectedError != nil && + errors.Is(err, testCase.expectedError): + + return + case err != nil: + t.Fatalf("aggregating nonce error: %v", err) + } + + if !bytes.Equal(testCase.expectedNonce, aggregatedNonce[:]) { + t.Fatalf("case: #%v, invalid nonce aggregation: "+ + "expected %x, got %x", i, testCase.expectedNonce, + aggregatedNonce) + } + + }) + } + + if *dumpJson { + jsonBytes, err := json.Marshal(jsonCases) + if err != nil { + t.Fatalf("unable to encode json: %v", err) + } + + var formattedJson bytes.Buffer + json.Indent(&formattedJson, jsonBytes, "", "\t") + err = ioutil.WriteFile( + nonceAggTestVectorName, formattedJson.Bytes(), 0644, + ) + if err != nil { + t.Fatalf("unable to write file: %v", err) + } + } +} + type memsetRandReader struct { i int } @@ -1045,3 +1183,21 @@ func to32ByteSlice(input []byte) [32]byte { copy(output[:], input) return output } + +func toPubNonceSlice(input []byte) [PubNonceSize]byte { + var output [PubNonceSize]byte + copy(output[:], input) + + return output +} + +func getGBytes() []byte { + return btcec.Generator().SerializeCompressed() +} + +func getNegGBytes() []byte { + pk := getGBytes() + pk[0] = 0x3 + + return pk +} From 53f47d65f139dbe484af573d0b9c8c1fd6384cc1 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Thu, 4 Aug 2022 11:49:17 +0200 Subject: [PATCH 06/11] btcec/schnorr/musig2: Add Sign test vectors This commit adds the testvectors from https://github.com/jonasnick/bips/commit/4c06f31daf54f0fc614144c28fcc45f5a10c6590 to the testcases --- btcec/schnorr/musig2/musig2_test.go | 240 ++++++++++++++++++++-------- 1 file changed, 169 insertions(+), 71 deletions(-) diff --git a/btcec/schnorr/musig2/musig2_test.go b/btcec/schnorr/musig2/musig2_test.go index 8b3a267b3c..7ff6e2e2c3 100644 --- a/btcec/schnorr/musig2/musig2_test.go +++ b/btcec/schnorr/musig2/musig2_test.go @@ -291,22 +291,28 @@ func parseKey(xHex string) *btcec.PublicKey { var ( signSetPrivKey, _ = btcec.PrivKeyFromBytes( - mustParseHex("7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671"), + mustParseHex("7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DF" + + "E8D76D7F2D1007671"), ) - signSetPubKey, _ = schnorr.ParsePubKey(schnorr.SerializePubKey(signSetPrivKey.PubKey())) + signSetPubKey = schnorr.SerializePubKey(signSetPrivKey.PubKey()) - signTestMsg = mustParseHex("F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF") + signTestMsg = mustParseHex("F95466D086770E689964664219266FE5ED215C92A" + + "E20BAB5C9D79ADDDDF3C0CF") - signSetKey2, _ = schnorr.ParsePubKey( - mustParseHex("F9308A019258C31049344F85F89D5229B531C845836F99B086" + - "01F113BCE036F9"), - ) - signSetKey3, _ = schnorr.ParsePubKey( - mustParseHex("DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843" + - "240F7B502BA659"), - ) + signSetKey2 = mustParseHex("F9308A019258C31049344F85F89D5229B531C8458" + + "36F99B08601F113BCE036F9") + + signSetKey3 = mustParseHex("DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA" + + "2DECED843240F7B502BA659") - signSetKeys = []*btcec.PublicKey{signSetPubKey, signSetKey2, signSetKey3} + invalidSetKey1 = mustParseHex("00000000000000000000000000000000" + + "00000000000000000000000000000007") + + signSetKeys = [][]byte{signSetPubKey, signSetKey2, signSetKey3, invalidPk1} + + aggregatedNonce = toPubNonceSlice(mustParseHex("028465FCF0BBDBCF443AA" + + "BCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926" + + "D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9")) ) func formatTweakParity(tweaks []KeyTweakDesc) string { @@ -331,20 +337,21 @@ type jsonTweak struct { XOnly bool `json:"x_only"` } -type tweakSignCase struct { - Keys []string `json:"keys"` - Tweaks []jsonTweak `json:"tweaks,omitempty"` +type jsonTweakSignCase struct { + Keys []string `json:"keys"` + Tweaks []jsonTweak `json:"tweaks,omitempty"` + AggNonce string `json:"agg_nonce"` - ExpectedSig string `json:"expected_sig"` + ExpectedSig string `json:"expected_sig"` + ExpectedError string `json:"expected_error` } type jsonSignTestCase struct { SecNonce string `json:"secret_nonce"` - AggNonce string `json:"agg_nonce"` SigningKey string `json:"signing_key"` Msg string `json:"msg"` - TestCases []tweakSignCase `json:"test_cases"` + TestCases []jsonTweakSignCase `json:"test_cases"` } // TestMuSig2SigningTestVectors tests that the musig2 implementation produces @@ -357,21 +364,11 @@ func TestMuSig2SigningTestVectors(t *testing.T) { jsonCases.SigningKey = hex.EncodeToString(signSetPrivKey.Serialize()) jsonCases.Msg = hex.EncodeToString(signTestMsg) - var aggregatedNonce [PubNonceSize]byte - copy( - aggregatedNonce[:], - mustParseHex("028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61"), - ) - copy( - aggregatedNonce[33:], - mustParseHex("037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9"), - ) - - jsonCases.AggNonce = hex.EncodeToString(aggregatedNonce[:]) - var secNonce [SecNonceSize]byte - copy(secNonce[:], mustParseHex("508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61")) - copy(secNonce[32:], mustParseHex("FA27FD49B1D50085B481285E1CA205D55C82CC1B31FF5CD54A489829355901F7")) + copy(secNonce[:], mustParseHex("508B81A611F100A6B2B6B29656590898AF488B"+ + "CF2E1F55CF22E5CFB84421FE61")) + copy(secNonce[32:], mustParseHex("FA27FD49B1D50085B481285E1CA205D55C82"+ + "CC1B31FF5CD54A489829355901F7")) jsonCases.SecNonce = hex.EncodeToString(secNonce[:]) @@ -410,40 +407,105 @@ func TestMuSig2SigningTestVectors(t *testing.T) { testCases := []struct { keyOrder []int + aggNonce [66]byte expectedPartialSig []byte tweaks []KeyTweakDesc + expectedError error }{ + // Vector 1 + { + keyOrder: []int{0, 1, 2}, + aggNonce: aggregatedNonce, + expectedPartialSig: mustParseHex("68537CC5234E505BD14" + + "061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B"), + }, + + // Vector 2 + { + keyOrder: []int{1, 0, 2}, + aggNonce: aggregatedNonce, + expectedPartialSig: mustParseHex("2DF67BFFF18E3DE797E" + + "13C6475C963048138DAEC5CB20A357CECA7C8424295EA"), + }, + + // Vector 3 + { + keyOrder: []int{1, 2, 0}, + aggNonce: aggregatedNonce, + expectedPartialSig: mustParseHex("0D5B651E6DE34A29A12" + + "DE7A8B4183B4AE6A7F7FBE15CDCAFA4A3D1BCAABC7517"), + }, + + // Vector 4: Signer 2 provided an invalid public key + { + keyOrder: []int{1, 0, 3}, + aggNonce: aggregatedNonce, + expectedError: secp256k1.ErrPubKeyNotOnCurve, + }, + + // Vector 5: Aggregate nonce is invalid due wrong tag, 0x04, + // in the first half. { - keyOrder: []int{0, 1, 2}, - expectedPartialSig: mustParseHex("68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B"), + + keyOrder: []int{1, 2, 0}, + aggNonce: toPubNonceSlice( + mustParseHex("048465FCF0BBDBCF443AABCCE533D42" + + "B4B5A10966AC09A49655E8C42DAAB8FCD610" + + "37496A3CC86926D452CAFCFD55D25972CA16" + + "75D549310DE296BFF42F72EEEA8C9")), + expectedError: secp256k1.ErrPubKeyInvalidFormat, }, + + // Vector 6: Aggregate nonce is invalid because the second half + // does not correspond to an X coordinate. { - keyOrder: []int{1, 0, 2}, - expectedPartialSig: mustParseHex("2DF67BFFF18E3DE797E13C6475C963048138DAEC5CB20A357CECA7C8424295EA"), + + keyOrder: []int{1, 2, 0}, + aggNonce: toPubNonceSlice( + mustParseHex("028465FCF0BBDBCF443AABCCE533D42" + + "B4B5A10966AC09A49655E8C42DAAB8FCD610" + + "200000000000000000000000000000000000" + + "00000000000000000000000000009")), + expectedError: secp256k1.ErrPubKeyNotOnCurve, }, + + // Vector 7: Aggregate nonce is invalid because the second half + // exceeds field size. { - keyOrder: []int{1, 2, 0}, - expectedPartialSig: mustParseHex("0D5B651E6DE34A29A12DE7A8B4183B4AE6A7F7FBE15CDCAFA4A3D1BCAABC7517"), + + keyOrder: []int{1, 2, 0}, + aggNonce: toPubNonceSlice( + mustParseHex("028465FCF0BBDBCF443AABCCE533D42" + + "B4B5A10966AC09A49655E8C42DAAB8FCD610" + + "2FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + "FFFFFFFFFFFFFFFFFFFFEFFFFFC30")), + expectedError: secp256k1.ErrPubKeyXTooBig, }, // A single x-only tweak. { - keyOrder: []int{1, 2, 0}, - expectedPartialSig: mustParseHex("5e24c7496b565debc3b9639e6f1304a21597f9603d3ab05b4913641775e1375b"), - tweaks: []KeyTweakDesc{genTweakParity(tweak1, true)}, + keyOrder: []int{1, 2, 0}, + aggNonce: aggregatedNonce, + expectedPartialSig: mustParseHex("5e24c7496b565debc3b" + + "9639e6f1304a21597f9603d3ab05b4913641775e1375b"), + tweaks: []KeyTweakDesc{genTweakParity(tweak1, true)}, }, // A single ordinary tweak. { - keyOrder: []int{1, 2, 0}, - expectedPartialSig: mustParseHex("78408ddcab4813d1394c97d493ef1084195c1d4b52e63ecd7bc5991644e44ddd"), - tweaks: []KeyTweakDesc{genTweakParity(tweak1, false)}, + keyOrder: []int{1, 2, 0}, + aggNonce: aggregatedNonce, + expectedPartialSig: mustParseHex("78408ddcab4813d1394c" + + "97d493ef1084195c1d4b52e63ecd7bc5991644e44ddd"), + tweaks: []KeyTweakDesc{genTweakParity(tweak1, false)}, }, // An ordinary tweak then an x-only tweak. { - keyOrder: []int{1, 2, 0}, - expectedPartialSig: mustParseHex("C3A829A81480E36EC3AB052964509A94EBF34210403D16B226A6F16EC85B7357"), + keyOrder: []int{1, 2, 0}, + aggNonce: aggregatedNonce, + expectedPartialSig: mustParseHex("C3A829A81480E36EC3A" + + "B052964509A94EBF34210403D16B226A6F16EC85B7357"), tweaks: []KeyTweakDesc{ genTweakParity(tweak1, false), genTweakParity(tweak2, true), @@ -452,8 +514,10 @@ func TestMuSig2SigningTestVectors(t *testing.T) { // Four tweaks, in the order: x-only, ordinary, x-only, ordinary. { - keyOrder: []int{1, 2, 0}, - expectedPartialSig: mustParseHex("8C4473C6A382BD3C4AD7BE59818DA5ED7CF8CEC4BC21996CFDA08BB4316B8BC7"), + keyOrder: []int{1, 2, 0}, + aggNonce: aggregatedNonce, + expectedPartialSig: mustParseHex("8C4473C6A382BD3C4AD" + + "7BE59818DA5ED7CF8CEC4BC21996CFDA08BB4316B8BC7"), tweaks: []KeyTweakDesc{ genTweakParity(tweak1, true), genTweakParity(tweak2, false), @@ -471,17 +535,22 @@ func TestMuSig2SigningTestVectors(t *testing.T) { if len(testCase.tweaks) != 0 { testName += fmt.Sprintf("/x_only=%v", formatTweakParity(testCase.tweaks)) } - t.Run(testName, func(t *testing.T) { - var strKeys []string keySet := make([]*btcec.PublicKey, 0, len(testCase.keyOrder)) for _, keyIndex := range testCase.keyOrder { - keySet = append(keySet, signSetKeys[keyIndex]) - strKeys = append( - strKeys, hex.EncodeToString( - schnorr.SerializePubKey(signSetKeys[keyIndex]), - ), - ) + keyBytes := signSetKeys[keyIndex] + pub, err := schnorr.ParsePubKey(keyBytes) + + switch { + case testCase.expectedError != nil && + errors.Is(err, testCase.expectedError): + + return + case err != nil: + t.Fatalf("unable to parse pubkeys: %v", err) + } + + keySet = append(keySet, pub) } var opts []SignOption @@ -491,19 +560,17 @@ func TestMuSig2SigningTestVectors(t *testing.T) { ) } - var jsonTweaks []jsonTweak - for _, tweak := range testCase.tweaks { - jsonTweaks = append(jsonTweaks, jsonTweak{ - Tweak: hex.EncodeToString(tweak.Tweak[:]), - XOnly: tweak.IsXOnly, - }) - } - partialSig, err := Sign( - secNonce, signSetPrivKey, aggregatedNonce, + secNonce, signSetPrivKey, testCase.aggNonce, keySet, msg, opts..., ) - if err != nil { + + switch { + case testCase.expectedError != nil && + errors.Is(err, testCase.expectedError): + + return + case err != nil: t.Fatalf("unable to generate partial sig: %v", err) } @@ -516,12 +583,43 @@ func TestMuSig2SigningTestVectors(t *testing.T) { ) } - jsonCases.TestCases = append(jsonCases.TestCases, tweakSignCase{ - Keys: strKeys, - Tweaks: jsonTweaks, - ExpectedSig: hex.EncodeToString(testCase.expectedPartialSig), - }) }) + + if *dumpJson { + var ( + strKeys []string + jsonError string + ) + + for _, keyIndex := range testCase.keyOrder { + keyBytes := signSetKeys[keyIndex] + strKeys = append(strKeys, hex.EncodeToString(keyBytes)) + } + + if testCase.expectedError != nil { + jsonError = testCase.expectedError.Error() + } + + tweakSignCase := jsonTweakSignCase{ + Keys: strKeys, + ExpectedSig: hex.EncodeToString(testCase.expectedPartialSig), + AggNonce: hex.EncodeToString(testCase.aggNonce[:]), + ExpectedError: jsonError, + } + + var jsonTweaks []jsonTweak + for _, tweak := range testCase.tweaks { + jsonTweaks = append( + jsonTweaks, + jsonTweak{ + Tweak: hex.EncodeToString(tweak.Tweak[:]), + XOnly: tweak.IsXOnly, + }) + } + tweakSignCase.Tweaks = jsonTweaks + + jsonCases.TestCases = append(jsonCases.TestCases, tweakSignCase) + } } if *dumpJson { From 85356e81740b03c73adc5b5edd57c52970926b19 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Wed, 29 Jun 2022 18:37:16 +0200 Subject: [PATCH 07/11] btcec/schnorr/musig2: Throw error on invalid partial sig --- btcec/schnorr/musig2/sign.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/btcec/schnorr/musig2/sign.go b/btcec/schnorr/musig2/sign.go index 4f9439fe6a..0529c53c9a 100644 --- a/btcec/schnorr/musig2/sign.go +++ b/btcec/schnorr/musig2/sign.go @@ -85,7 +85,10 @@ func (p *PartialSignature) Decode(r io.Reader) error { return nil } - p.S.SetBytes(&sBytes) + overflows := p.S.SetBytes(&sBytes) + if overflows == 1 { + return ErrPartialSigInvalid + } return nil } From 5e960074ea29b8af62d6def11eeda8ff13501224 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Thu, 4 Aug 2022 11:51:02 +0200 Subject: [PATCH 08/11] btcec/schnorr/musig2: Add PartialSigVerify testvectors Adds testvectors from https://github.com/jonasnick/bips/commit/ebb6a7454bfc852f405fb7efb198c7e919248316 and https://github.com/jonasnick/bips/commit/6788ee5412ccb394f20b0758227a916d8f1fa631 --- btcec/schnorr/musig2/musig2_test.go | 377 ++++++++++++++++++++++++---- 1 file changed, 332 insertions(+), 45 deletions(-) diff --git a/btcec/schnorr/musig2/musig2_test.go b/btcec/schnorr/musig2/musig2_test.go index 7ff6e2e2c3..9608fcad27 100644 --- a/btcec/schnorr/musig2/musig2_test.go +++ b/btcec/schnorr/musig2/musig2_test.go @@ -308,11 +308,60 @@ var ( invalidSetKey1 = mustParseHex("00000000000000000000000000000000" + "00000000000000000000000000000007") + signExpected1 = mustParseHex("68537CC5234E505BD14061F8DA9E90C220A1818" + + "55FD8BDB7F127BB12403B4D3B") + signExpected2 = mustParseHex("2DF67BFFF18E3DE797E13C6475C963048138DAE" + + "C5CB20A357CECA7C8424295EA") + signExpected3 = mustParseHex("0D5B651E6DE34A29A12DE7A8B4183B4AE6A7F7F" + + "BE15CDCAFA4A3D1BCAABC7517") + signSetKeys = [][]byte{signSetPubKey, signSetKey2, signSetKey3, invalidPk1} aggregatedNonce = toPubNonceSlice(mustParseHex("028465FCF0BBDBCF443AA" + "BCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926" + "D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9")) + verifyPnonce1 = mustParsePubNonce("0337C87821AFD50A8644D820A8F3E02E49" + + "9C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352A" + + "A9405D1428C15F4B75F04DAE642A95C2548480") + verifyPnonce2 = mustParsePubNonce("0279BE667EF9DCBBAC55A06295CE870B07" + + "029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE" + + "870B07029BFCDB2DCE28D959F2815B16F81798") + verifyPnonce3 = mustParsePubNonce("032DE2662628C90B03F5E720284EB52FF7" + + "D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1C" + + "A6BEB2090C93D930321071AD40B2F44E599046") + + tweak1 = KeyTweakDesc{ + Tweak: [32]byte{ + 0xE8, 0xF7, 0x91, 0xFF, 0x92, 0x25, 0xA2, 0xAF, + 0x01, 0x02, 0xAF, 0xFF, 0x4A, 0x9A, 0x72, 0x3D, + 0x96, 0x12, 0xA6, 0x82, 0xA2, 0x5E, 0xBE, 0x79, + 0x80, 0x2B, 0x26, 0x3C, 0xDF, 0xCD, 0x83, 0xBB, + }, + } + tweak2 = KeyTweakDesc{ + Tweak: [32]byte{ + 0xae, 0x2e, 0xa7, 0x97, 0xcc, 0xf, 0xe7, 0x2a, + 0xc5, 0xb9, 0x7b, 0x97, 0xf3, 0xc6, 0x95, 0x7d, + 0x7e, 0x41, 0x99, 0xa1, 0x67, 0xa5, 0x8e, 0xb0, + 0x8b, 0xca, 0xff, 0xda, 0x70, 0xac, 0x4, 0x55, + }, + } + tweak3 = KeyTweakDesc{ + Tweak: [32]byte{ + 0xf5, 0x2e, 0xcb, 0xc5, 0x65, 0xb3, 0xd8, 0xbe, + 0xa2, 0xdf, 0xd5, 0xb7, 0x5a, 0x4f, 0x45, 0x7e, + 0x54, 0x36, 0x98, 0x9, 0x32, 0x2e, 0x41, 0x20, + 0x83, 0x16, 0x26, 0xf2, 0x90, 0xfa, 0x87, 0xe0, + }, + } + tweak4 = KeyTweakDesc{ + Tweak: [32]byte{ + 0x19, 0x69, 0xad, 0x73, 0xcc, 0x17, 0x7f, 0xa0, + 0xb4, 0xfc, 0xed, 0x6d, 0xf1, 0xf7, 0xbf, 0x99, + 0x7, 0xe6, 0x65, 0xfd, 0xe9, 0xba, 0x19, 0x6a, + 0x74, 0xfe, 0xd0, 0xa3, 0xcf, 0x5a, 0xef, 0x9d, + }, + } ) func formatTweakParity(tweaks []KeyTweakDesc) string { @@ -372,39 +421,6 @@ func TestMuSig2SigningTestVectors(t *testing.T) { jsonCases.SecNonce = hex.EncodeToString(secNonce[:]) - tweak1 := KeyTweakDesc{ - Tweak: [32]byte{ - 0xE8, 0xF7, 0x91, 0xFF, 0x92, 0x25, 0xA2, 0xAF, - 0x01, 0x02, 0xAF, 0xFF, 0x4A, 0x9A, 0x72, 0x3D, - 0x96, 0x12, 0xA6, 0x82, 0xA2, 0x5E, 0xBE, 0x79, - 0x80, 0x2B, 0x26, 0x3C, 0xDF, 0xCD, 0x83, 0xBB, - }, - } - tweak2 := KeyTweakDesc{ - Tweak: [32]byte{ - 0xae, 0x2e, 0xa7, 0x97, 0xcc, 0xf, 0xe7, 0x2a, - 0xc5, 0xb9, 0x7b, 0x97, 0xf3, 0xc6, 0x95, 0x7d, - 0x7e, 0x41, 0x99, 0xa1, 0x67, 0xa5, 0x8e, 0xb0, - 0x8b, 0xca, 0xff, 0xda, 0x70, 0xac, 0x4, 0x55, - }, - } - tweak3 := KeyTweakDesc{ - Tweak: [32]byte{ - 0xf5, 0x2e, 0xcb, 0xc5, 0x65, 0xb3, 0xd8, 0xbe, - 0xa2, 0xdf, 0xd5, 0xb7, 0x5a, 0x4f, 0x45, 0x7e, - 0x54, 0x36, 0x98, 0x9, 0x32, 0x2e, 0x41, 0x20, - 0x83, 0x16, 0x26, 0xf2, 0x90, 0xfa, 0x87, 0xe0, - }, - } - tweak4 := KeyTweakDesc{ - Tweak: [32]byte{ - 0x19, 0x69, 0xad, 0x73, 0xcc, 0x17, 0x7f, 0xa0, - 0xb4, 0xfc, 0xed, 0x6d, 0xf1, 0xf7, 0xbf, 0x99, - 0x7, 0xe6, 0x65, 0xfd, 0xe9, 0xba, 0x19, 0x6a, - 0x74, 0xfe, 0xd0, 0xa3, 0xcf, 0x5a, 0xef, 0x9d, - }, - } - testCases := []struct { keyOrder []int aggNonce [66]byte @@ -414,26 +430,23 @@ func TestMuSig2SigningTestVectors(t *testing.T) { }{ // Vector 1 { - keyOrder: []int{0, 1, 2}, - aggNonce: aggregatedNonce, - expectedPartialSig: mustParseHex("68537CC5234E505BD14" + - "061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B"), + keyOrder: []int{0, 1, 2}, + aggNonce: aggregatedNonce, + expectedPartialSig: signExpected1, }, // Vector 2 { - keyOrder: []int{1, 0, 2}, - aggNonce: aggregatedNonce, - expectedPartialSig: mustParseHex("2DF67BFFF18E3DE797E" + - "13C6475C963048138DAEC5CB20A357CECA7C8424295EA"), + keyOrder: []int{1, 0, 2}, + aggNonce: aggregatedNonce, + expectedPartialSig: signExpected2, }, // Vector 3 { - keyOrder: []int{1, 2, 0}, - aggNonce: aggregatedNonce, - expectedPartialSig: mustParseHex("0D5B651E6DE34A29A12" + - "DE7A8B4183B4AE6A7F7FBE15CDCAFA4A3D1BCAABC7517"), + keyOrder: []int{1, 2, 0}, + aggNonce: aggregatedNonce, + expectedPartialSig: signExpected3, }, // Vector 4: Signer 2 provided an invalid public key @@ -639,6 +652,261 @@ func TestMuSig2SigningTestVectors(t *testing.T) { } } +func TestMusig2PartialSigVerifyTestVectors(t *testing.T) { + t.Parallel() + + testCases := []struct { + partialSig []byte + nonces [][66]byte + pubnonceIndex int + keyOrder []int + tweaks []KeyTweakDesc + expectedError error + }{ + // A single x-only tweak. + { + keyOrder: []int{1, 2, 0}, + nonces: [][66]byte{ + verifyPnonce2, + verifyPnonce3, + verifyPnonce1, + }, + pubnonceIndex: 2, + partialSig: mustParseHex("5e24c7496b565debc3b9639e" + + "6f1304a21597f9603d3ab05b4913641775e1375b"), + tweaks: []KeyTweakDesc{genTweakParity(tweak1, true)}, + }, + // A single ordinary tweak. + { + keyOrder: []int{1, 2, 0}, + nonces: [][66]byte{ + verifyPnonce2, + verifyPnonce3, + verifyPnonce1, + }, + pubnonceIndex: 2, + partialSig: mustParseHex("78408ddcab4813d1394c97d4" + + "93ef1084195c1d4b52e63ecd7bc5991644e44ddd"), + tweaks: []KeyTweakDesc{genTweakParity(tweak1, false)}, + }, + // An ordinary tweak then an x-only tweak. + { + keyOrder: []int{1, 2, 0}, + nonces: [][66]byte{ + verifyPnonce2, + verifyPnonce3, + verifyPnonce1, + }, + pubnonceIndex: 2, + partialSig: mustParseHex("C3A829A81480E36EC3AB0529" + + "64509A94EBF34210403D16B226A6F16EC85B7357"), + tweaks: []KeyTweakDesc{ + genTweakParity(tweak1, false), + genTweakParity(tweak2, true), + }, + }, + + // Four tweaks, in the order: x-only, ordinary, x-only, ordinary. + { + keyOrder: []int{1, 2, 0}, + nonces: [][66]byte{ + verifyPnonce2, + verifyPnonce3, + verifyPnonce1, + }, + pubnonceIndex: 2, + partialSig: mustParseHex("8C4473C6A382BD3C4AD7BE5" + + "9818DA5ED7CF8CEC4BC21996CFDA08BB4316B8BC7"), + tweaks: []KeyTweakDesc{ + genTweakParity(tweak1, true), + genTweakParity(tweak2, false), + genTweakParity(tweak3, true), + genTweakParity(tweak4, false), + }, + }, + // Vector 8. + { + + partialSig: signExpected1, + pubnonceIndex: 0, + keyOrder: []int{0, 1, 2}, + nonces: [][66]byte{ + verifyPnonce1, + verifyPnonce2, + verifyPnonce3, + }, + }, + // Vector 9. + { + + partialSig: signExpected2, + pubnonceIndex: 1, + keyOrder: []int{1, 0, 2}, + nonces: [][66]byte{ + verifyPnonce2, + verifyPnonce1, + verifyPnonce3, + }, + }, + // Vector 10. + { + + partialSig: signExpected3, + pubnonceIndex: 2, + keyOrder: []int{1, 2, 0}, + nonces: [][66]byte{ + verifyPnonce2, + verifyPnonce3, + verifyPnonce1, + }, + }, + // Vector 11: Wrong signature (which is equal to the negation + // of valid signature expected[0]). + { + + partialSig: mustParseHex("97AC833ADCB1AFA42EBF9E0" + + "725616F3C9A0D5B614F6FE283CEAAA37A8FFAF406"), + pubnonceIndex: 0, + keyOrder: []int{0, 1, 2}, + nonces: [][66]byte{ + verifyPnonce1, + verifyPnonce2, + verifyPnonce3, + }, + expectedError: ErrPartialSigInvalid, + }, + // Vector 12: Wrong signer. + { + + partialSig: signExpected1, + pubnonceIndex: 1, + keyOrder: []int{0, 1, 2}, + nonces: [][66]byte{ + verifyPnonce1, + verifyPnonce2, + verifyPnonce3, + }, + expectedError: ErrPartialSigInvalid, + }, + // Vector 13: Signature exceeds group size. + { + + partialSig: mustParseHex("FFFFFFFFFFFFFFFFFFFFFFFF" + + "FFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"), + pubnonceIndex: 0, + keyOrder: []int{0, 1, 2}, + nonces: [][66]byte{ + verifyPnonce1, + verifyPnonce2, + verifyPnonce3, + }, + expectedError: ErrPartialSigInvalid, + }, + // Vector 14: Invalid pubnonce. + { + + partialSig: signExpected1, + pubnonceIndex: 0, + keyOrder: []int{0, 1, 2}, + nonces: [][66]byte{ + canParsePubNonce("020000000000000000000000000" + + "000000000000000000000000000000000000009"), + verifyPnonce2, + verifyPnonce3, + }, + expectedError: secp256k1.ErrPubKeyNotOnCurve, + }, + // Vector 15: Invalid public key. + { + + partialSig: signExpected1, + pubnonceIndex: 0, + keyOrder: []int{3, 1, 2}, + nonces: [][66]byte{ + verifyPnonce1, + verifyPnonce2, + verifyPnonce3, + }, + expectedError: secp256k1.ErrPubKeyNotOnCurve, + }, + } + + for _, testCase := range testCases { + + // todo find name + testName := fmt.Sprintf("%v/tweak=%v", testCase.pubnonceIndex, testCase.keyOrder) + + t.Run(testName, func(t *testing.T) { + + combinedNonce, err := AggregateNonces(testCase.nonces) + + switch { + case testCase.expectedError != nil && + errors.Is(err, testCase.expectedError): + + return + case err != nil: + t.Fatalf("unable to aggregate nonces %v", err) + } + + keySet := make([]*btcec.PublicKey, 0, len(testCase.keyOrder)) + for _, keyIndex := range testCase.keyOrder { + keyBytes := signSetKeys[keyIndex] + pub, err := schnorr.ParsePubKey(keyBytes) + + switch { + case testCase.expectedError != nil && + errors.Is(err, testCase.expectedError): + + return + case err != nil: + t.Fatalf("unable to parse pubkeys: %v", err) + } + + keySet = append(keySet, pub) + } + + ps := &PartialSignature{} + err = ps.Decode(bytes.NewBuffer(testCase.partialSig)) + + switch { + case testCase.expectedError != nil && + errors.Is(err, testCase.expectedError): + + return + case err != nil: + t.Fatal(err) + } + + var opts []SignOption + if len(testCase.tweaks) != 0 { + opts = append( + opts, WithTweaks(testCase.tweaks...), + ) + } + + err = verifyPartialSig( + ps, + testCase.nonces[testCase.pubnonceIndex], + combinedNonce, + keySet, + signSetKeys[testCase.keyOrder[testCase.pubnonceIndex]], + to32ByteSlice(signTestMsg), + opts..., + ) + + switch { + case testCase.expectedError != nil && + errors.Is(err, testCase.expectedError): + + return + case err != nil: + t.Fatalf("unable to aggregate nonces %v", err) + } + }) + } +} + type signer struct { privKey *btcec.PrivateKey pubKey *btcec.PublicKey @@ -1299,3 +1567,22 @@ func getNegGBytes() []byte { return pk } + +func mustParsePubNonce(str string) [PubNonceSize]byte { + b, err := hex.DecodeString(str) + if err != nil { + panic(fmt.Errorf("unable to parse hex: %v", err)) + } + if len(b) != PubNonceSize { + panic(fmt.Errorf("not a public nonce: %v", err)) + } + return toPubNonceSlice(b) +} + +func canParsePubNonce(str string) [PubNonceSize]byte { + b, err := hex.DecodeString(str) + if err != nil { + panic(fmt.Errorf("unable to parse hex: %v", err)) + } + return toPubNonceSlice(b) +} From 4b2fe9f83e6cfbf6b383caa51ea4637c0d2c19af Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Thu, 4 Aug 2022 11:55:19 +0200 Subject: [PATCH 09/11] btcec/schnorr/musig2: Add CombineSig testvectors This commit adds the testvectors from https://github.com/jonasnick/bips/commit/cdc3520c0726d73125f7dbf9826e3be9cffe1948 --- btcec/schnorr/musig2/musig2_test.go | 273 ++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) diff --git a/btcec/schnorr/musig2/musig2_test.go b/btcec/schnorr/musig2/musig2_test.go index 9608fcad27..1ff59a2ffa 100644 --- a/btcec/schnorr/musig2/musig2_test.go +++ b/btcec/schnorr/musig2/musig2_test.go @@ -16,6 +16,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/decred/dcrd/dcrec/secp256k1/v4" ) @@ -1535,6 +1536,266 @@ func (mr *memsetRandReader) Read(buf []byte) (n int, err error) { return len(buf), nil } +var ( + combineSigKey0 = mustParseHex("487D1B83B41B4CBBD07A111F1BBC7BDC8864CF" + + "EF5DBF96E46E51C68399B0BEF6") + combineSigKey1 = mustParseHex("4795C22501BF534BC478FF619407A7EC9E8D88" + + "83646D69BD43A0728944EA802F") + combineSigKey2 = mustParseHex("0F5BE837F3AB7E7FEFF1FAA44D673C2017206A" + + "E836D2C7893CDE4ACB7D55EDEB") + combineSigKey3 = mustParseHex("0FD453223E444FCA91FB5310990AE8A0C5DAA1" + + "4D2A4C8944E1C0BC80C30DF682") + + combineSigKeys = [][]byte{combineSigKey0, combineSigKey1, + combineSigKey2, combineSigKey3} + + combineSigAggNonce0 = mustParsePubNonce("024FA51009A56F0D6DF737131CE1" + + "FBBD833797AF3B4FE6BF0D68F4D49F68B0947E0248FB3BB9191F0CFF1380" + + "6A3A2F1429C23012654FCE4E41F7EC9169EAA6056B21") + combineSigAggNonce1 = mustParsePubNonce("023B11E63E2460E5E0F1561BB700" + + "FEA95B991DD9CA2CBBE92A3960641FA7469F6702CA4CD38375FE8BEB857C" + + "770807225BFC7D712F42BA896B83FC71138E56409B21") + combineSigAggNonce2 = mustParsePubNonce("03F98BEAA32B8A38FE3797C4E813" + + "DC9CE05ADBE32200035FB37EB0A030B735E9B6030E6118EC98EA2BA7A358" + + "C2E38E7E13E63681EEB683E067061BF7D52DCF08E615") + combineSigAggNonce3 = mustParsePubNonce("026491FBCFD47148043A0F7310E6" + + "2EF898C10F2D0376EE6B232EAAD36F3C2E29E303020CB17D168908E2904D" + + "E2EB571CD232CA805A6981D0F86CDBBD2F12BD91F6D0") + + psig0 = mustParseHex("E5C1CBD6E7E89FE9EE30D5F3B6D06B9C218846E4A1DEF4E" + + "E851410D51ABBD850") + psig1 = mustParseHex("9BC470F7F1C9BC848BDF179B0023282FFEF40908E0EF884" + + "59784A4355FC86D0C") + psig2 = mustParseHex("D5D8A09929BA264B2F5DF15ACA1CF2DEFA47C048DF0C323" + + "2E965FFE2F2831B1D") + psig3 = mustParseHex("A915197503C1051EA77DC91F01C3A0E60BFD64473BD536C" + + "B613F9645BD61C843") + psig4 = mustParseHex("99A144D7076A128022134E036B8BDF33811F7EAED9A1E48" + + "549B46D8A63D64DC9") + psig5 = mustParseHex("716A72A0C1E531EBB4555C8E29FD35C796F4F231C3B0391" + + "93D7E8D7AEFBDF5F7") + psig6 = mustParseHex("06B6DD04BC0F1EF740916730AD7DAC794255B1612217197" + + "65BDE9686A26633DC") + psig7 = mustParseHex("BF6D85D4930062726EBC6EBB184AFD68DBB3FED159C5019" + + "89690A62600D6FBAB") + + combineSigExpected0 = mustParseHex("4006D4D069F3B51E968762FF8074153E2" + + "78E5BCD221AABE0743CA001B77E79F581863CCED9B25C6E7A0FED8EB6F39" + + "3CD65CD7306D385DCF85CC6567DAA4E041B") + combineSigExpected1 = mustParseHex("98BCD40DFD94B47A3DA37D7B78EB6CCE8" + + "ABEACA23C3ADE6F4678902410EB35C67EEDBA0E2D7B2B69D6DBBA79CBE09" + + "3C64B9647A96B98C8C28AD3379BDFAEA21F") + combineSigExpected2 = mustParseHex("3741FEDCCDD7508B58DCB9A780FF5D974" + + "52EC8C0448D8C97004EA7175C14F2007A54D1DE356EBA6719278436EF111" + + "DFA8F1B832368371B9B7A25001709039679") + combineSigExpected3 = mustParseHex("F4B3DA3CF0D0F7CF5C1840593BF1A1A41" + + "5DA341619AE848F2210696DC8C7512540962C84EF7F0CEC491065F2D5772" + + "13CF10E8A63D153297361B3B172BE27B61F") + + combineSigTweak0 = mustParseHex32("B511DA492182A91B0FFB9A98020D55F260" + + "AE86D7ECBD0399C7383D59A5F2AF7C") + combineSigTweak1 = mustParseHex32("A815FE049EE3C5AAB66310477FBC8BCCCA" + + "C2F3395F59F921C364ACD78A2F48DC") + combineSigTweak2 = mustParseHex32("75448A87274B056468B977BE06EB1E9F65" + + "7577B7320B0A3376EA51FD420D18A8") + tweak0False = KeyTweakDesc{ + Tweak: combineSigTweak0, + IsXOnly: false, + } + tweak0True = KeyTweakDesc{ + Tweak: combineSigTweak0, + IsXOnly: true, + } + tweak1False = KeyTweakDesc{ + Tweak: combineSigTweak1, + IsXOnly: false, + } + tweak2True = KeyTweakDesc{ + Tweak: combineSigTweak2, + IsXOnly: true, + } + combineSigsMsg = mustParseHex32("599C67EA410D005B9DA90817CF03ED3B1C86" + + "8E4DA4EDF00A5880B0082C237869") +) + +func TestMusig2CombineSigsTestVectors(t *testing.T) { + + testCases := []struct { + partialSigs [][]byte + aggNonce [66]byte + keyOrder []int + expected []byte + tweaks []KeyTweakDesc + expectedError error + }{ + // Vector 1 + { + partialSigs: [][]byte{psig0, psig1}, + aggNonce: combineSigAggNonce0, + keyOrder: []int{0, 1}, + expected: combineSigExpected0, + }, + // Vector 2 + { + partialSigs: [][]byte{psig2, psig3}, + aggNonce: combineSigAggNonce1, + keyOrder: []int{0, 2}, + expected: combineSigExpected1, + }, + // Vector 3 + { + partialSigs: [][]byte{psig4, psig5}, + aggNonce: combineSigAggNonce2, + keyOrder: []int{0, 2}, + expected: combineSigExpected2, + tweaks: []KeyTweakDesc{tweak0False}, + }, + // Vector 4 + { + partialSigs: [][]byte{psig6, psig7}, + aggNonce: combineSigAggNonce3, + keyOrder: []int{0, 3}, + expected: combineSigExpected3, + tweaks: []KeyTweakDesc{ + tweak0True, + tweak1False, + tweak2True, + }, + }, + // Vector 5: Partial signature is invalid because it exceeds group size + { + partialSigs: [][]byte{ + psig7, + mustParseHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + "EBAAEDCE6AF48A03BBFD25E8CD0364141"), + }, + aggNonce: combineSigAggNonce3, + expectedError: ErrPartialSigInvalid, + }, + } + + for _, testCase := range testCases { + var pSigs []*PartialSignature + for _, partialSig := range testCase.partialSigs { + pSig := &PartialSignature{} + err := pSig.Decode(bytes.NewReader(partialSig)) + + switch { + case testCase.expectedError != nil && + errors.Is(err, testCase.expectedError): + + return + case err != nil: + t.Fatal(err) + } + + pSigs = append(pSigs, pSig) + } + + keySet := make([]*btcec.PublicKey, 0, len(testCase.keyOrder)) + for _, keyIndex := range testCase.keyOrder { + keyBytes := combineSigKeys[keyIndex] + pub, err := schnorr.ParsePubKey(keyBytes) + if err != nil { + t.Fatalf("unable to parse pubkeys: %v", err) + } + keySet = append(keySet, pub) + } + + uniqueKeyIndex := secondUniqueKeyIndex(keySet, false) + aggOpts := []KeyAggOption{ + WithUniqueKeyIndex(uniqueKeyIndex), + } + if len(testCase.tweaks) > 0 { + aggOpts = append(aggOpts, WithKeyTweaks(testCase.tweaks...)) + } + + combinedKey, _, _, err := AggregateKeys( + keySet, false, aggOpts..., + ) + if err != nil { + t.Fatal(err) + } + + aggPubkey, err := aggNonceToPubkey( + testCase.aggNonce, combinedKey, combineSigsMsg, + ) + if err != nil { + t.Fatal(err) + } + + var opts []CombineOption + if len(testCase.tweaks) > 0 { + opts = append(opts, WithTweakedCombine( + combineSigsMsg, keySet, testCase.tweaks, false, + )) + } + + sig := CombineSigs(aggPubkey, pSigs, opts...) + expectedSig, err := schnorr.ParseSignature(testCase.expected) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(sig.Serialize(), expectedSig.Serialize()) { + t.Fatalf("sigs not expected %x \n got %x", expectedSig.Serialize(), sig.Serialize()) + } + + if !sig.Verify(combineSigsMsg[:], combinedKey.FinalKey) { + t.Fatal("sig not valid for m") + } + } +} + +// aggNonceToPubkey gets a nonce as a public key for the TestMusig2CombineSigsTestVectors +// test. +// TODO(sputn1ck): build into intermediate routine. +func aggNonceToPubkey(combinedNonce [66]byte, combinedKey *AggregateKey, + msg [32]byte) (*btcec.PublicKey, error) { + + // b = int_from_bytes(tagged_hash('MuSig/noncecoef', aggnonce + bytes_from_point(Q) + msg)) % n + var ( + nonceMsgBuf bytes.Buffer + nonceBlinder btcec.ModNScalar + ) + nonceMsgBuf.Write(combinedNonce[:]) + nonceMsgBuf.Write(schnorr.SerializePubKey(combinedKey.FinalKey)) + nonceMsgBuf.Write(msg[:]) + nonceBlindHash := chainhash.TaggedHash(NonceBlindTag, nonceMsgBuf.Bytes()) + nonceBlinder.SetByteSlice(nonceBlindHash[:]) + + r1, err := btcec.ParsePubKey( + combinedNonce[:btcec.PubKeyBytesLenCompressed], + ) + if err != nil { + return nil, err + } + + r2, err := btcec.ParsePubKey( + combinedNonce[btcec.PubKeyBytesLenCompressed:], + ) + if err != nil { + return nil, err + } + + var nonce, r1J, r2J btcec.JacobianPoint + r1.AsJacobian(&r1J) + r2.AsJacobian(&r2J) + + // With our nonce blinding value, we'll now combine both the public + // nonces, using the blinding factor to tweak the second nonce: + // * R = R_1 + b*R_2 + btcec.ScalarMultNonConst(&nonceBlinder, &r2J, &r2J) + btcec.AddNonConst(&r1J, &r2J, &nonce) + + nonce.ToAffine() + + return btcec.NewPublicKey( + &nonce.X, &nonce.Y, + ), nil + +} + func memsetLoop(a []byte, v uint8) { for i := range a { a[i] = byte(v) @@ -1568,6 +1829,18 @@ func getNegGBytes() []byte { return pk } +func mustParseHex32(str string) [32]byte { + b, err := hex.DecodeString(str) + if err != nil { + panic(fmt.Errorf("unable to parse hex: %v", err)) + } + if len(b) != 32 { + panic(fmt.Errorf("not a 32 byte slice: %v", err)) + } + + return to32ByteSlice(b) +} + func mustParsePubNonce(str string) [PubNonceSize]byte { b, err := hex.DecodeString(str) if err != nil { From 44eb8c64f8d0f0e3a84400e7f93b9eb16ea08873 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Wed, 29 Jun 2022 18:48:02 +0200 Subject: [PATCH 10/11] btcec/schnorr/musig2: Allow infinity nonces This commit updates the musig2 module to allow infinity nonces, as per Musig2 0.4.0. --- btcec/curve.go | 52 ++++++++++++++++++ btcec/error.go | 5 ++ btcec/schnorr/musig2/musig2_test.go | 6 ++- btcec/schnorr/musig2/nonces.go | 36 +++---------- btcec/schnorr/musig2/sign.go | 84 +++++++++++++++-------------- 5 files changed, 113 insertions(+), 70 deletions(-) diff --git a/btcec/curve.go b/btcec/curve.go index 5224e35c8e..70a9229f9a 100644 --- a/btcec/curve.go +++ b/btcec/curve.go @@ -4,6 +4,8 @@ package btcec import ( + "fmt" + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" ) @@ -11,6 +13,9 @@ import ( // Jacobian projective coordinates and thus represents a point on the curve. type JacobianPoint = secp.JacobianPoint +// infinityPoint is the jacobian representation of the point at infinity. +var infinityPoint JacobianPoint + // MakeJacobianPoint returns a Jacobian point with the provided X, Y, and Z // coordinates. func MakeJacobianPoint(x, y, z *FieldVal) JacobianPoint { @@ -61,3 +66,50 @@ func ScalarBaseMultNonConst(k *ModNScalar, result *JacobianPoint) { func ScalarMultNonConst(k *ModNScalar, point, result *JacobianPoint) { secp.ScalarMultNonConst(k, point, result) } + +// ParseJacobian parses a byte slice point as a secp.Publickey and returns the +// pubkey as a JacobianPoint. If the nonce is a zero slice, the infinityPoint +// is returned. +func ParseJacobian(point []byte) (JacobianPoint, error) { + var result JacobianPoint + + if len(point) != 33 { + str := fmt.Sprintf("invalid nonce: invalid length: %v", + len(point)) + return JacobianPoint{}, makeError(secp.ErrPubKeyInvalidLen, str) + } + + if point[0] == 0x00 { + return infinityPoint, nil + } + + noncePk, err := secp.ParsePubKey(point) + if err != nil { + return JacobianPoint{}, err + } + noncePk.AsJacobian(&result) + + return result, nil +} + +// JacobianToByteSlice converts the passed JacobianPoint to a Pubkey +// and serializes that to a byte slice. If the JacobianPoint is the infinity +// point, a zero slice is returned. +func JacobianToByteSlice(point JacobianPoint) []byte { + if point.X == infinityPoint.X && point.Y == infinityPoint.Y { + return make([]byte, 33) + } + + point.ToAffine() + + return NewPublicKey( + &point.X, &point.Y, + ).SerializeCompressed() +} + +// GeneratorJacobian sets the passed JacobianPoint to the Generator Point. +func GeneratorJacobian(jacobian *JacobianPoint) { + var k ModNScalar + k.SetInt(1) + ScalarBaseMultNonConst(&k, jacobian) +} diff --git a/btcec/error.go b/btcec/error.go index 81ca2b044d..df6ec678a8 100644 --- a/btcec/error.go +++ b/btcec/error.go @@ -17,3 +17,8 @@ type Error = secp.Error // errors.As, so the caller can directly check against an error kind when // determining the reason for an error. type ErrorKind = secp.ErrorKind + +// makeError creates an secp.Error given a set of arguments. +func makeError(kind ErrorKind, desc string) Error { + return Error{Err: kind, Description: desc} +} diff --git a/btcec/schnorr/musig2/musig2_test.go b/btcec/schnorr/musig2/musig2_test.go index 1ff59a2ffa..f7f84be3d9 100644 --- a/btcec/schnorr/musig2/musig2_test.go +++ b/btcec/schnorr/musig2/musig2_test.go @@ -1461,7 +1461,7 @@ func TestMusig2AggregateNoncesTestVectors(t *testing.T) { }, expectedNonce: append( append([]byte{}, expectedNonce[0:33]...), - getGBytes()..., + getInfinityBytes()..., ), }, } @@ -1829,6 +1829,10 @@ func getNegGBytes() []byte { return pk } +func getInfinityBytes() []byte { + return make([]byte, 33) +} + func mustParseHex32(str string) [32]byte { b, err := hex.DecodeString(str) if err != nil { diff --git a/btcec/schnorr/musig2/nonces.go b/btcec/schnorr/musig2/nonces.go index 0ba100229a..66d2cb9c22 100644 --- a/btcec/schnorr/musig2/nonces.go +++ b/btcec/schnorr/musig2/nonces.go @@ -331,7 +331,7 @@ func AggregateNonces(pubNonces [][PubNonceSize]byte) ([PubNonceSize]byte, error) // function to extra 33 bytes at a time from the packed 2x public // nonces. type nonceSlicer func([PubNonceSize]byte) []byte - combineNonces := func(slicer nonceSlicer) (*btcec.PublicKey, error) { + combineNonces := func(slicer nonceSlicer) (btcec.JacobianPoint, error) { // Convert the set of nonces into jacobian coordinates we can // use to accumulate them all into each other. pubNonceJs := make([]*btcec.JacobianPoint, len(pubNonces)) @@ -339,14 +339,12 @@ func AggregateNonces(pubNonces [][PubNonceSize]byte) ([PubNonceSize]byte, error) // Using the slicer, extract just the bytes we need to // decode. var nonceJ btcec.JacobianPoint - pubNonce, err := btcec.ParsePubKey( - slicer(pubNonceBytes), - ) + + nonceJ, err := btcec.ParseJacobian(slicer(pubNonceBytes)) if err != nil { - return nil, err + return btcec.JacobianPoint{}, err } - pubNonce.AsJacobian(&nonceJ) pubNonceJs[i] = &nonceJ } @@ -359,27 +357,8 @@ func AggregateNonces(pubNonces [][PubNonceSize]byte) ([PubNonceSize]byte, error) ) } - // Now that we've aggregated all the points, we need to check - // if this point is the point at infinity, if so, then we'll - // just return the generator. At a later step, the malicious - // party will be detected. - if aggregateNonce == infinityPoint { - // TODO(roasbeef): better way to get the generator w/ - // the new API? -- via old curve params instead? - var generator btcec.JacobianPoint - one := new(btcec.ModNScalar).SetInt(1) - btcec.ScalarBaseMultNonConst(one, &generator) - - generator.ToAffine() - return btcec.NewPublicKey( - &generator.X, &generator.Y, - ), nil - } - aggregateNonce.ToAffine() - return btcec.NewPublicKey( - &aggregateNonce.X, &aggregateNonce.Y, - ), nil + return aggregateNonce, nil } // The final nonce public nonce is actually two nonces, one that @@ -392,6 +371,7 @@ func AggregateNonces(pubNonces [][PubNonceSize]byte) ([PubNonceSize]byte, error) if err != nil { return finalNonce, err } + combinedNonce2, err := combineNonces(func(n [PubNonceSize]byte) []byte { return n[btcec.PubKeyBytesLenCompressed:] }) @@ -399,10 +379,10 @@ func AggregateNonces(pubNonces [][PubNonceSize]byte) ([PubNonceSize]byte, error) return finalNonce, err } - copy(finalNonce[:], combinedNonce1.SerializeCompressed()) + copy(finalNonce[:], btcec.JacobianToByteSlice(combinedNonce1)) copy( finalNonce[btcec.PubKeyBytesLenCompressed:], - combinedNonce2.SerializeCompressed(), + btcec.JacobianToByteSlice(combinedNonce2), ) return finalNonce, nil diff --git a/btcec/schnorr/musig2/sign.go b/btcec/schnorr/musig2/sign.go index 0529c53c9a..fce61aa8a8 100644 --- a/btcec/schnorr/musig2/sign.go +++ b/btcec/schnorr/musig2/sign.go @@ -200,20 +200,6 @@ func Sign(secNonce [SecNonceSize]byte, privKey *btcec.PrivateKey, option(opts) } - // Next, we'll parse the public nonces into R1 and R2. - r1, err := btcec.ParsePubKey( - combinedNonce[:btcec.PubKeyBytesLenCompressed], - ) - if err != nil { - return nil, err - } - r2, err := btcec.ParsePubKey( - combinedNonce[btcec.PubKeyBytesLenCompressed:], - ) - if err != nil { - return nil, err - } - // Compute the hash of all the keys here as we'll need it do aggregate // the keys and also at the final step of signing. keysHash := keyHashFingerprint(pubKeys, opts.sortKeys) @@ -259,19 +245,31 @@ func Sign(secNonce [SecNonceSize]byte, privKey *btcec.PrivateKey, ) nonceBlinder.SetByteSlice(nonceBlindHash[:]) - var nonce, r1J, r2J btcec.JacobianPoint - r1.AsJacobian(&r1J) - r2.AsJacobian(&r2J) + // Next, we'll parse the public nonces into R1 and R2. + r1J, err := btcec.ParseJacobian( + combinedNonce[:btcec.PubKeyBytesLenCompressed], + ) + if err != nil { + return nil, err + } + r2J, err := btcec.ParseJacobian( + combinedNonce[btcec.PubKeyBytesLenCompressed:], + ) + if err != nil { + return nil, err + } // With our nonce blinding value, we'll now combine both the public // nonces, using the blinding factor to tweak the second nonce: // * R = R_1 + b*R_2 + var nonce btcec.JacobianPoint btcec.ScalarMultNonConst(&nonceBlinder, &r2J, &r2J) btcec.AddNonConst(&r1J, &r2J, &nonce) // If the combined nonce it eh point at infinity, then we'll bail out. if nonce == infinityPoint { - return nil, ErrNoncePointAtInfinity + G := btcec.Generator() + G.AsJacobian(&nonce) } // Next we'll parse out our two secret nonces, which we'll be using in @@ -375,6 +373,7 @@ func (p *PartialSignature) Verify(pubNonce [PubNonceSize]byte, signingKey *btcec.PublicKey, msg [32]byte, signOpts ...SignOption) bool { pubKey := schnorr.SerializePubKey(signingKey) + return verifyPartialSig( p, pubNonce, combinedNonce, keySet, pubKey, msg, signOpts..., ) == nil @@ -399,19 +398,6 @@ func verifyPartialSig(partialSig *PartialSignature, pubNonce [PubNonceSize]byte, // Next we'll parse out the two public nonces into something we can // use. // - // TODO(roasbeef): consolidate, new method - r1, err := btcec.ParsePubKey( - combinedNonce[:btcec.PubKeyBytesLenCompressed], - ) - if err != nil { - return err - } - r2, err := btcec.ParsePubKey( - combinedNonce[btcec.PubKeyBytesLenCompressed:], - ) - if err != nil { - return err - } // Compute the hash of all the keys here as we'll need it do aggregate // the keys and also at the final step of verification. @@ -456,45 +442,61 @@ func verifyPartialSig(partialSig *PartialSignature, pubNonce [PubNonceSize]byte, nonceBlindHash := chainhash.TaggedHash(NonceBlindTag, nonceMsgBuf.Bytes()) nonceBlinder.SetByteSlice(nonceBlindHash[:]) - var nonce, r1J, r2J btcec.JacobianPoint - r1.AsJacobian(&r1J) - r2.AsJacobian(&r2J) + r1J, err := btcec.ParseJacobian( + combinedNonce[:btcec.PubKeyBytesLenCompressed], + ) + if err != nil { + return err + } + r2J, err := btcec.ParseJacobian( + combinedNonce[btcec.PubKeyBytesLenCompressed:], + ) + if err != nil { + return err + } // With our nonce blinding value, we'll now combine both the public // nonces, using the blinding factor to tweak the second nonce: // * R = R_1 + b*R_2 + + var nonce btcec.JacobianPoint btcec.ScalarMultNonConst(&nonceBlinder, &r2J, &r2J) btcec.AddNonConst(&r1J, &r2J, &nonce) // Next, we'll parse out the set of public nonces this signer used to // generate the signature. - pubNonce1, err := btcec.ParsePubKey( + pubNonce1J, err := btcec.ParseJacobian( pubNonce[:btcec.PubKeyBytesLenCompressed], ) if err != nil { return err } - pubNonce2, err := btcec.ParsePubKey( + pubNonce2J, err := btcec.ParseJacobian( pubNonce[btcec.PubKeyBytesLenCompressed:], ) if err != nil { return err } + // If the nonce is the infinity point we set it to the Generator. + if nonce == infinityPoint { + btcec.GeneratorJacobian(&nonce) + } else { + nonce.ToAffine() + } + // We'll perform a similar aggregation and blinding operator as we did // above for the combined nonces: R' = R_1' + b*R_2'. - var pubNonceJ, pubNonce1J, pubNonce2J btcec.JacobianPoint - pubNonce1.AsJacobian(&pubNonce1J) - pubNonce2.AsJacobian(&pubNonce2J) + var pubNonceJ btcec.JacobianPoint + btcec.ScalarMultNonConst(&nonceBlinder, &pubNonce2J, &pubNonce2J) btcec.AddNonConst(&pubNonce1J, &pubNonce2J, &pubNonceJ) - nonce.ToAffine() + pubNonceJ.ToAffine() // If the combined nonce used in the challenge hash has an odd y // coordinate, then we'll negate our final public nonce. if nonce.Y.IsOdd() { - pubNonceJ.ToAffine() pubNonceJ.Y.Negate(1) pubNonceJ.Y.Normalize() } From 06ce9608aa3bd2de895b5c12e545e9e0f2935c42 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Wed, 29 Jun 2022 18:52:32 +0200 Subject: [PATCH 11/11] btcec/schnorr/musig2: add infinity testvectors This commit adds the testvectors from https://github.com/jonasnick/bips/commit/20ba03106d245d42375e65f739c58dd9074daf6e --- btcec/schnorr/musig2/musig2_test.go | 48 ++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/btcec/schnorr/musig2/musig2_test.go b/btcec/schnorr/musig2/musig2_test.go index f7f84be3d9..e58a6d4328 100644 --- a/btcec/schnorr/musig2/musig2_test.go +++ b/btcec/schnorr/musig2/musig2_test.go @@ -316,6 +316,9 @@ var ( signExpected3 = mustParseHex("0D5B651E6DE34A29A12DE7A8B4183B4AE6A7F7F" + "BE15CDCAFA4A3D1BCAABC7517") + signExpected4 = mustParseHex("8D5E0407FB4756EEBCD86264C32D792EE36EEB6" + + "9E952BBB30B8E41BEBC4D22FA") + signSetKeys = [][]byte{signSetPubKey, signSetKey2, signSetKey3, invalidPk1} aggregatedNonce = toPubNonceSlice(mustParseHex("028465FCF0BBDBCF443AA" + @@ -330,6 +333,9 @@ var ( verifyPnonce3 = mustParsePubNonce("032DE2662628C90B03F5E720284EB52FF7" + "D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1C" + "A6BEB2090C93D930321071AD40B2F44E599046") + verifyPnonce4 = mustParsePubNonce("0237C87821AFD50A8644D820A8F3E02E49" + + "9C931865C2360FB43D0A0D20DAFE07EA0387BF891D2A6DEAEBADC909352A" + + "A9405D1428C15F4B75F04DAE642A95C2548480") tweak1 = KeyTweakDesc{ Tweak: [32]byte{ @@ -449,15 +455,21 @@ func TestMuSig2SigningTestVectors(t *testing.T) { aggNonce: aggregatedNonce, expectedPartialSig: signExpected3, }, + // Vector 4 Both halves of aggregate nonce correspond to point at infinity + { + keyOrder: []int{0, 1}, + aggNonce: mustNonceAgg([][66]byte{verifyPnonce1, verifyPnonce4}), + expectedPartialSig: signExpected4, + }, - // Vector 4: Signer 2 provided an invalid public key + // Vector 5: Signer 2 provided an invalid public key { keyOrder: []int{1, 0, 3}, aggNonce: aggregatedNonce, expectedError: secp256k1.ErrPubKeyNotOnCurve, }, - // Vector 5: Aggregate nonce is invalid due wrong tag, 0x04, + // Vector 6: Aggregate nonce is invalid due wrong tag, 0x04, // in the first half. { @@ -470,7 +482,7 @@ func TestMuSig2SigningTestVectors(t *testing.T) { expectedError: secp256k1.ErrPubKeyInvalidFormat, }, - // Vector 6: Aggregate nonce is invalid because the second half + // Vector 7: Aggregate nonce is invalid because the second half // does not correspond to an X coordinate. { @@ -483,7 +495,7 @@ func TestMuSig2SigningTestVectors(t *testing.T) { expectedError: secp256k1.ErrPubKeyNotOnCurve, }, - // Vector 7: Aggregate nonce is invalid because the second half + // Vector 8: Aggregate nonce is invalid because the second half // exceeds field size. { @@ -725,7 +737,7 @@ func TestMusig2PartialSigVerifyTestVectors(t *testing.T) { genTweakParity(tweak4, false), }, }, - // Vector 8. + // Vector 9. { partialSig: signExpected1, @@ -737,7 +749,7 @@ func TestMusig2PartialSigVerifyTestVectors(t *testing.T) { verifyPnonce3, }, }, - // Vector 9. + // Vector 10. { partialSig: signExpected2, @@ -749,7 +761,7 @@ func TestMusig2PartialSigVerifyTestVectors(t *testing.T) { verifyPnonce3, }, }, - // Vector 10. + // Vector 11. { partialSig: signExpected3, @@ -761,7 +773,19 @@ func TestMusig2PartialSigVerifyTestVectors(t *testing.T) { verifyPnonce1, }, }, - // Vector 11: Wrong signature (which is equal to the negation + // Vector 12: Both halves of aggregate nonce correspond to + // point at infinity. + { + + partialSig: signExpected4, + pubnonceIndex: 0, + keyOrder: []int{0, 1}, + nonces: [][66]byte{ + verifyPnonce1, + verifyPnonce4, + }, + }, + // Vector 13: Wrong signature (which is equal to the negation // of valid signature expected[0]). { @@ -1796,6 +1820,14 @@ func aggNonceToPubkey(combinedNonce [66]byte, combinedKey *AggregateKey, } +func mustNonceAgg(nonces [][66]byte) [66]byte { + aggNonce, err := AggregateNonces(nonces) + if err != nil { + panic("can't aggregate nonces") + } + return aggNonce +} + func memsetLoop(a []byte, v uint8) { for i := range a { a[i] = byte(v)