mirror of
https://github.com/btcsuite/btcd.git
synced 2024-11-19 18:00:11 +01:00
btcec/schnorr/musig2: add sig combine test vectors
This commit is contained in:
parent
ca28a98425
commit
5d895bbea5
86
btcec/schnorr/musig2/data/sig_agg_vectors.json
Normal file
86
btcec/schnorr/musig2/data/sig_agg_vectors.json
Normal file
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -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
|
||||
@ -230,46 +282,14 @@ 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:],
|
||||
)
|
||||
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:
|
||||
// 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)
|
||||
nonce, nonceBlinder, err := computeSigningNonce(
|
||||
combinedNonce, combinedKey.FinalKey, msg,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Next we'll parse out our two secret nonces, which we'll be using in
|
||||
@ -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)
|
||||
|
||||
|
@ -19,6 +19,8 @@ import (
|
||||
|
||||
const (
|
||||
signVerifyTestVectorFileName = "sign_verify_vectors.json"
|
||||
|
||||
sigCombineTestVectorFileName = "sig_agg_vectors.json"
|
||||
)
|
||||
|
||||
type signVerifyValidCase struct {
|
||||
@ -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()),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user