btcec/schnorr/musig: update nonce test vectors to musig2 1.0.0

This commit is contained in:
Olaoluwa Osuntokun 2022-10-11 15:29:24 -07:00
parent 1567f20055
commit 3d9f4484df
No known key found for this signature in database
GPG Key ID: 3BBD59E99B280306
7 changed files with 279 additions and 207 deletions

View File

@ -6,6 +6,11 @@ require (
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1
github.com/davecgh/go-spew v1.1.1
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1
github.com/stretchr/testify v1.8.0
)
require github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
require (
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@ -1,8 +1,21 @@
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -0,0 +1,54 @@
{
"pnonces": [
"020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E66603BA47FBC1834437B3212E89A84D8425E7BF12E0245D98262268EBDCB385D50641",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833",
"020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E6660279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60379BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"04FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B831",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A602FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30"
],
"valid_test_cases": [
{
"pnonce_indices": [0, 1],
"expected": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B024725377345BDE0E9C33AF3C43C0A29A9249F2F2956FA8CFEB55C8573D0262DC8"
},
{
"pnonce_indices": [2, 3],
"expected": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B000000000000000000000000000000000000000000000000000000000000000000",
"comment": "Sum of second points encoded in the nonces is point at infinity which is serialized as 33 zero bytes"
}
],
"error_test_cases": [
{
"pnonce_indices": [0, 4],
"error": {
"type": "invalid_contribution",
"signer": 1,
"contrib": "pubnonce"
},
"comment": "Public nonce from signer 1 is invalid due wrong tag, 0x04, in the first half",
"btcec_err": "invalid public key: unsupported format: 4"
},
{
"pnonce_indices": [5, 1],
"error": {
"type": "invalid_contribution",
"signer": 0,
"contrib": "pubnonce"
},
"comment": "Public nonce from signer 0 is invalid because the second half does not correspond to an X coordinate",
"btcec_err": "invalid public key: x coordinate 48c264cdd57d3c24d79990b0f865674eb62a0f9018277a95011b41bfc193b831 is not on the secp256k1 curve"
},
{
"pnonce_indices": [6, 1],
"error": {
"type": "invalid_contribution",
"signer": 0,
"contrib": "pubnonce"
},
"comment": "Public nonce from signer 0 is invalid because second half exceeds field size",
"btcec_err": "invalid public key: x >= field prime"
}
]
}

View File

@ -0,0 +1,36 @@
{
"test_cases": [
{
"rand_": "0000000000000000000000000000000000000000000000000000000000000000",
"sk": "0202020202020202020202020202020202020202020202020202020202020202",
"aggpk": "0707070707070707070707070707070707070707070707070707070707070707",
"msg": "0101010101010101010101010101010101010101010101010101010101010101",
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
"expected": "BC6C683EBBCC39DCB3C29B3D010D2AAA7C86CFB562FC41ED9A460EE061013E75FB4AD2F0B816713269800D018803906D5481E00A940EAB4F4AC49B4A372EB0F4"
},
{
"rand_": "0000000000000000000000000000000000000000000000000000000000000000",
"sk": "0202020202020202020202020202020202020202020202020202020202020202",
"aggpk": "0707070707070707070707070707070707070707070707070707070707070707",
"msg": "",
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
"expected": "AAC4BFD707F4953B4063851D7E4AAD5C59D5D0BFB0E71012788A85698B5ACF8F11834D5051928424BA501C8CD064F3F942F8D4A07D8A2ED79F153E4ABD9EBBE9"
},
{
"rand_": "0000000000000000000000000000000000000000000000000000000000000000",
"sk": "0202020202020202020202020202020202020202020202020202020202020202",
"aggpk": "0707070707070707070707070707070707070707070707070707070707070707",
"msg": "2626262626262626262626262626262626262626262626262626262626262626262626262626",
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
"expected": "DF54500DD2B503DBA3753C48A9D6B67E6C11EC4325EDD1DC256C7F75D6A85DBECA6D9857A6F3F292FB3B50DBCBF69FADB67B1CDDB0EA6EB693F6455C4C9088E1"
},
{
"rand_": "0000000000000000000000000000000000000000000000000000000000000000",
"sk": null,
"aggpk": null,
"msg": null,
"extra_in": null,
"expected": "7B3B5A002356471AF0E961DE2549C121BD0D48ABCEEDC6E034BDDF86AD3E0A187ECEE674CEF7364B0BC4BEEFB8B66CAD89F98DE2F8C5A5EAD5D1D1E4BD7D04CD"
}
]
}

View File

@ -46,6 +46,8 @@ var (
"B65481B6BAAFB3C5810106717BEB")
keyCombo4, _ = hex.DecodeString("2EB18851887E7BDC5E830E89B19DDBC28078" +
"F1FA88AAD0AD01CA06FE4F80210B")
testVectorBaseDir = "data"
)
// getInfinityTweak returns a tweak that, when tweaking the Generator, triggers
@ -1340,210 +1342,6 @@ func TestMuSigEarlyNonce(t *testing.T) {
}
}
// 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: "BC6C683EBBCC39DCB3C29B3D010D2AAA7C86C" +
"FB562FC41ED9A460EE061013E75FB4AD2F0B81671326" +
"9800D018803906D5481E00A940EAB4F4AC49B4A372EB0F4",
},
{
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[:])
}
}
}
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]...),
getInfinityBytes()...,
),
},
}
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
}

View File

@ -255,10 +255,10 @@ func genNonceAuxBytes(rand []byte, i int,
return nil, err
}
switch len(opts.msg) {
switch {
// If the message isn't present, then we'll just write out a single
// uint8 of a zero byte: m_prefixed = bytes(1, 0).
case 0:
case opts.msg == nil:
if _, err := w.Write([]byte{0x00}); err != nil {
return nil, err
}
@ -266,6 +266,8 @@ func genNonceAuxBytes(rand []byte, i int,
// Otherwise, we'll write a single byte of 0x01 with a 1 byte length
// prefix, followed by the message itself with an 8 byte length prefix:
// m_prefixed = bytes(1, 1) || bytes(8, len(m)) || m.
case len(opts.msg) == 0:
fallthrough
default:
if _, err := w.Write([]byte{0x01}); err != nil {
return nil, err

View File

@ -0,0 +1,164 @@
// Copyright 2013-2022 The btcsuite developers
package musig2
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"path"
"testing"
"github.com/stretchr/testify/require"
)
type nonceGenTestCase struct {
Rand string `json:"rand_"`
Sk string `json:"sk"`
AggPk string `json:"aggpk"`
Msg *string `json:"msg"`
ExtraIn string `json:"extra_in"`
Expected string `json:"expected"`
}
type nonceGenTestCases struct {
TestCases []nonceGenTestCase `json:"test_cases"`
}
const (
nonceGenTestVectorsFileName = "nonce_gen_vectors.json"
nonceAggTestVectorsFileName = "nonce_agg_vectors.json"
)
// TestMusig2NonceGenTestVectors tests the nonce generation function with the
// testvectors defined in the Musig2 BIP.
func TestMusig2NonceGenTestVectors(t *testing.T) {
t.Parallel()
testVectorPath := path.Join(
testVectorBaseDir, nonceGenTestVectorsFileName,
)
testVectorBytes, err := os.ReadFile(testVectorPath)
require.NoError(t, err)
var testCases nonceGenTestCases
require.NoError(t, json.Unmarshal(testVectorBytes, &testCases))
for i, testCase := range testCases.TestCases {
testCase := testCase
customOpts := nonceGenOpts{
randReader: &memsetRandReader{i: 0},
secretKey: mustParseHex(testCase.Sk),
combinedKey: mustParseHex(testCase.AggPk),
auxInput: mustParseHex(testCase.ExtraIn),
}
if testCase.Msg != nil {
customOpts.msg = mustParseHex(*testCase.Msg)
}
t.Run(fmt.Sprintf("test_case=%v", i), func(t *testing.T) {
nonce, err := GenNonces(withCustomOptions(customOpts))
if err != nil {
t.Fatalf("err gen nonce aux bytes %v", err)
}
expectedBytes, _ := hex.DecodeString(testCase.Expected)
if !bytes.Equal(nonce.SecNonce[:], expectedBytes) {
t.Fatalf("nonces don't match: expected %x, got %x",
expectedBytes, nonce.SecNonce[:])
}
})
}
}
type nonceAggError struct {
Type string `json:"type"`
Signer int `json:"signer"`
Contrib string `json:"contrib"`
}
type nonceAggValidCase struct {
Indices []int `json:"pnonce_indices"`
Expected string `json:"expected"`
Comment string `json:"comment"`
}
type nonceAggInvalidCase struct {
Indices []int `json:"pnonce_indices"`
Error nonceAggError `json:"error"`
Comment string `json:"comment"`
ExpectedErr string `json:"btcec_err"`
}
type nonceAggTestCases struct {
Nonces []string `json:"pnonces"`
ValidCases []nonceAggValidCase `json:"valid_test_cases"`
InvalidCases []nonceAggInvalidCase `json:"error_test_cases"`
}
// TestMusig2AggregateNoncesTestVectors tests that the musig2 implementation
// passes the nonce aggregration test vectors for musig2 1.0.
func TestMusig2AggregateNoncesTestVectors(t *testing.T) {
t.Parallel()
testVectorPath := path.Join(
testVectorBaseDir, nonceAggTestVectorsFileName,
)
testVectorBytes, err := os.ReadFile(testVectorPath)
require.NoError(t, err)
var testCases nonceAggTestCases
require.NoError(t, json.Unmarshal(testVectorBytes, &testCases))
nonces := make([][PubNonceSize]byte, len(testCases.Nonces))
for i := range testCases.Nonces {
var nonce [PubNonceSize]byte
copy(nonce[:], mustParseHex(testCases.Nonces[i]))
nonces[i] = nonce
}
for i, testCase := range testCases.ValidCases {
testCase := testCase
var testNonces [][PubNonceSize]byte
for _, idx := range testCase.Indices {
testNonces = append(testNonces, nonces[idx])
}
t.Run(fmt.Sprintf("valid_case=%v", i), func(t *testing.T) {
aggregatedNonce, err := AggregateNonces(testNonces)
require.NoError(t, err)
var expectedNonce [PubNonceSize]byte
copy(expectedNonce[:], mustParseHex(testCase.Expected))
require.Equal(t, aggregatedNonce[:], expectedNonce[:])
})
}
for i, testCase := range testCases.InvalidCases {
var testNonces [][PubNonceSize]byte
for _, idx := range testCase.Indices {
testNonces = append(testNonces, nonces[idx])
}
t.Run(fmt.Sprintf("invalid_case=%v", i), func(t *testing.T) {
_, err := AggregateNonces(testNonces)
require.True(t, err != nil)
require.Equal(t, testCase.ExpectedErr, err.Error())
})
}
}