Skip to content

Commit

Permalink
btcec/schnorr/musig2: add sig combine test vectors
Browse files Browse the repository at this point in the history
  • Loading branch information
Roasbeef committed Oct 21, 2022
1 parent ca28a98 commit 5d895bb
Show file tree
Hide file tree
Showing 3 changed files with 268 additions and 38 deletions.
86 changes: 86 additions & 0 deletions btcec/schnorr/musig2/data/sig_agg_vectors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"pubkeys": [
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
"02D2DC6F5DF7C56ACF38C7FA0AE7A759AE30E19B37359DFDE015872324C7EF6E05",
"03C7FB101D97FF930ACD0C6760852EF64E69083DE0B06AC6335724754BB4B0522C",
"02352433B21E7E05D3B452B81CAE566E06D2E003ECE16D1074AABA4289E0E3D581"
],
"pnonces": [
"0300A32F8548F59C533F55DB9754E3C0BA3C2544F085649FDCE42B8BD3F244C2CA0384449BED61004E8863452A38534E91875516C3CC543122CE2BE1F31845025588",
"03F66B072A869BC2A57D776D487151D707E82B4F1B885066A589858C1BF3871DB603ED391C9658AB6031A96ACBD5E2D9FEC465EFDC8C0D0B765C9B9F3579D520FB6F",
"03A5791CA078E278126EF457C25B5C835F7282C0A47BDBF464BA35C3769427D5CD034D40350F8A5590985E38AAEFC3C695DF671C2E5498E2B60C082C546E06ECAF78",
"020DE6382B8C0550E8174D5263B981224EBCFEF7706588B6936177FEB68E639B8C02BA5F18DDB3487AD087F63CEF7D7818AC8ECA3D6B736113FF36FB25D113F514F6",
"031883080513BB69B31367F9A7B5F4E81246C627060A7414B7F137FA8459F261990345445505F158EDCFDF0D4BF26E04E018C143BF76B5D457AE57DF06CA41371DF0",
"0300028E83123E7FAB1E1F230547CE8B96CC23F13197312972DE72AACBA98EF9870274C2D8566E9E021AA7E2DDDA01B52AE670E0742418F147610528B65ACDB4D0B3"
],
"tweaks": [
"B511DA492182A91B0FFB9A98020D55F260AE86D7ECBD0399C7383D59A5F2AF7C",
"A815FE049EE3C5AAB66310477FBC8BCCCAC2F3395F59F921C364ACD78A2F48DC",
"75448A87274B056468B977BE06EB1E9F657577B7320B0A3376EA51FD420D18A8"
],
"psigs": [
"7918521F42E5727FE2E82D802876E0C8844336FDA1B58C82696A55B0188C8B3D",
"599044037AE15C4A99FB94F022B48E7AB215BF703954EC0B83D0E06230476001",
"F05BE3CA783AD1FAF68C5059B43F859BFD4EBB0242459DF2C6BF013F4217F7E7",
"BF85B2A751066466C24A5E7FA6C90DBAADAC2DF1F0BB48546AE239E340437CEB",
"142076B034A7401123EFB07E2317DF819B86B3FFA17180DDD093997D018270D0",
"B7A0C7F5B325B7993925E56B60F53EF8198169F31E1AF7E62BBEF1C5DCD1BA22",
"C717ECA32C148CE8EB8882CD9656DF9C64929DCAE9AF798E381B1E888DDF0F8F",
"5988823E78488D8005311E16E5EA67AF70514CB44F5A5CD51FFA262BEEAA21CE",
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
],
"msg": "599C67EA410D005B9DA90817CF03ED3B1C868E4DA4EDF00A5880B0082C237869",
"valid_test_cases": [
{
"aggnonce": "02BC34CDF6FA1298D7B6A126812FAD0739005BC44E45C21276EEFE41AAF841C86F03F3562AED52243BB99F43D1677DB59F0FEFB961633997F7AC924B78FBD0B0334F",
"nonce_indices": [0, 1],
"key_indices": [0, 1],
"tweak_indices": [],
"is_xonly": [],
"psig_indices": [0, 1],
"expected": "CA3C28729659E50F829F55DC5DB1DE88A05D1702B4165B85F95B627FC57733F8D2A89622BDC6CECA7CE3C2704B2B6F433658F66DDB0A788DED3B361248D3EB3E"
},
{
"aggnonce": "035538518B8043CF4EACD0E701A80657B741C0E6445EC1D6C6177964D22C642971030CFE657EC882F4E08E751B883A78AC1491B30FC86CB57AF2DFF012C2BE6DF1F2",
"nonce_indices": [0, 2],
"key_indices": [0, 2],
"tweak_indices": [],
"is_xonly": [],
"psig_indices": [2, 3],
"expected": "3997A11DFF76349532CF25E761365EA1D4F24B62EB23A12A9DAABD5976C3DB9FAFE19671C9413661B8D6AED95B089357F04C0C0D83B8460B71CEDC95B2253391"
},
{
"aggnonce": "024366775E6FFBEBBB954225936BAED71A3884C7933B18225088D19E7AF12D8D5D028D79A520B347B793FFE897A7EB79A4366A3FDCDC652C243FAC3976B3D6DF8AB2",
"nonce_indices": [0, 3],
"key_indices": [0, 2],
"tweak_indices": [0],
"is_xonly": [false],
"psig_indices": [4, 5],
"expected": "5AF759C2839B7FEE59D31DAB800F82FC21258457773A3B1F69F5228C80CAD4317EA39AD756601030E4D4051B7C9A25AB4DE7CB39BED26E0A03A1B2ED5B747F7F"
},
{
"aggnonce": "03B25098C6D0B72DC5717314AF26C126609B4776AA468553DD4354EE20B216B227027D242E9203499173A74E286C1F796F2711E171EE937706BBEA2F4DB10C4E6809",
"nonce_indices": [0, 4],
"key_indices": [0, 3],
"tweak_indices": [0, 1, 2],
"is_xonly": [true, false, true],
"psig_indices": [6, 7],
"expected": "B495A478F91D6E10BF08A156E46D9E62B4C5399C1AEDDA1A9D306F06AFB8A52F2C078FD6B50DDBC33BFFE583C3C1E3D0D5E52891E190101C70D2278BCA943457"
}
],
"error_test_cases": [
{
"aggnonce": "03B25098C6D0B72DC5717314AF26C126609B4776AA468553DD4354EE20B216B227027D242E9203499173A74E286C1F796F2711E171EE937706BBEA2F4DB10C4E6809",
"nonce_indices": [0, 4],
"key_indices": [0, 3],
"tweak_indices": [0, 1, 2],
"is_xonly": [true, false, true],
"psig_indices": [7, 8],
"error": {
"type": "invalid_contribution",
"signer": 1
},
"comment": "Partial signature is invalid because it exceeds group size"
}
]
}
96 changes: 58 additions & 38 deletions btcec/schnorr/musig2/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,58 @@ func WithBip86SignTweak() SignOption {
}
}

// computeSigningNonce calculates the final nonce used for signing. This will
// be the R value used in the final signature.
func computeSigningNonce(combinedNonce [PubNonceSize]byte,
combinedKey *btcec.PublicKey, msg [32]byte) (
*btcec.JacobianPoint, *btcec.ModNScalar, error) {

// Next we'll compute the value b, that blinds our second public
// nonce:
// * b = h(tag=NonceBlindTag, combinedNonce || combinedKey || m).
var (
nonceMsgBuf bytes.Buffer
nonceBlinder btcec.ModNScalar
)
nonceMsgBuf.Write(combinedNonce[:])
nonceMsgBuf.Write(schnorr.SerializePubKey(combinedKey))
nonceMsgBuf.Write(msg[:])
nonceBlindHash := chainhash.TaggedHash(
NonceBlindTag, nonceMsgBuf.Bytes(),
)
nonceBlinder.SetByteSlice(nonceBlindHash[:])

// Next, we'll parse the public nonces into R1 and R2.
r1J, err := btcec.ParseJacobian(
combinedNonce[:btcec.PubKeyBytesLenCompressed],
)
if err != nil {
return nil, nil, err
}
r2J, err := btcec.ParseJacobian(
combinedNonce[btcec.PubKeyBytesLenCompressed:],
)
if err != nil {
return nil, 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 is the point at infinity, we'll use the
// generator point instead.
if nonce == infinityPoint {
G := btcec.Generator()
G.AsJacobian(&nonce)
}

return &nonce, &nonceBlinder, nil
}

// Sign generates a musig2 partial signature given the passed key set, secret
// nonce, public nonce, and private keys. This method returns an error if the
// generated nonces are either too large, or end up mapping to the point at
Expand Down Expand Up @@ -230,48 +282,16 @@ func Sign(secNonce [SecNonceSize]byte, privKey *btcec.PrivateKey,
return nil, err
}

// Next we'll compute the value b, that blinds our second public
// nonce:
// * b = h(tag=NonceBlindTag, combinedNonce || combinedKey || m).
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[:])

// 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:],
// We'll now combine both the public nonces, using the blinding factor
// to tweak the second nonce:
// * R = R_1 + b*R_2
nonce, nonceBlinder, err := computeSigningNonce(
combinedNonce, combinedKey.FinalKey, msg,
)
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 {
G := btcec.Generator()
G.AsJacobian(&nonce)
}

// Next we'll parse out our two secret nonces, which we'll be using in
// the core signing process below.
var k1, k2 btcec.ModNScalar
Expand Down Expand Up @@ -336,7 +356,7 @@ func Sign(secNonce [SecNonceSize]byte, privKey *btcec.PrivateKey,
// With mu constructed, we can finally generate our partial signature
// as: s = (k1_1 + b*k_2 + e*a*d) mod n.
s := new(btcec.ModNScalar)
s.Add(&k1).Add(k2.Mul(&nonceBlinder)).Add(e.Mul(a).Mul(&privKeyScalar))
s.Add(&k1).Add(k2.Mul(nonceBlinder)).Add(e.Mul(a).Mul(&privKeyScalar))

sig := NewPartialSignature(s, nonceKey)

Expand Down
124 changes: 124 additions & 0 deletions btcec/schnorr/musig2/sign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (

const (
signVerifyTestVectorFileName = "sign_verify_vectors.json"

sigCombineTestVectorFileName = "sig_agg_vectors.json"
)

type signVerifyValidCase struct {
Expand Down Expand Up @@ -265,3 +267,125 @@ func TestMusig2SignVerify(t *testing.T) {
}

}

type sigCombineValidCase struct {
AggNonce string `json:"aggnonce"`

NonceIndices []int `json:"nonce_indices"`

Indices []int `json:"key_indices"`

TweakIndices []int `json:"tweak_indices"`

IsXOnly []bool `json:"is_xonly"`

PSigIndices []int `json:"psig_indices"`

Expected string `json:"expected"`
}

type sigCombineTestVectors struct {
PubKeys []string `json:"pubkeys"`

PubNonces []string `json:"pnonces"`

Tweaks []string `json:"tweaks"`

Psigs []string `json:"psigs"`

Msg string `json:"msg"`

ValidCases []sigCombineValidCase `json:"valid_test_cases"`
}

func pSigsFromIndicies(t *testing.T, sigs []string, indices []int) []*PartialSignature {
pSigs := make([]*PartialSignature, len(indices))
for i, idx := range indices {
var pSig PartialSignature
err := pSig.Decode(bytes.NewReader(mustParseHex(sigs[idx])))
require.NoError(t, err)

pSigs[i] = &pSig
}

return pSigs
}

// TestMusig2SignCombine tests that we pass the musig2 sig combination tests.
func TestMusig2SignCombine(t *testing.T) {
t.Parallel()

testVectorPath := path.Join(
testVectorBaseDir, sigCombineTestVectorFileName,
)
testVectorBytes, err := os.ReadFile(testVectorPath)
require.NoError(t, err)

var testCases sigCombineTestVectors
require.NoError(t, json.Unmarshal(testVectorBytes, &testCases))

var msg [32]byte
copy(msg[:], mustParseHex(testCases.Msg))

for i, testCase := range testCases.ValidCases {
testCase := testCase

testName := fmt.Sprintf("valid_case_%v", i)
t.Run(testName, func(t *testing.T) {
pubKeys, err := keysFromIndices(
t, testCase.Indices, testCases.PubKeys,
)
require.NoError(t, err)

pubNonces := pubNoncesFromIndices(
t, testCase.NonceIndices, testCases.PubNonces,
)

partialSigs := pSigsFromIndicies(
t, testCases.Psigs, testCase.PSigIndices,
)

var (
combineOpts []CombineOption
keyOpts []KeyAggOption
)
if len(testCase.TweakIndices) > 0 {
tweaks := tweaksFromIndices(
t, testCase.TweakIndices,
testCases.Tweaks, testCase.IsXOnly,
)

combineOpts = append(combineOpts, WithTweakedCombine(
msg, pubKeys, tweaks, false,
))

keyOpts = append(keyOpts, WithKeyTweaks(tweaks...))
}

combinedKey, _, _, err := AggregateKeys(
pubKeys, false, keyOpts...,
)
require.NoError(t, err)

combinedNonce, err := AggregateNonces(pubNonces)
require.NoError(t, err)

finalNonceJ, _, err := computeSigningNonce(
combinedNonce, combinedKey.FinalKey, msg,
)

finalNonceJ.ToAffine()
finalNonce := btcec.NewPublicKey(
&finalNonceJ.X, &finalNonceJ.Y,
)

combinedSig := CombineSigs(
finalNonce, partialSigs, combineOpts...,
)
require.Equal(t,
strings.ToLower(testCase.Expected),
hex.EncodeToString(combinedSig.Serialize()),
)
})
}
}

0 comments on commit 5d895bb

Please sign in to comment.