mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-31 16:09:02 +02:00
Merge pull request #8683 from lightningnetwork/funding-tapcript
[1/?]: multi: add ability to fund+use musig2 channels that commit to a tapscript root
This commit is contained in:
commit
bb44793cbd
@ -225,28 +225,139 @@ const (
|
||||
// A tlv type definition used to serialize an outpoint's indexStatus
|
||||
// for use in the outpoint index.
|
||||
indexStatusType tlv.Type = 0
|
||||
|
||||
// A tlv type definition used to serialize and deserialize a KeyLocator
|
||||
// from the database.
|
||||
keyLocType tlv.Type = 1
|
||||
|
||||
// A tlv type used to serialize and deserialize the
|
||||
// `InitialLocalBalance` field.
|
||||
initialLocalBalanceType tlv.Type = 2
|
||||
|
||||
// A tlv type used to serialize and deserialize the
|
||||
// `InitialRemoteBalance` field.
|
||||
initialRemoteBalanceType tlv.Type = 3
|
||||
|
||||
// A tlv type definition used to serialize and deserialize the
|
||||
// confirmed ShortChannelID for a zero-conf channel.
|
||||
realScidType tlv.Type = 4
|
||||
|
||||
// A tlv type definition used to serialize and deserialize the
|
||||
// Memo for the channel channel.
|
||||
channelMemoType tlv.Type = 5
|
||||
)
|
||||
|
||||
// chanAuxData houses the auxiliary data that is stored for each channel in a
|
||||
// TLV stream within the root bucket. This is stored as a TLV stream appended
|
||||
// to the existing hard-coded fields in the channel's root bucket.
|
||||
type chanAuxData struct {
|
||||
// revokeKeyLoc is the key locator for the revocation key.
|
||||
revokeKeyLoc tlv.RecordT[tlv.TlvType1, keyLocRecord]
|
||||
|
||||
// initialLocalBalance is the initial local balance of the channel.
|
||||
initialLocalBalance tlv.RecordT[tlv.TlvType2, uint64]
|
||||
|
||||
// initialRemoteBalance is the initial remote balance of the channel.
|
||||
initialRemoteBalance tlv.RecordT[tlv.TlvType3, uint64]
|
||||
|
||||
// realScid is the real short channel ID of the channel corresponding to
|
||||
// the on-chain outpoint.
|
||||
realScid tlv.RecordT[tlv.TlvType4, lnwire.ShortChannelID]
|
||||
|
||||
// memo is an optional text field that gives context to the user about
|
||||
// the channel.
|
||||
memo tlv.OptionalRecordT[tlv.TlvType5, []byte]
|
||||
|
||||
// tapscriptRoot is the optional Tapscript root the channel funding
|
||||
// output commits to.
|
||||
tapscriptRoot tlv.OptionalRecordT[tlv.TlvType6, [32]byte]
|
||||
}
|
||||
|
||||
// encode serializes the chanAuxData to the given io.Writer.
|
||||
func (c *chanAuxData) encode(w io.Writer) error {
|
||||
tlvRecords := []tlv.Record{
|
||||
c.revokeKeyLoc.Record(),
|
||||
c.initialLocalBalance.Record(),
|
||||
c.initialRemoteBalance.Record(),
|
||||
c.realScid.Record(),
|
||||
}
|
||||
c.memo.WhenSome(func(memo tlv.RecordT[tlv.TlvType5, []byte]) {
|
||||
tlvRecords = append(tlvRecords, memo.Record())
|
||||
})
|
||||
c.tapscriptRoot.WhenSome(
|
||||
func(root tlv.RecordT[tlv.TlvType6, [32]byte]) {
|
||||
tlvRecords = append(tlvRecords, root.Record())
|
||||
},
|
||||
)
|
||||
|
||||
// Create the tlv stream.
|
||||
tlvStream, err := tlv.NewStream(tlvRecords...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tlvStream.Encode(w)
|
||||
}
|
||||
|
||||
// decode deserializes the chanAuxData from the given io.Reader.
|
||||
func (c *chanAuxData) decode(r io.Reader) error {
|
||||
memo := c.memo.Zero()
|
||||
tapscriptRoot := c.tapscriptRoot.Zero()
|
||||
|
||||
// Create the tlv stream.
|
||||
tlvStream, err := tlv.NewStream(
|
||||
c.revokeKeyLoc.Record(),
|
||||
c.initialLocalBalance.Record(),
|
||||
c.initialRemoteBalance.Record(),
|
||||
c.realScid.Record(),
|
||||
memo.Record(),
|
||||
tapscriptRoot.Record(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tlvs, err := tlvStream.DecodeWithParsedTypes(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, ok := tlvs[memo.TlvType()]; ok {
|
||||
c.memo = tlv.SomeRecordT(memo)
|
||||
}
|
||||
if _, ok := tlvs[tapscriptRoot.TlvType()]; ok {
|
||||
c.tapscriptRoot = tlv.SomeRecordT(tapscriptRoot)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// toOpeChan converts the chanAuxData to an OpenChannel by setting the relevant
|
||||
// fields in the OpenChannel struct.
|
||||
func (c *chanAuxData) toOpenChan(o *OpenChannel) {
|
||||
o.RevocationKeyLocator = c.revokeKeyLoc.Val.KeyLocator
|
||||
o.InitialLocalBalance = lnwire.MilliSatoshi(c.initialLocalBalance.Val)
|
||||
o.InitialRemoteBalance = lnwire.MilliSatoshi(c.initialRemoteBalance.Val)
|
||||
o.confirmedScid = c.realScid.Val
|
||||
c.memo.WhenSomeV(func(memo []byte) {
|
||||
o.Memo = memo
|
||||
})
|
||||
c.tapscriptRoot.WhenSomeV(func(h [32]byte) {
|
||||
o.TapscriptRoot = fn.Some[chainhash.Hash](h)
|
||||
})
|
||||
}
|
||||
|
||||
// newChanAuxDataFromChan creates a new chanAuxData from the given channel.
|
||||
func newChanAuxDataFromChan(openChan *OpenChannel) *chanAuxData {
|
||||
c := &chanAuxData{
|
||||
revokeKeyLoc: tlv.NewRecordT[tlv.TlvType1](
|
||||
keyLocRecord{openChan.RevocationKeyLocator},
|
||||
),
|
||||
initialLocalBalance: tlv.NewPrimitiveRecord[tlv.TlvType2](
|
||||
uint64(openChan.InitialLocalBalance),
|
||||
),
|
||||
initialRemoteBalance: tlv.NewPrimitiveRecord[tlv.TlvType3](
|
||||
uint64(openChan.InitialRemoteBalance),
|
||||
),
|
||||
realScid: tlv.NewRecordT[tlv.TlvType4](
|
||||
openChan.confirmedScid,
|
||||
),
|
||||
}
|
||||
|
||||
if len(openChan.Memo) != 0 {
|
||||
c.memo = tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType5](openChan.Memo),
|
||||
)
|
||||
}
|
||||
openChan.TapscriptRoot.WhenSome(func(h chainhash.Hash) {
|
||||
c.tapscriptRoot = tlv.SomeRecordT(
|
||||
tlv.NewPrimitiveRecord[tlv.TlvType6, [32]byte](h),
|
||||
)
|
||||
})
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// indexStatus is an enum-like type that describes what state the
|
||||
// outpoint is in. Currently only two possible values.
|
||||
type indexStatus uint8
|
||||
@ -324,6 +435,11 @@ const (
|
||||
// SimpleTaprootFeatureBit indicates that the simple-taproot-chans
|
||||
// feature bit was negotiated during the lifetime of the channel.
|
||||
SimpleTaprootFeatureBit ChannelType = 1 << 10
|
||||
|
||||
// TapscriptRootBit indicates that this is a MuSig2 channel with a top
|
||||
// level tapscript commitment. This MUST be set along with the
|
||||
// SimpleTaprootFeatureBit.
|
||||
TapscriptRootBit ChannelType = 1 << 11
|
||||
)
|
||||
|
||||
// IsSingleFunder returns true if the channel type if one of the known single
|
||||
@ -394,6 +510,12 @@ func (c ChannelType) IsTaproot() bool {
|
||||
return c&SimpleTaprootFeatureBit == SimpleTaprootFeatureBit
|
||||
}
|
||||
|
||||
// HasTapscriptRoot returns true if the channel is using a top level tapscript
|
||||
// root commitment.
|
||||
func (c ChannelType) HasTapscriptRoot() bool {
|
||||
return c&TapscriptRootBit == TapscriptRootBit
|
||||
}
|
||||
|
||||
// ChannelConstraints represents a set of constraints meant to allow a node to
|
||||
// limit their exposure, enact flow control and ensure that all HTLCs are
|
||||
// economically relevant. This struct will be mirrored for both sides of the
|
||||
@ -856,6 +978,10 @@ type OpenChannel struct {
|
||||
// channel that will be useful to our future selves.
|
||||
Memo []byte
|
||||
|
||||
// TapscriptRoot is an optional tapscript root used to derive the MuSig2
|
||||
// funding output.
|
||||
TapscriptRoot fn.Option[chainhash.Hash]
|
||||
|
||||
// TODO(roasbeef): eww
|
||||
Db *ChannelStateDB
|
||||
|
||||
@ -4007,32 +4133,9 @@ func putChanInfo(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Convert balance fields into uint64.
|
||||
localBalance := uint64(channel.InitialLocalBalance)
|
||||
remoteBalance := uint64(channel.InitialRemoteBalance)
|
||||
|
||||
// Create the tlv stream.
|
||||
tlvStream, err := tlv.NewStream(
|
||||
// Write the RevocationKeyLocator as the first entry in a tlv
|
||||
// stream.
|
||||
MakeKeyLocRecord(
|
||||
keyLocType, &channel.RevocationKeyLocator,
|
||||
),
|
||||
tlv.MakePrimitiveRecord(
|
||||
initialLocalBalanceType, &localBalance,
|
||||
),
|
||||
tlv.MakePrimitiveRecord(
|
||||
initialRemoteBalanceType, &remoteBalance,
|
||||
),
|
||||
MakeScidRecord(realScidType, &channel.confirmedScid),
|
||||
tlv.MakePrimitiveRecord(channelMemoType, &channel.Memo),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tlvStream.Encode(&w); err != nil {
|
||||
return err
|
||||
auxData := newChanAuxDataFromChan(channel)
|
||||
if err := auxData.encode(&w); err != nil {
|
||||
return fmt.Errorf("unable to encode aux data: %w", err)
|
||||
}
|
||||
|
||||
if err := chanBucket.Put(chanInfoKey, w.Bytes()); err != nil {
|
||||
@ -4221,45 +4324,14 @@ func fetchChanInfo(chanBucket kvdb.RBucket, channel *OpenChannel) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Create balance fields in uint64, and Memo field as byte slice.
|
||||
var (
|
||||
localBalance uint64
|
||||
remoteBalance uint64
|
||||
memo []byte
|
||||
)
|
||||
|
||||
// Create the tlv stream.
|
||||
tlvStream, err := tlv.NewStream(
|
||||
// Write the RevocationKeyLocator as the first entry in a tlv
|
||||
// stream.
|
||||
MakeKeyLocRecord(
|
||||
keyLocType, &channel.RevocationKeyLocator,
|
||||
),
|
||||
tlv.MakePrimitiveRecord(
|
||||
initialLocalBalanceType, &localBalance,
|
||||
),
|
||||
tlv.MakePrimitiveRecord(
|
||||
initialRemoteBalanceType, &remoteBalance,
|
||||
),
|
||||
MakeScidRecord(realScidType, &channel.confirmedScid),
|
||||
tlv.MakePrimitiveRecord(channelMemoType, &memo),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
var auxData chanAuxData
|
||||
if err := auxData.decode(r); err != nil {
|
||||
return fmt.Errorf("unable to decode aux data: %w", err)
|
||||
}
|
||||
|
||||
if err := tlvStream.Decode(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Attach the balance fields.
|
||||
channel.InitialLocalBalance = lnwire.MilliSatoshi(localBalance)
|
||||
channel.InitialRemoteBalance = lnwire.MilliSatoshi(remoteBalance)
|
||||
|
||||
// Attach the memo field if non-empty.
|
||||
if len(memo) > 0 {
|
||||
channel.Memo = memo
|
||||
}
|
||||
// Assign all the relevant fields from the aux data into the actual
|
||||
// open channel.
|
||||
auxData.toOpenChan(channel)
|
||||
|
||||
channel.Packager = NewChannelPackager(channel.ShortChannelID)
|
||||
|
||||
@ -4417,6 +4489,25 @@ func deleteThawHeight(chanBucket kvdb.RwBucket) error {
|
||||
return chanBucket.Delete(frozenChanKey)
|
||||
}
|
||||
|
||||
// keyLocRecord is a wrapper struct around keychain.KeyLocator to implement the
|
||||
// tlv.RecordProducer interface.
|
||||
type keyLocRecord struct {
|
||||
keychain.KeyLocator
|
||||
}
|
||||
|
||||
// Record creates a Record out of a KeyLocator using the passed Type and the
|
||||
// EKeyLocator and DKeyLocator functions. The size will always be 8 as
|
||||
// KeyFamily is uint32 and the Index is uint32.
|
||||
//
|
||||
// NOTE: This is part of the tlv.RecordProducer interface.
|
||||
func (k *keyLocRecord) Record() tlv.Record {
|
||||
// Note that we set the type here as zero, as when used with a
|
||||
// tlv.RecordT, the type param will be used as the type.
|
||||
return tlv.MakeStaticRecord(
|
||||
0, &k.KeyLocator, 8, EKeyLocator, DKeyLocator,
|
||||
)
|
||||
}
|
||||
|
||||
// EKeyLocator is an encoder for keychain.KeyLocator.
|
||||
func EKeyLocator(w io.Writer, val interface{}, buf *[8]byte) error {
|
||||
if v, ok := val.(*keychain.KeyLocator); ok {
|
||||
@ -4445,22 +4536,6 @@ func DKeyLocator(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
|
||||
return tlv.NewTypeForDecodingErr(val, "keychain.KeyLocator", l, 8)
|
||||
}
|
||||
|
||||
// MakeKeyLocRecord creates a Record out of a KeyLocator using the passed
|
||||
// Type and the EKeyLocator and DKeyLocator functions. The size will always be
|
||||
// 8 as KeyFamily is uint32 and the Index is uint32.
|
||||
func MakeKeyLocRecord(typ tlv.Type, keyLoc *keychain.KeyLocator) tlv.Record {
|
||||
return tlv.MakeStaticRecord(typ, keyLoc, 8, EKeyLocator, DKeyLocator)
|
||||
}
|
||||
|
||||
// MakeScidRecord creates a Record out of a ShortChannelID using the passed
|
||||
// Type and the EShortChannelID and DShortChannelID functions. The size will
|
||||
// always be 8 for the ShortChannelID.
|
||||
func MakeScidRecord(typ tlv.Type, scid *lnwire.ShortChannelID) tlv.Record {
|
||||
return tlv.MakeStaticRecord(
|
||||
typ, scid, 8, lnwire.EShortChannelID, lnwire.DShortChannelID,
|
||||
)
|
||||
}
|
||||
|
||||
// ShutdownInfo contains various info about the shutdown initiation of a
|
||||
// channel.
|
||||
type ShutdownInfo struct {
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lnmock"
|
||||
@ -172,7 +173,7 @@ func fundingPointOption(chanPoint wire.OutPoint) testChannelOption {
|
||||
}
|
||||
|
||||
// channelIDOption is an option which sets the short channel ID of the channel.
|
||||
var channelIDOption = func(chanID lnwire.ShortChannelID) testChannelOption {
|
||||
func channelIDOption(chanID lnwire.ShortChannelID) testChannelOption {
|
||||
return func(params *testChannelParams) {
|
||||
params.channel.ShortChannelID = chanID
|
||||
}
|
||||
@ -312,6 +313,9 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
|
||||
uniqueOutputIndex.Add(1)
|
||||
op := wire.OutPoint{Hash: key, Index: uniqueOutputIndex.Load()}
|
||||
|
||||
var tapscriptRoot chainhash.Hash
|
||||
copy(tapscriptRoot[:], bytes.Repeat([]byte{1}, 32))
|
||||
|
||||
return &OpenChannel{
|
||||
ChanType: SingleFunderBit | FrozenBit,
|
||||
ChainHash: key,
|
||||
@ -354,6 +358,8 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
|
||||
ThawHeight: uint32(defaultPendingHeight),
|
||||
InitialLocalBalance: lnwire.MilliSatoshi(9000),
|
||||
InitialRemoteBalance: lnwire.MilliSatoshi(3000),
|
||||
Memo: []byte("test"),
|
||||
TapscriptRoot: fn.Some(tapscriptRoot),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
)
|
||||
@ -301,8 +302,11 @@ func (c *chainWatcher) Start() error {
|
||||
err error
|
||||
)
|
||||
if chanState.ChanType.IsTaproot() {
|
||||
fundingOpts := fn.MapOptionZ(
|
||||
chanState.TapscriptRoot, lnwallet.TapscriptRootToOpt,
|
||||
)
|
||||
c.fundingPkScript, _, err = input.GenTaprootFundingScript(
|
||||
localKey, remoteKey, 0,
|
||||
localKey, remoteKey, 0, fundingOpts...,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/discovery"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/labels"
|
||||
@ -2853,8 +2854,12 @@ func makeFundingScript(channel *channeldb.OpenChannel) ([]byte, error) {
|
||||
remoteKey := channel.RemoteChanCfg.MultiSigKey.PubKey
|
||||
|
||||
if channel.ChanType.IsTaproot() {
|
||||
fundingOpts := fn.MapOptionZ(
|
||||
channel.TapscriptRoot, lnwallet.TapscriptRootToOpt,
|
||||
)
|
||||
pkScript, _, err := input.GenTaprootFundingScript(
|
||||
localKey, remoteKey, int64(channel.Capacity),
|
||||
fundingOpts...,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -11,8 +11,10 @@ import (
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/lnutils"
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
)
|
||||
@ -197,27 +199,59 @@ func GenFundingPkScript(aPub, bPub []byte, amt int64) ([]byte, *wire.TxOut, erro
|
||||
return witnessScript, wire.NewTxOut(amt, pkScript), nil
|
||||
}
|
||||
|
||||
// fundingScriptOpts is a functional option that can be used to modify the way
|
||||
// the funding pkScript is created.
|
||||
type fundingScriptOpts struct {
|
||||
tapscriptRoot fn.Option[chainhash.Hash]
|
||||
}
|
||||
|
||||
// FundingScriptOpt is a functional option that can be used to modify the way
|
||||
// the funding script is created.
|
||||
type FundingScriptOpt func(*fundingScriptOpts)
|
||||
|
||||
// defaultFundingScriptOpts returns a new instance of the default
|
||||
// fundingScriptOpts.
|
||||
func defaultFundingScriptOpts() *fundingScriptOpts {
|
||||
return &fundingScriptOpts{}
|
||||
}
|
||||
|
||||
// WithTapscriptRoot is a functional option that can be used to specify the
|
||||
// tapscript root for a MuSig2 funding output.
|
||||
func WithTapscriptRoot(root chainhash.Hash) FundingScriptOpt {
|
||||
return func(o *fundingScriptOpts) {
|
||||
o.tapscriptRoot = fn.Some(root)
|
||||
}
|
||||
}
|
||||
|
||||
// GenTaprootFundingScript constructs the taproot-native funding output that
|
||||
// uses musig2 to create a single aggregated key to anchor the channel.
|
||||
// uses MuSig2 to create a single aggregated key to anchor the channel.
|
||||
func GenTaprootFundingScript(aPub, bPub *btcec.PublicKey,
|
||||
amt int64) ([]byte, *wire.TxOut, error) {
|
||||
amt int64, opts ...FundingScriptOpt) ([]byte, *wire.TxOut, error) {
|
||||
|
||||
options := defaultFundingScriptOpts()
|
||||
for _, optFunc := range opts {
|
||||
optFunc(options)
|
||||
}
|
||||
|
||||
muSig2Opt := musig2.WithBIP86KeyTweak()
|
||||
options.tapscriptRoot.WhenSome(func(scriptRoot chainhash.Hash) {
|
||||
muSig2Opt = musig2.WithTaprootKeyTweak(scriptRoot[:])
|
||||
})
|
||||
|
||||
// Similar to the existing p2wsh funding script, we'll always make sure
|
||||
// we sort the keys before any major operations. In order to ensure
|
||||
// that there's no other way this output can be spent, we'll use a BIP
|
||||
// 86 tweak here during aggregation.
|
||||
//
|
||||
// TODO(roasbeef): revisit if BIP 86 is needed here?
|
||||
// 86 tweak here during aggregation, unless the user has explicitly
|
||||
// specified a tapscript root.
|
||||
combinedKey, _, _, err := musig2.AggregateKeys(
|
||||
[]*btcec.PublicKey{aPub, bPub}, true,
|
||||
musig2.WithBIP86KeyTweak(),
|
||||
[]*btcec.PublicKey{aPub, bPub}, true, muSig2Opt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to combine keys: %w", err)
|
||||
}
|
||||
|
||||
// Now that we have the combined key, we can create a taproot pkScript
|
||||
// from this, and then make the txout given the amount.
|
||||
// from this, and then make the txOut given the amount.
|
||||
pkScript, err := PayToTaprootScript(combinedKey.FinalKey)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to make taproot "+
|
||||
@ -227,7 +261,7 @@ func GenTaprootFundingScript(aPub, bPub *btcec.PublicKey,
|
||||
txOut := wire.NewTxOut(amt, pkScript)
|
||||
|
||||
// For the "witness program" we just return the raw pkScript since the
|
||||
// output we create can _only_ be spent with a musig2 signature.
|
||||
// output we create can _only_ be spent with a MuSig2 signature.
|
||||
return pkScript, txOut, nil
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnutils"
|
||||
@ -175,8 +176,9 @@ func (m *mockChannel) RemoteUpfrontShutdownScript() lnwire.DeliveryAddress {
|
||||
}
|
||||
|
||||
func (m *mockChannel) CreateCloseProposal(fee btcutil.Amount,
|
||||
localScript, remoteScript []byte, _ ...lnwallet.ChanCloseOpt,
|
||||
) (input.Signature, *chainhash.Hash, btcutil.Amount, error) {
|
||||
localScript, remoteScript []byte,
|
||||
_ ...lnwallet.ChanCloseOpt) (input.Signature, *chainhash.Hash,
|
||||
btcutil.Amount, error) {
|
||||
|
||||
if m.chanType.IsTaproot() {
|
||||
return lnwallet.NewMusigPartialSig(
|
||||
@ -185,6 +187,7 @@ func (m *mockChannel) CreateCloseProposal(fee btcutil.Amount,
|
||||
R: new(btcec.PublicKey),
|
||||
},
|
||||
lnwire.Musig2Nonce{}, lnwire.Musig2Nonce{}, nil,
|
||||
fn.None[chainhash.Hash](),
|
||||
), nil, 0, nil
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,9 @@ import (
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
)
|
||||
@ -56,6 +58,14 @@ type ShimIntent struct {
|
||||
// generate an aggregate key to use as the taproot-native multi-sig
|
||||
// output.
|
||||
musig2 bool
|
||||
|
||||
// tapscriptRoot is the root of the tapscript tree that will be used to
|
||||
// create the funding output. This field will only be utilized if the
|
||||
// MuSig2 flag above is set to true.
|
||||
//
|
||||
// TODO(roasbeef): fold above into new chan type? sum type like thing,
|
||||
// includes the tapscript root, etc
|
||||
tapscriptRoot fn.Option[chainhash.Hash]
|
||||
}
|
||||
|
||||
// FundingOutput returns the witness script, and the output that creates the
|
||||
@ -73,12 +83,18 @@ func (s *ShimIntent) FundingOutput() ([]byte, *wire.TxOut, error) {
|
||||
// If musig2 is active, then we'll return a single aggregated key
|
||||
// rather than using the "existing" funding script.
|
||||
if s.musig2 {
|
||||
var scriptOpts []input.FundingScriptOpt
|
||||
s.tapscriptRoot.WhenSome(func(root chainhash.Hash) {
|
||||
scriptOpts = append(
|
||||
scriptOpts, input.WithTapscriptRoot(root),
|
||||
)
|
||||
})
|
||||
|
||||
// Similar to the existing p2wsh script, we'll always ensure
|
||||
// the keys are sorted before use.
|
||||
return input.GenTaprootFundingScript(
|
||||
s.localKey.PubKey,
|
||||
s.remoteKey,
|
||||
int64(totalAmt),
|
||||
s.localKey.PubKey, s.remoteKey, int64(totalAmt),
|
||||
scriptOpts...,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,11 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcwallet/wallet"
|
||||
"github.com/btcsuite/btcwallet/wtxmgr"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
)
|
||||
|
||||
@ -119,6 +121,11 @@ type Request struct {
|
||||
// output. By definition, this'll also use segwit v1 (taproot) for the
|
||||
// funding output.
|
||||
Musig2 bool
|
||||
|
||||
// TapscriptRoot is the root of the tapscript tree that will be used to
|
||||
// create the funding output. This field will only be utilized if the
|
||||
// Musig2 flag above is set to true.
|
||||
TapscriptRoot fn.Option[chainhash.Hash]
|
||||
}
|
||||
|
||||
// Intent is returned by an Assembler and represents the base functionality the
|
@ -534,6 +534,7 @@ func (p *PsbtAssembler) ProvisionChannel(req *Request) (Intent, error) {
|
||||
ShimIntent: ShimIntent{
|
||||
localFundingAmt: p.fundingAmt,
|
||||
musig2: req.Musig2,
|
||||
tapscriptRoot: req.TapscriptRoot,
|
||||
},
|
||||
State: PsbtShimRegistered,
|
||||
BasePsbt: p.basePsbt,
|
||||
|
@ -393,7 +393,6 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
|
||||
// we will call the specialized coin selection function for
|
||||
// that.
|
||||
case r.FundUpToMaxAmt != 0 && r.MinFundAmt != 0:
|
||||
|
||||
// We need to ensure that manually selected coins, which
|
||||
// are spent entirely on the channel funding, leave
|
||||
// enough funds in the wallet to cover for a reserve.
|
||||
@ -538,6 +537,7 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
|
||||
localFundingAmt: localContributionAmt,
|
||||
remoteFundingAmt: r.RemoteAmt,
|
||||
musig2: r.Musig2,
|
||||
tapscriptRoot: r.TapscriptRoot,
|
||||
},
|
||||
InputCoins: selectedCoins,
|
||||
coinLeaser: w.cfg.CoinLeaser,
|
||||
|
@ -26,6 +26,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
@ -1475,8 +1476,13 @@ func (lc *LightningChannel) createSignDesc() error {
|
||||
remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey
|
||||
|
||||
if chanState.ChanType.IsTaproot() {
|
||||
fundingOpts := fn.MapOptionZ(
|
||||
chanState.TapscriptRoot, TapscriptRootToOpt,
|
||||
)
|
||||
|
||||
fundingPkScript, _, err = input.GenTaprootFundingScript(
|
||||
localKey, remoteKey, int64(lc.channelState.Capacity),
|
||||
fundingOpts...,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -6501,11 +6507,15 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) {
|
||||
"verification nonce: %w", err)
|
||||
}
|
||||
|
||||
tapscriptTweak := fn.MapOption(TapscriptRootToTweak)(
|
||||
lc.channelState.TapscriptRoot,
|
||||
)
|
||||
|
||||
// Now that we have the local nonce, we'll re-create the musig
|
||||
// session we had for this height.
|
||||
musigSession := NewPartialMusigSession(
|
||||
*localNonce, ourKey, theirKey, lc.Signer,
|
||||
&lc.fundingOutput, LocalMusigCommit,
|
||||
&lc.fundingOutput, LocalMusigCommit, tapscriptTweak,
|
||||
)
|
||||
|
||||
var remoteSig lnwire.PartialSigWithNonce
|
||||
@ -9048,12 +9058,13 @@ func (lc *LightningChannel) InitRemoteMusigNonces(remoteNonce *musig2.Nonces,
|
||||
// TODO(roasbeef): propagate rename of signing and verification nonces
|
||||
|
||||
sessionCfg := &MusigSessionCfg{
|
||||
LocalKey: localChanCfg.MultiSigKey,
|
||||
RemoteKey: remoteChanCfg.MultiSigKey,
|
||||
LocalNonce: *localNonce,
|
||||
RemoteNonce: *remoteNonce,
|
||||
Signer: lc.Signer,
|
||||
InputTxOut: &lc.fundingOutput,
|
||||
LocalKey: localChanCfg.MultiSigKey,
|
||||
RemoteKey: remoteChanCfg.MultiSigKey,
|
||||
LocalNonce: *localNonce,
|
||||
RemoteNonce: *remoteNonce,
|
||||
Signer: lc.Signer,
|
||||
InputTxOut: &lc.fundingOutput,
|
||||
TapscriptTweak: lc.channelState.TapscriptRoot,
|
||||
}
|
||||
lc.musigSessions = NewMusigPairSession(
|
||||
sessionCfg,
|
||||
|
@ -386,6 +386,12 @@ func TestSimpleAddSettleWorkflow(t *testing.T) {
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("taproot with tapscript root", func(t *testing.T) {
|
||||
flags := channeldb.SimpleTaprootFeatureBit |
|
||||
channeldb.TapscriptRootBit
|
||||
testAddSettleWorkflow(t, true, flags, false)
|
||||
})
|
||||
|
||||
t.Run("storeFinalHtlcResolutions=true", func(t *testing.T) {
|
||||
testAddSettleWorkflow(t, false, 0, true)
|
||||
})
|
||||
@ -828,6 +834,16 @@ func TestForceClose(t *testing.T) {
|
||||
anchorAmt: anchorSize * 2,
|
||||
})
|
||||
})
|
||||
t.Run("taproot with tapscript root", func(t *testing.T) {
|
||||
testForceClose(t, &forceCloseTestCase{
|
||||
chanType: channeldb.SingleFunderTweaklessBit |
|
||||
channeldb.AnchorOutputsBit |
|
||||
channeldb.SimpleTaprootFeatureBit |
|
||||
channeldb.TapscriptRootBit,
|
||||
expectedCommitWeight: input.TaprootCommitWeight,
|
||||
anchorAmt: anchorSize * 2,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type forceCloseTestCase struct {
|
||||
|
@ -8,8 +8,10 @@ import (
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
@ -37,6 +39,12 @@ var (
|
||||
ErrSessionNotFinalized = fmt.Errorf("musig2 session not finalized")
|
||||
)
|
||||
|
||||
// tapscriptRootToSignOpt is a function that takes a tapscript root and returns
|
||||
// a MuSig2 sign opt that'll apply the tweak when signing+verifying.
|
||||
func tapscriptRootToSignOpt(root chainhash.Hash) musig2.SignOption {
|
||||
return musig2.WithTaprootSignTweak(root[:])
|
||||
}
|
||||
|
||||
// MusigPartialSig is a wrapper around the base musig2.PartialSignature type
|
||||
// that also includes information about the set of nonces used, and also the
|
||||
// signer. This allows us to implement the input.Signature interface, as that
|
||||
@ -54,25 +62,30 @@ type MusigPartialSig struct {
|
||||
|
||||
// signerKeys is the set of public keys of all signers.
|
||||
signerKeys []*btcec.PublicKey
|
||||
|
||||
// tapscriptRoot is an optional tweak, that if specified, will be used
|
||||
// instead of the normal BIP 86 tweak when validating the signature.
|
||||
tapscriptTweak fn.Option[chainhash.Hash]
|
||||
}
|
||||
|
||||
// NewMusigPartialSig creates a new musig partial signature.
|
||||
func NewMusigPartialSig(sig *musig2.PartialSignature,
|
||||
signerNonce, combinedNonce lnwire.Musig2Nonce,
|
||||
signerKeys []*btcec.PublicKey) *MusigPartialSig {
|
||||
// NewMusigPartialSig creates a new MuSig2 partial signature.
|
||||
func NewMusigPartialSig(sig *musig2.PartialSignature, signerNonce,
|
||||
combinedNonce lnwire.Musig2Nonce, signerKeys []*btcec.PublicKey,
|
||||
tapscriptTweak fn.Option[chainhash.Hash]) *MusigPartialSig {
|
||||
|
||||
return &MusigPartialSig{
|
||||
sig: sig,
|
||||
signerNonce: signerNonce,
|
||||
combinedNonce: combinedNonce,
|
||||
signerKeys: signerKeys,
|
||||
sig: sig,
|
||||
signerNonce: signerNonce,
|
||||
combinedNonce: combinedNonce,
|
||||
signerKeys: signerKeys,
|
||||
tapscriptTweak: tapscriptTweak,
|
||||
}
|
||||
}
|
||||
|
||||
// FromWireSig maps a wire partial sig to this internal type that we'll use to
|
||||
// perform signature validation.
|
||||
func (p *MusigPartialSig) FromWireSig(sig *lnwire.PartialSigWithNonce,
|
||||
) *MusigPartialSig {
|
||||
func (p *MusigPartialSig) FromWireSig(
|
||||
sig *lnwire.PartialSigWithNonce) *MusigPartialSig {
|
||||
|
||||
p.sig = &musig2.PartialSignature{
|
||||
S: &sig.Sig,
|
||||
@ -135,9 +148,15 @@ func (p *MusigPartialSig) Verify(msg []byte, pub *btcec.PublicKey) bool {
|
||||
var m [32]byte
|
||||
copy(m[:], msg)
|
||||
|
||||
// If we have a tapscript tweak, then we'll use that as a tweak
|
||||
// otherwise, we'll fall back to the normal BIP 86 sign tweak.
|
||||
signOpts := fn.MapOption(tapscriptRootToSignOpt)(
|
||||
p.tapscriptTweak,
|
||||
).UnwrapOr(musig2.WithBip86SignTweak())
|
||||
|
||||
return p.sig.Verify(
|
||||
p.signerNonce, p.combinedNonce, p.signerKeys, pub, m,
|
||||
musig2.WithSortedKeys(), musig2.WithBip86SignTweak(),
|
||||
musig2.WithSortedKeys(), signOpts,
|
||||
)
|
||||
}
|
||||
|
||||
@ -160,6 +179,14 @@ func (n *MusigNoncePair) String() string {
|
||||
n.SigningNonce.PubNonce[:])
|
||||
}
|
||||
|
||||
// TapscriptRootToTweak is a function that takes a MuSig2 taproot tweak and
|
||||
// returns the root hash of the tapscript tree.
|
||||
func muSig2TweakToRoot(tweak input.MuSig2Tweaks) chainhash.Hash {
|
||||
var root chainhash.Hash
|
||||
copy(root[:], tweak.TaprootTweak)
|
||||
return root
|
||||
}
|
||||
|
||||
// MusigSession abstracts over the details of a logical musig session. A single
|
||||
// session is used for each commitment transactions. The sessions use a JIT
|
||||
// nonce style, wherein part of the session can be created using only the
|
||||
@ -197,15 +224,20 @@ type MusigSession struct {
|
||||
// commitType tracks if this is the session for the local or remote
|
||||
// commitment.
|
||||
commitType MusigCommitType
|
||||
|
||||
// tapscriptTweak is an optional tweak, that if specified, will be used
|
||||
// instead of the normal BIP 86 tweak when creating the MuSig2
|
||||
// aggregate key and session.
|
||||
tapscriptTweak fn.Option[input.MuSig2Tweaks]
|
||||
}
|
||||
|
||||
// NewPartialMusigSession creates a new musig2 session given only the
|
||||
// verification nonce (local nonce), and the other information that has already
|
||||
// been bound to the session.
|
||||
func NewPartialMusigSession(verificationNonce musig2.Nonces,
|
||||
localKey, remoteKey keychain.KeyDescriptor,
|
||||
signer input.MuSig2Signer, inputTxOut *wire.TxOut,
|
||||
commitType MusigCommitType) *MusigSession {
|
||||
localKey, remoteKey keychain.KeyDescriptor, signer input.MuSig2Signer,
|
||||
inputTxOut *wire.TxOut, commitType MusigCommitType,
|
||||
tapscriptTweak fn.Option[input.MuSig2Tweaks]) *MusigSession {
|
||||
|
||||
signerKeys := []*btcec.PublicKey{localKey.PubKey, remoteKey.PubKey}
|
||||
|
||||
@ -214,13 +246,14 @@ func NewPartialMusigSession(verificationNonce musig2.Nonces,
|
||||
}
|
||||
|
||||
return &MusigSession{
|
||||
nonces: nonces,
|
||||
remoteKey: remoteKey,
|
||||
localKey: localKey,
|
||||
inputTxOut: inputTxOut,
|
||||
signerKeys: signerKeys,
|
||||
signer: signer,
|
||||
commitType: commitType,
|
||||
nonces: nonces,
|
||||
remoteKey: remoteKey,
|
||||
localKey: localKey,
|
||||
inputTxOut: inputTxOut,
|
||||
signerKeys: signerKeys,
|
||||
signer: signer,
|
||||
commitType: commitType,
|
||||
tapscriptTweak: tapscriptTweak,
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,9 +287,9 @@ func (m *MusigSession) FinalizeSession(signingNonce musig2.Nonces) error {
|
||||
remoteNonce = m.nonces.SigningNonce
|
||||
}
|
||||
|
||||
tweakDesc := input.MuSig2Tweaks{
|
||||
tweakDesc := m.tapscriptTweak.UnwrapOr(input.MuSig2Tweaks{
|
||||
TaprootBIP0086Tweak: true,
|
||||
}
|
||||
})
|
||||
m.session, err = m.signer.MuSig2CreateSession(
|
||||
input.MuSig2Version100RC2, m.localKey.KeyLocator, m.signerKeys,
|
||||
&tweakDesc, [][musig2.PubNonceSize]byte{remoteNonce.PubNonce},
|
||||
@ -351,8 +384,11 @@ func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tapscriptRoot := fn.MapOption(muSig2TweakToRoot)(m.tapscriptTweak)
|
||||
|
||||
return NewMusigPartialSig(
|
||||
sig, m.session.PublicNonce, m.combinedNonce, m.signerKeys,
|
||||
tapscriptRoot,
|
||||
), nil
|
||||
}
|
||||
|
||||
@ -364,7 +400,7 @@ func (m *MusigSession) Refresh(verificationNonce *musig2.Nonces,
|
||||
|
||||
return NewPartialMusigSession(
|
||||
*verificationNonce, m.localKey, m.remoteKey, m.signer,
|
||||
m.inputTxOut, m.commitType,
|
||||
m.inputTxOut, m.commitType, m.tapscriptTweak,
|
||||
), nil
|
||||
}
|
||||
|
||||
@ -451,9 +487,11 @@ func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx,
|
||||
// When we verify a commitment signature, we always assume that we're
|
||||
// verifying a signature on our local commitment. Therefore, we'll use:
|
||||
// their remote nonce, and also public key.
|
||||
tapscriptRoot := fn.MapOption(muSig2TweakToRoot)(m.tapscriptTweak)
|
||||
partialSig := NewMusigPartialSig(
|
||||
&musig2.PartialSignature{S: &sig.Sig},
|
||||
m.nonces.SigningNonce.PubNonce, m.combinedNonce, m.signerKeys,
|
||||
tapscriptRoot,
|
||||
)
|
||||
|
||||
// With the partial sig loaded with the proper context, we'll now
|
||||
@ -537,6 +575,10 @@ type MusigSessionCfg struct {
|
||||
// InputTxOut is the output that we're signing for. This will be the
|
||||
// funding input.
|
||||
InputTxOut *wire.TxOut
|
||||
|
||||
// TapscriptRoot is an optional tweak that can be used to modify the
|
||||
// MuSig2 public key used in the session.
|
||||
TapscriptTweak fn.Option[chainhash.Hash]
|
||||
}
|
||||
|
||||
// MusigPairSession houses the two musig2 sessions needed to do funding and
|
||||
@ -561,13 +603,14 @@ func NewMusigPairSession(cfg *MusigSessionCfg) *MusigPairSession {
|
||||
//
|
||||
// Both sessions will be created using only the verification nonce for
|
||||
// the local+remote party.
|
||||
tapscriptTweak := fn.MapOption(TapscriptRootToTweak)(cfg.TapscriptTweak)
|
||||
localSession := NewPartialMusigSession(
|
||||
cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey,
|
||||
cfg.Signer, cfg.InputTxOut, LocalMusigCommit,
|
||||
cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer,
|
||||
cfg.InputTxOut, LocalMusigCommit, tapscriptTweak,
|
||||
)
|
||||
remoteSession := NewPartialMusigSession(
|
||||
cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey,
|
||||
cfg.Signer, cfg.InputTxOut, RemoteMusigCommit,
|
||||
cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer,
|
||||
cfg.InputTxOut, RemoteMusigCommit, tapscriptTweak,
|
||||
)
|
||||
|
||||
return &MusigPairSession{
|
||||
|
@ -412,6 +412,10 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
chanType |= channeldb.ScidAliasFeatureBit
|
||||
}
|
||||
|
||||
if req.TapscriptRoot.IsSome() {
|
||||
chanType |= channeldb.TapscriptRootBit
|
||||
}
|
||||
|
||||
return &ChannelReservation{
|
||||
ourContribution: &ChannelContribution{
|
||||
FundingAmount: ourBalance.ToSatoshis(),
|
||||
@ -445,6 +449,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
InitialLocalBalance: ourBalance,
|
||||
InitialRemoteBalance: theirBalance,
|
||||
Memo: req.Memo,
|
||||
TapscriptRoot: req.TapscriptRoot,
|
||||
},
|
||||
pushMSat: req.PushMSat,
|
||||
pendingChanID: req.PendingChanID,
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
@ -343,6 +344,21 @@ func CreateTestChannels(t *testing.T, chanType channeldb.ChannelType,
|
||||
Packager: channeldb.NewChannelPackager(shortChanID),
|
||||
}
|
||||
|
||||
// If the channel type has a tapscript root, then we'll also specify
|
||||
// one here to apply to both the channels.
|
||||
if chanType.HasTapscriptRoot() {
|
||||
var tapscriptRoot chainhash.Hash
|
||||
_, err := io.ReadFull(rand.Reader, tapscriptRoot[:])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
someRoot := fn.Some(tapscriptRoot)
|
||||
|
||||
aliceChannelState.TapscriptRoot = someRoot
|
||||
bobChannelState.TapscriptRoot = someRoot
|
||||
}
|
||||
|
||||
aliceSigner := input.NewMockSigner(aliceKeys, nil)
|
||||
bobSigner := input.NewMockSigner(bobKeys, nil)
|
||||
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"github.com/btcsuite/btcwallet/wallet"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
@ -200,6 +201,11 @@ type InitFundingReserveMsg struct {
|
||||
// channel that will be useful to our future selves.
|
||||
Memo []byte
|
||||
|
||||
// TapscriptRoot is the root of the tapscript tree that will be used to
|
||||
// create the funding output. This is an optional field that should
|
||||
// only be set for taproot channels.
|
||||
TapscriptRoot fn.Option[chainhash.Hash]
|
||||
|
||||
// err is a channel in which all errors will be sent across. Will be
|
||||
// nil if this initial set is successful.
|
||||
//
|
||||
@ -2086,8 +2092,14 @@ func (l *LightningWallet) verifyCommitSig(res *ChannelReservation,
|
||||
// already. If we're the responder in the funding flow, we may
|
||||
// not have generated it already.
|
||||
if res.musigSessions == nil {
|
||||
fundingOpts := fn.MapOptionZ(
|
||||
res.partialState.TapscriptRoot,
|
||||
TapscriptRootToOpt,
|
||||
)
|
||||
|
||||
_, fundingOutput, err := input.GenTaprootFundingScript(
|
||||
localKey, remoteKey, channelValue,
|
||||
fundingOpts...,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -2327,11 +2339,18 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
|
||||
fundingTxOut *wire.TxOut
|
||||
)
|
||||
if chanType.IsTaproot() {
|
||||
fundingWitnessScript, fundingTxOut, err = input.GenTaprootFundingScript( //nolint:lll
|
||||
fundingOpts := fn.MapOptionZ(
|
||||
pendingReservation.partialState.TapscriptRoot,
|
||||
TapscriptRootToOpt,
|
||||
)
|
||||
//nolint:lll
|
||||
fundingWitnessScript, fundingTxOut, err = input.GenTaprootFundingScript(
|
||||
ourKey.PubKey, theirKey.PubKey, channelValue,
|
||||
fundingOpts...,
|
||||
)
|
||||
} else {
|
||||
fundingWitnessScript, fundingTxOut, err = input.GenFundingPkScript( //nolint:lll
|
||||
//nolint:lll
|
||||
fundingWitnessScript, fundingTxOut, err = input.GenFundingPkScript(
|
||||
ourKey.PubKey.SerializeCompressed(),
|
||||
theirKey.PubKey.SerializeCompressed(), channelValue,
|
||||
)
|
||||
@ -2445,6 +2464,20 @@ func initStateHints(commit1, commit2 *wire.MsgTx,
|
||||
return nil
|
||||
}
|
||||
|
||||
// TapscriptRootToOpt is a helper function that converts a tapscript root into
|
||||
// the functional option we can use to pass into GenTaprootFundingScript.
|
||||
func TapscriptRootToOpt(root chainhash.Hash) []input.FundingScriptOpt {
|
||||
return []input.FundingScriptOpt{input.WithTapscriptRoot(root)}
|
||||
}
|
||||
|
||||
// TapscriptRootToTweak is a helper function that converts a tapscript root
|
||||
// into a tweak that can be used with the MuSig2 API.
|
||||
func TapscriptRootToTweak(root chainhash.Hash) input.MuSig2Tweaks {
|
||||
return input.MuSig2Tweaks{
|
||||
TaprootTweak: root[:],
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateChannel will attempt to fully validate a newly mined channel, given
|
||||
// its funding transaction and existing channel state. If this method returns
|
||||
// an error, then the mined channel is invalid, and shouldn't be used.
|
||||
@ -2466,8 +2499,13 @@ func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel,
|
||||
// funding transaction, and also commitment validity.
|
||||
var fundingScript []byte
|
||||
if channelState.ChanType.IsTaproot() {
|
||||
fundingOpts := fn.MapOptionZ(
|
||||
channelState.TapscriptRoot, TapscriptRootToOpt,
|
||||
)
|
||||
|
||||
fundingScript, _, err = input.GenTaprootFundingScript(
|
||||
localKey, remoteKey, int64(channel.Capacity),
|
||||
fundingOpts...,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
|
||||
@ -43,10 +44,15 @@ func (m *MusigChanCloser) ProposalClosingOpts() (
|
||||
}
|
||||
|
||||
localKey, remoteKey := m.channel.MultiSigKeys()
|
||||
|
||||
tapscriptTweak := fn.MapOption(lnwallet.TapscriptRootToTweak)(
|
||||
m.channel.State().TapscriptRoot,
|
||||
)
|
||||
|
||||
m.musigSession = lnwallet.NewPartialMusigSession(
|
||||
*m.remoteNonce, localKey, remoteKey,
|
||||
m.channel.Signer, m.channel.FundingTxOut(),
|
||||
lnwallet.RemoteMusigCommit,
|
||||
lnwallet.RemoteMusigCommit, tapscriptTweak,
|
||||
)
|
||||
|
||||
err := m.musigSession.FinalizeSession(*m.localNonce)
|
||||
|
@ -1557,6 +1557,8 @@ func makeFundingScript(bitcoinKey1, bitcoinKey2 []byte,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(roasbeef): add tapscript root to gossip v1.5
|
||||
|
||||
return fundingScript, nil
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user