mirror of
https://github.com/btcsuite/btcd.git
synced 2024-11-19 09:50:08 +01:00
btcec/schnorr/musig2: add explicit support for BIP 86 multi-signing
In this commit, we add a series of new functional optinos to make signing for an aggregated key where the final taproot output key was derived using BIP 86. This can be used in cases where no script path shuold be allowed, and only an n-of-n multi-sig should be used.
This commit is contained in:
parent
f7168c8663
commit
9d0d52708a
@ -90,6 +90,10 @@ type contextOptions struct {
|
||||
// also commits to the public key, which in this case is the aggregated
|
||||
// key before the tweak.
|
||||
taprootTweak []byte
|
||||
|
||||
// bip86Tweak if true, then the weak will just be
|
||||
// h_tapTweak(internalKey) as there is no true script root.
|
||||
bip86Tweak bool
|
||||
}
|
||||
|
||||
// defaultContextOptions returns the default context options.
|
||||
@ -115,6 +119,16 @@ func WithTaprootTweakCtx(scriptRoot []byte) ContextOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithBip86TweakCtx specifies that within this context, the final key should
|
||||
// use the taproot tweak as defined in BIP 341, with the BIP 86 modification:
|
||||
// outputKey = internalKey + h_tapTweak(internalKey)*G. In this case, the
|
||||
// aggreaged key before the tweak will be used as the internal key.
|
||||
func WithBip86TweakCtx() ContextOption {
|
||||
return func(o *contextOptions) {
|
||||
o.bip86Tweak = true
|
||||
}
|
||||
}
|
||||
|
||||
// NewContext creates a new signing context with the passed singing key and set
|
||||
// of public keys for each of the other signers.
|
||||
//
|
||||
@ -160,11 +174,16 @@ func NewContext(signingKey *btcec.PrivateKey,
|
||||
keyAggOpts := []KeyAggOption{
|
||||
WithKeysHash(keysHash), WithUniqueKeyIndex(uniqueKeyIndex),
|
||||
}
|
||||
if opts.taprootTweak != nil {
|
||||
switch {
|
||||
case opts.bip86Tweak:
|
||||
keyAggOpts = append(
|
||||
keyAggOpts, WithBIP86KeyTweak(),
|
||||
)
|
||||
case opts.taprootTweak != nil:
|
||||
keyAggOpts = append(
|
||||
keyAggOpts, WithTaprootKeyTweak(opts.taprootTweak),
|
||||
)
|
||||
} else if len(opts.tweaks) != 0 {
|
||||
case len(opts.tweaks) != 0:
|
||||
keyAggOpts = append(keyAggOpts, WithKeyTweaks(opts.tweaks...))
|
||||
}
|
||||
|
||||
@ -214,7 +233,7 @@ func (c *Context) SigningKeys() []*btcec.PublicKey {
|
||||
// returning the internal key. If a taproot tweak wasn't speciifed, then this
|
||||
// method will return an error.
|
||||
func (c *Context) TaprootInternalKey() (*btcec.PublicKey, error) {
|
||||
if c.opts.taprootTweak == nil {
|
||||
if c.opts.taprootTweak == nil && !c.opts.bip86Tweak {
|
||||
return nil, ErrTaprootInternalKeyUnavailable
|
||||
}
|
||||
|
||||
@ -329,11 +348,16 @@ func (s *Session) Sign(msg [32]byte,
|
||||
return nil, ErrCombinedNonceUnavailable
|
||||
}
|
||||
|
||||
if s.ctx.opts.taprootTweak != nil {
|
||||
switch {
|
||||
case s.ctx.opts.bip86Tweak:
|
||||
signOpts = append(
|
||||
signOpts, WithBip86SignTweak(),
|
||||
)
|
||||
case s.ctx.opts.taprootTweak != nil:
|
||||
signOpts = append(
|
||||
signOpts, WithTaprootSignTweak(s.ctx.opts.taprootTweak),
|
||||
)
|
||||
} else if len(s.ctx.opts.tweaks) != 0 {
|
||||
case len(s.ctx.opts.tweaks) != 0:
|
||||
signOpts = append(signOpts, WithTweaks(s.ctx.opts.tweaks...))
|
||||
}
|
||||
|
||||
@ -379,14 +403,21 @@ func (s *Session) CombineSig(sig *PartialSignature) (bool, error) {
|
||||
// final signature.
|
||||
if haveAllSigs {
|
||||
var combineOpts []CombineOption
|
||||
if s.ctx.opts.taprootTweak != nil {
|
||||
switch {
|
||||
case s.ctx.opts.bip86Tweak:
|
||||
combineOpts = append(
|
||||
combineOpts, WithBip86TweakedCombine(
|
||||
s.msg, s.ctx.keySet, s.ctx.shouldSort,
|
||||
),
|
||||
)
|
||||
case s.ctx.opts.taprootTweak != nil:
|
||||
combineOpts = append(
|
||||
combineOpts, WithTaprootTweakedCombine(
|
||||
s.msg, s.ctx.keySet, s.ctx.opts.taprootTweak,
|
||||
s.ctx.shouldSort,
|
||||
),
|
||||
)
|
||||
} else if len(s.ctx.opts.tweaks) != 0 {
|
||||
case len(s.ctx.opts.tweaks) != 0:
|
||||
combineOpts = append(
|
||||
combineOpts, WithTweakedCombine(
|
||||
s.msg, s.ctx.keySet, s.ctx.opts.tweaks,
|
||||
|
@ -176,6 +176,11 @@ type keyAggOption struct {
|
||||
// taprootTweak controls if the tweaks above should be applied in a BIP
|
||||
// 340 style.
|
||||
taprootTweak bool
|
||||
|
||||
// bip86Tweak specifies that the taproot tweak should be done in a BIP
|
||||
// 86 style, where we don't expect an actual tweak and instead just
|
||||
// commit to the public key itself.
|
||||
bip86Tweak bool
|
||||
}
|
||||
|
||||
// WithKeysHash allows key aggregation to be optimize, by allowing the caller
|
||||
@ -226,6 +231,22 @@ func WithTaprootKeyTweak(scriptRoot []byte) KeyAggOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithBIP86KeyTweak specifies that then during key aggregation, the BIP 86
|
||||
// tweak which just commits to the hash of the serialized public key should be
|
||||
// used. This option should be used when signing with a key that was derived
|
||||
// using BIP 86.
|
||||
func WithBIP86KeyTweak() KeyAggOption {
|
||||
return func(o *keyAggOption) {
|
||||
o.tweaks = []KeyTweakDesc{
|
||||
{
|
||||
IsXOnly: true,
|
||||
},
|
||||
}
|
||||
o.taprootTweak = true
|
||||
o.bip86Tweak = true
|
||||
}
|
||||
}
|
||||
|
||||
// defaultKeyAggOptions returns the set of default arguments for key
|
||||
// aggregation.
|
||||
func defaultKeyAggOptions() *keyAggOption {
|
||||
@ -370,18 +391,34 @@ func AggregateKeys(keys []*btcec.PublicKey, sort bool,
|
||||
// We'll copy over the key at this point, since this represents the
|
||||
// aggregated key before any tweaks have been applied. This'll be used
|
||||
// as the internal key for script path proofs.
|
||||
finalKeyJ.ToAffine()
|
||||
combinedKey := btcec.NewPublicKey(&finalKeyJ.X, &finalKeyJ.Y)
|
||||
|
||||
// At this point, if this is a taproot tweak, then we'll modify the
|
||||
// base tweak value to use the BIP 341 tweak value.
|
||||
if opts.taprootTweak {
|
||||
// Emulate the same behavior as txscript.ComputeTaprootOutputKey
|
||||
// which only operates on the x-only public key.
|
||||
key, _ := schnorr.ParsePubKey(schnorr.SerializePubKey(
|
||||
combinedKey,
|
||||
))
|
||||
|
||||
// We only use the actual tweak bytes if we're not committing
|
||||
// to a BIP-0086 key only spend output. Otherwise, we just
|
||||
// commit to the internal key and an empty byte slice as the
|
||||
// root hash.
|
||||
tweakBytes := []byte{}
|
||||
if !opts.bip86Tweak {
|
||||
tweakBytes = opts.tweaks[0].Tweak[:]
|
||||
}
|
||||
|
||||
// Compute the taproot key tagged hash of:
|
||||
// h_tapTweak(internalKey || scriptRoot). We only do this for
|
||||
// the first one, as you can only specify a single tweak when
|
||||
// using the taproot mode with this API.
|
||||
tapTweakHash := chainhash.TaggedHash(
|
||||
chainhash.TagTapTweak, schnorr.SerializePubKey(combinedKey),
|
||||
opts.tweaks[0].Tweak[:],
|
||||
chainhash.TagTapTweak, schnorr.SerializePubKey(key),
|
||||
tweakBytes,
|
||||
)
|
||||
opts.tweaks[0].Tweak = *tapTweakHash
|
||||
}
|
||||
|
@ -324,6 +324,8 @@ func testMultiPartySign(t *testing.T, taprootTweak []byte,
|
||||
|
||||
var ctxOpts []ContextOption
|
||||
switch {
|
||||
case len(taprootTweak) == 0:
|
||||
ctxOpts = append(ctxOpts, WithBip86TweakCtx())
|
||||
case taprootTweak != nil:
|
||||
ctxOpts = append(ctxOpts, WithTaprootTweakCtx(taprootTweak))
|
||||
case len(tweaks) != 0:
|
||||
@ -469,4 +471,10 @@ func TestMuSigMultiParty(t *testing.T) {
|
||||
|
||||
testMultiPartySign(t, testTweak[:])
|
||||
})
|
||||
|
||||
t.Run("taproot_bip_86", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testMultiPartySign(t, []byte{})
|
||||
})
|
||||
}
|
||||
|
@ -117,6 +117,11 @@ type signOptions struct {
|
||||
// the taproot tweak also commits to the public key, which in this case
|
||||
// is the aggregated key before the tweak.
|
||||
taprootTweak []byte
|
||||
|
||||
// bip86Tweak specifies that the taproot tweak should be done in a BIP
|
||||
// 86 style, where we don't expect an actual tweak and instead just
|
||||
// commit to the public key itself.
|
||||
bip86Tweak bool
|
||||
}
|
||||
|
||||
// defaultSignOptions returns the default set of signing operations.
|
||||
@ -164,6 +169,20 @@ func WithTaprootSignTweak(scriptRoot []byte) SignOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithBip86SignTweak allows a caller to specify a tweak that should be used in
|
||||
// a bip 340 manner when signing, factoring in BIP 86 as well. This differs
|
||||
// from WithTaprootSignTweak as no true script root will be committed to,
|
||||
// instead we just commit to the internal key.
|
||||
//
|
||||
// This option should be used in the taproot context to create a valid
|
||||
// signature for the keypath spend for taproot, when the output key was
|
||||
// generated using BIP 86.
|
||||
func WithBip86SignTweak() SignOption {
|
||||
return func(o *signOptions) {
|
||||
o.bip86Tweak = true
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -200,11 +219,16 @@ func Sign(secNonce [SecNonceSize]byte, privKey *btcec.PrivateKey,
|
||||
keyAggOpts := []KeyAggOption{
|
||||
WithKeysHash(keysHash), WithUniqueKeyIndex(uniqueKeyIndex),
|
||||
}
|
||||
if opts.taprootTweak != nil {
|
||||
switch {
|
||||
case opts.bip86Tweak:
|
||||
keyAggOpts = append(
|
||||
keyAggOpts, WithBIP86KeyTweak(),
|
||||
)
|
||||
case opts.taprootTweak != nil:
|
||||
keyAggOpts = append(
|
||||
keyAggOpts, WithTaprootKeyTweak(opts.taprootTweak),
|
||||
)
|
||||
} else {
|
||||
case len(opts.tweaks) != 0:
|
||||
keyAggOpts = append(keyAggOpts, WithKeyTweaks(opts.tweaks...))
|
||||
}
|
||||
|
||||
@ -394,11 +418,16 @@ func verifyPartialSig(partialSig *PartialSignature, pubNonce [PubNonceSize]byte,
|
||||
keyAggOpts := []KeyAggOption{
|
||||
WithKeysHash(keysHash), WithUniqueKeyIndex(uniqueKeyIndex),
|
||||
}
|
||||
if opts.taprootTweak != nil {
|
||||
switch {
|
||||
case opts.bip86Tweak:
|
||||
keyAggOpts = append(
|
||||
keyAggOpts, WithBIP86KeyTweak(),
|
||||
)
|
||||
case opts.taprootTweak != nil:
|
||||
keyAggOpts = append(
|
||||
keyAggOpts, WithTaprootKeyTweak(opts.taprootTweak),
|
||||
)
|
||||
} else {
|
||||
case len(opts.tweaks) != 0:
|
||||
keyAggOpts = append(keyAggOpts, WithKeyTweaks(opts.tweaks...))
|
||||
}
|
||||
|
||||
@ -569,7 +598,7 @@ func WithTweakedCombine(msg [32]byte, keys []*btcec.PublicKey,
|
||||
// output key, where the internal key is the aggregated key pre-tweak.
|
||||
//
|
||||
// This option should be used over WithTweakedCombine when attempting to
|
||||
// aggregate signatures for a top-level taproot keysepnd, where the output key
|
||||
// aggregate signatures for a top-level taproot keyspend, where the output key
|
||||
// commits to a script root.
|
||||
func WithTaprootTweakedCombine(msg [32]byte, keys []*btcec.PublicKey,
|
||||
scriptRoot []byte, sort bool) CombineOption {
|
||||
@ -585,6 +614,28 @@ func WithTaprootTweakedCombine(msg [32]byte, keys []*btcec.PublicKey,
|
||||
}
|
||||
}
|
||||
|
||||
// WithBip86TweakedCombine is similar to the WithTaprootTweakedCombine option,
|
||||
// but assumes a BIP 341 + BIP 86 context where the final tweaked key is to be
|
||||
// used as the output key, where the internal key is the aggregated key
|
||||
// pre-tweak.
|
||||
//
|
||||
// This option should be used over WithTaprootTweakedCombine when attempting to
|
||||
// aggregate signatures for a top-level taproot keyspend, where the output key
|
||||
// was generated using BIP 86.
|
||||
func WithBip86TweakedCombine(msg [32]byte, keys []*btcec.PublicKey,
|
||||
sort bool) CombineOption {
|
||||
|
||||
return func(o *combineOptions) {
|
||||
combinedKey, _, tweakAcc, _ := AggregateKeys(
|
||||
keys, sort, WithBIP86KeyTweak(),
|
||||
)
|
||||
|
||||
o.msg = msg
|
||||
o.combinedKey = combinedKey.FinalKey
|
||||
o.tweakAcc = tweakAcc
|
||||
}
|
||||
}
|
||||
|
||||
// CombineSigs combines the set of public keys given the final aggregated
|
||||
// nonce, and the series of partial signatures for each nonce.
|
||||
func CombineSigs(combinedNonce *btcec.PublicKey,
|
||||
|
Loading…
Reference in New Issue
Block a user