Skip to content

Commit 533e626

Browse files
committed
utils: accept raw keys in MuSig2Sign
1 parent ab280a0 commit 533e626

3 files changed

Lines changed: 155 additions & 31 deletions

File tree

staticaddr/script/script_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"fmt"
77
"testing"
88

9-
"github.com/btcsuite/btcd/btcec/v2"
109
"github.com/btcsuite/btcd/txscript"
1110
"github.com/btcsuite/btcd/wire"
1211
"github.com/lightninglabs/loop/test"
@@ -28,9 +27,12 @@ func TestStaticAddressScript(t *testing.T) {
2827
clientPrivKey, clientPubKey := test.CreateKey(1)
2928
serverPrivKey, serverPubKey := test.CreateKey(2)
3029

30+
var clientKey, serverKey [32]byte
31+
copy(clientKey[:], clientPrivKey.Serialize())
32+
copy(serverKey[:], serverPrivKey.Serialize())
33+
3134
// Keys used for the Musig2 session.
32-
privKeys := []*btcec.PrivateKey{clientPrivKey, serverPrivKey}
33-
pubKeys := []*btcec.PublicKey{clientPubKey, serverPubKey}
35+
privKeys := [][32]byte{clientKey, serverKey}
3436

3537
// Create a new static address.
3638
staticAddress, err := NewStaticAddress(
@@ -91,7 +93,7 @@ func TestStaticAddressScript(t *testing.T) {
9193
}
9294

9395
sig, err := utils.MuSig2Sign(
94-
version, privKeys, pubKeys, tweak, msg,
96+
version, privKeys, tweak, msg,
9597
)
9698
require.NoError(t, err)
9799

utils/musig.go

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,37 @@ import (
44
"fmt"
55

66
"github.com/btcsuite/btcd/btcec/v2"
7+
"github.com/btcsuite/btcd/btcec/v2/schnorr"
78
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
89
"github.com/lightningnetwork/lnd/input"
910
)
1011

1112
// MuSig2Sign will create a MuSig2 signature for the passed message using the
12-
// passed private keys. It expects at least two signing keys, and the number of
13-
// private keys and public keys must match.
14-
func MuSig2Sign(version input.MuSig2Version, privKeys []*btcec.PrivateKey,
15-
pubKeys []*btcec.PublicKey, tweaks *input.MuSig2Tweaks,
16-
msg [32]byte) ([]byte, error) {
17-
18-
if len(privKeys) != len(pubKeys) {
19-
return nil, fmt.Errorf("number of private keys (%d) must "+
20-
"match number of public keys (%d)",
21-
len(privKeys), len(pubKeys))
13+
// passed raw private keys. It expects at least two signing keys.
14+
func MuSig2Sign(version input.MuSig2Version, keys [][32]byte,
15+
tweaks *input.MuSig2Tweaks, msg [32]byte) ([]byte, error) {
16+
17+
privKeys := make([]*btcec.PrivateKey, len(keys))
18+
pubKeys := make([]*btcec.PublicKey, len(keys))
19+
20+
// First parse the raw private keys and also create the corresponding
21+
// public keys.
22+
for i, key := range keys {
23+
privKeys[i], pubKeys[i] = btcec.PrivKeyFromBytes(key[:])
24+
25+
// MuSig2 v0.4 expects x-only public keys.
26+
if version == input.MuSig2Version040 {
27+
pubKey := pubKeys[i].SerializeCompressed()
28+
xOnlyPubKey, err := schnorr.ParsePubKey(pubKey[1:])
29+
if err != nil {
30+
return nil, fmt.Errorf("error parsing x-only "+
31+
"pubkey: %v", err)
32+
}
33+
34+
pubKeys[i] = xOnlyPubKey
35+
}
2236
}
37+
2338
if len(privKeys) < 2 {
2439
return nil, fmt.Errorf("need at least two signing keys")
2540
}

utils/musig_test.go

Lines changed: 124 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,145 @@ import (
44
"testing"
55

66
"github.com/btcsuite/btcd/btcec/v2"
7+
"github.com/btcsuite/btcd/btcec/v2/schnorr"
78
"github.com/lightninglabs/loop/test"
89
"github.com/lightningnetwork/lnd/input"
910
"github.com/stretchr/testify/require"
1011
)
1112

13+
// rawKeys returns serialized private keys for the given test seeds.
14+
func rawKeys(seeds ...int32) [][32]byte {
15+
keys := make([][32]byte, len(seeds))
16+
for i, seed := range seeds {
17+
privKey, _ := test.CreateKey(seed)
18+
copy(keys[i][:], privKey.Serialize())
19+
}
20+
21+
return keys
22+
}
23+
24+
// signerPubKeys returns signer public keys derived from the given seeds in the
25+
// format expected by the given MuSig2 version.
26+
func signerPubKeys(t *testing.T, version input.MuSig2Version,
27+
seeds ...int32) []*btcec.PublicKey {
28+
29+
t.Helper()
30+
31+
pubKeys := make([]*btcec.PublicKey, len(seeds))
32+
for i, seed := range seeds {
33+
_, pubKey := test.CreateKey(seed)
34+
35+
if version == input.MuSig2Version040 {
36+
var err error
37+
pubKey, err = schnorr.ParsePubKey(
38+
schnorr.SerializePubKey(pubKey),
39+
)
40+
require.NoError(t, err)
41+
}
42+
43+
pubKeys[i] = pubKey
44+
}
45+
46+
return pubKeys
47+
}
48+
49+
// hasOddY returns true if the compressed serialization of the public key uses
50+
// the odd-Y prefix.
51+
func hasOddY(pubKey *btcec.PublicKey) bool {
52+
return pubKey.SerializeCompressed()[0] == 0x03
53+
}
54+
1255
// TestMuSig2SignRejectsSingleSigner ensures the helper fails fast with a clear
1356
// error instead of entering an invalid one-party MuSig2 flow.
1457
func TestMuSig2SignRejectsSingleSigner(t *testing.T) {
15-
privKey, pubKey := test.CreateKey(1)
16-
1758
_, err := MuSig2Sign(
1859
input.MuSig2Version100RC2,
19-
[]*btcec.PrivateKey{privKey},
20-
[]*btcec.PublicKey{pubKey},
60+
rawKeys(1),
2161
&input.MuSig2Tweaks{},
2262
[32]byte{},
2363
)
2464
require.ErrorContains(t, err, "need at least two signing keys")
2565
}
2666

27-
// TestMuSig2SignRejectsMismatchedKeyCounts ensures we fail before creating any
28-
// MuSig2 sessions when the signer sets are inconsistent.
29-
func TestMuSig2SignRejectsMismatchedKeyCounts(t *testing.T) {
30-
privKey, pubKey1 := test.CreateKey(1)
31-
_, pubKey2 := test.CreateKey(2)
67+
// TestMuSig2SignSupportsVersions verifies the helper works with the supported
68+
// MuSig2 versions used in Loop.
69+
func TestMuSig2SignSupportsVersions(t *testing.T) {
70+
t.Parallel()
3271

33-
_, err := MuSig2Sign(
34-
input.MuSig2Version100RC2,
35-
[]*btcec.PrivateKey{privKey},
36-
[]*btcec.PublicKey{pubKey1, pubKey2},
37-
&input.MuSig2Tweaks{},
38-
[32]byte{},
39-
)
40-
require.ErrorContains(t, err, "must match number of public keys")
72+
tweaks := &input.MuSig2Tweaks{}
73+
msg := [32]byte{1}
74+
75+
testCases := []struct {
76+
name string
77+
version input.MuSig2Version
78+
seeds []int32
79+
oddYSigner int32
80+
}{
81+
{
82+
name: testVersionName(input.MuSig2Version040),
83+
version: input.MuSig2Version040,
84+
seeds: []int32{1, 2},
85+
},
86+
{
87+
name: testVersionName(input.MuSig2Version100RC2),
88+
version: input.MuSig2Version100RC2,
89+
seeds: []int32{1, 2},
90+
},
91+
{
92+
name: testVersionName(input.MuSig2Version040) +
93+
" odd Y",
94+
version: input.MuSig2Version040,
95+
seeds: []int32{5, 1},
96+
oddYSigner: 5,
97+
},
98+
}
99+
100+
for _, testCase := range testCases {
101+
t.Run(testCase.name, func(t *testing.T) {
102+
t.Parallel()
103+
104+
if testCase.oddYSigner != 0 {
105+
_, oddYKey := test.CreateKey(
106+
testCase.oddYSigner,
107+
)
108+
require.True(t, hasOddY(oddYKey))
109+
}
110+
111+
keys := rawKeys(testCase.seeds...)
112+
pubKeys := signerPubKeys(
113+
t, testCase.version, testCase.seeds...,
114+
)
115+
116+
sigBytes, err := MuSig2Sign(
117+
testCase.version, keys, tweaks, msg,
118+
)
119+
require.NoError(t, err)
120+
require.Len(t, sigBytes, 64)
121+
122+
sig, err := schnorr.ParseSignature(sigBytes)
123+
require.NoError(t, err)
124+
125+
combinedKey, err := input.MuSig2CombineKeys(
126+
testCase.version, pubKeys, true, tweaks,
127+
)
128+
require.NoError(t, err)
129+
require.True(
130+
t, sig.Verify(msg[:], combinedKey.FinalKey),
131+
)
132+
})
133+
}
134+
}
135+
136+
// testVersionName returns a stable subtest name for a MuSig2 version.
137+
func testVersionName(version input.MuSig2Version) string {
138+
switch version {
139+
case input.MuSig2Version040:
140+
return "MuSig2 0.4"
141+
142+
case input.MuSig2Version100RC2:
143+
return "MuSig2 1.0RC2"
144+
145+
default:
146+
return "unknown"
147+
}
41148
}

0 commit comments

Comments
 (0)