Skip to content

Commit bb44793

Browse files
authored
Merge pull request #8683 from lightningnetwork/funding-tapcript
[1/?]: multi: add ability to fund+use musig2 channels that commit to a tapscript root
2 parents 7fb2333 + 26ce8ee commit bb44793

File tree

18 files changed

+438
-150
lines changed

18 files changed

+438
-150
lines changed

channeldb/channel.go

Lines changed: 170 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -225,27 +225,138 @@ const (
225225
// A tlv type definition used to serialize an outpoint's indexStatus
226226
// for use in the outpoint index.
227227
indexStatusType tlv.Type = 0
228+
)
228229

229-
// A tlv type definition used to serialize and deserialize a KeyLocator
230-
// from the database.
231-
keyLocType tlv.Type = 1
230+
// chanAuxData houses the auxiliary data that is stored for each channel in a
231+
// TLV stream within the root bucket. This is stored as a TLV stream appended
232+
// to the existing hard-coded fields in the channel's root bucket.
233+
type chanAuxData struct {
234+
// revokeKeyLoc is the key locator for the revocation key.
235+
revokeKeyLoc tlv.RecordT[tlv.TlvType1, keyLocRecord]
232236

233-
// A tlv type used to serialize and deserialize the
234-
// `InitialLocalBalance` field.
235-
initialLocalBalanceType tlv.Type = 2
237+
// initialLocalBalance is the initial local balance of the channel.
238+
initialLocalBalance tlv.RecordT[tlv.TlvType2, uint64]
236239

237-
// A tlv type used to serialize and deserialize the
238-
// `InitialRemoteBalance` field.
239-
initialRemoteBalanceType tlv.Type = 3
240+
// initialRemoteBalance is the initial remote balance of the channel.
241+
initialRemoteBalance tlv.RecordT[tlv.TlvType3, uint64]
240242

241-
// A tlv type definition used to serialize and deserialize the
242-
// confirmed ShortChannelID for a zero-conf channel.
243-
realScidType tlv.Type = 4
243+
// realScid is the real short channel ID of the channel corresponding to
244+
// the on-chain outpoint.
245+
realScid tlv.RecordT[tlv.TlvType4, lnwire.ShortChannelID]
244246

245-
// A tlv type definition used to serialize and deserialize the
246-
// Memo for the channel channel.
247-
channelMemoType tlv.Type = 5
248-
)
247+
// memo is an optional text field that gives context to the user about
248+
// the channel.
249+
memo tlv.OptionalRecordT[tlv.TlvType5, []byte]
250+
251+
// tapscriptRoot is the optional Tapscript root the channel funding
252+
// output commits to.
253+
tapscriptRoot tlv.OptionalRecordT[tlv.TlvType6, [32]byte]
254+
}
255+
256+
// encode serializes the chanAuxData to the given io.Writer.
257+
func (c *chanAuxData) encode(w io.Writer) error {
258+
tlvRecords := []tlv.Record{
259+
c.revokeKeyLoc.Record(),
260+
c.initialLocalBalance.Record(),
261+
c.initialRemoteBalance.Record(),
262+
c.realScid.Record(),
263+
}
264+
c.memo.WhenSome(func(memo tlv.RecordT[tlv.TlvType5, []byte]) {
265+
tlvRecords = append(tlvRecords, memo.Record())
266+
})
267+
c.tapscriptRoot.WhenSome(
268+
func(root tlv.RecordT[tlv.TlvType6, [32]byte]) {
269+
tlvRecords = append(tlvRecords, root.Record())
270+
},
271+
)
272+
273+
// Create the tlv stream.
274+
tlvStream, err := tlv.NewStream(tlvRecords...)
275+
if err != nil {
276+
return err
277+
}
278+
279+
return tlvStream.Encode(w)
280+
}
281+
282+
// decode deserializes the chanAuxData from the given io.Reader.
283+
func (c *chanAuxData) decode(r io.Reader) error {
284+
memo := c.memo.Zero()
285+
tapscriptRoot := c.tapscriptRoot.Zero()
286+
287+
// Create the tlv stream.
288+
tlvStream, err := tlv.NewStream(
289+
c.revokeKeyLoc.Record(),
290+
c.initialLocalBalance.Record(),
291+
c.initialRemoteBalance.Record(),
292+
c.realScid.Record(),
293+
memo.Record(),
294+
tapscriptRoot.Record(),
295+
)
296+
if err != nil {
297+
return err
298+
}
299+
300+
tlvs, err := tlvStream.DecodeWithParsedTypes(r)
301+
if err != nil {
302+
return err
303+
}
304+
305+
if _, ok := tlvs[memo.TlvType()]; ok {
306+
c.memo = tlv.SomeRecordT(memo)
307+
}
308+
if _, ok := tlvs[tapscriptRoot.TlvType()]; ok {
309+
c.tapscriptRoot = tlv.SomeRecordT(tapscriptRoot)
310+
}
311+
312+
return nil
313+
}
314+
315+
// toOpeChan converts the chanAuxData to an OpenChannel by setting the relevant
316+
// fields in the OpenChannel struct.
317+
func (c *chanAuxData) toOpenChan(o *OpenChannel) {
318+
o.RevocationKeyLocator = c.revokeKeyLoc.Val.KeyLocator
319+
o.InitialLocalBalance = lnwire.MilliSatoshi(c.initialLocalBalance.Val)
320+
o.InitialRemoteBalance = lnwire.MilliSatoshi(c.initialRemoteBalance.Val)
321+
o.confirmedScid = c.realScid.Val
322+
c.memo.WhenSomeV(func(memo []byte) {
323+
o.Memo = memo
324+
})
325+
c.tapscriptRoot.WhenSomeV(func(h [32]byte) {
326+
o.TapscriptRoot = fn.Some[chainhash.Hash](h)
327+
})
328+
}
329+
330+
// newChanAuxDataFromChan creates a new chanAuxData from the given channel.
331+
func newChanAuxDataFromChan(openChan *OpenChannel) *chanAuxData {
332+
c := &chanAuxData{
333+
revokeKeyLoc: tlv.NewRecordT[tlv.TlvType1](
334+
keyLocRecord{openChan.RevocationKeyLocator},
335+
),
336+
initialLocalBalance: tlv.NewPrimitiveRecord[tlv.TlvType2](
337+
uint64(openChan.InitialLocalBalance),
338+
),
339+
initialRemoteBalance: tlv.NewPrimitiveRecord[tlv.TlvType3](
340+
uint64(openChan.InitialRemoteBalance),
341+
),
342+
realScid: tlv.NewRecordT[tlv.TlvType4](
343+
openChan.confirmedScid,
344+
),
345+
}
346+
347+
if len(openChan.Memo) != 0 {
348+
c.memo = tlv.SomeRecordT(
349+
tlv.NewPrimitiveRecord[tlv.TlvType5](openChan.Memo),
350+
)
351+
}
352+
openChan.TapscriptRoot.WhenSome(func(h chainhash.Hash) {
353+
c.tapscriptRoot = tlv.SomeRecordT(
354+
tlv.NewPrimitiveRecord[tlv.TlvType6, [32]byte](h),
355+
)
356+
})
357+
358+
return c
359+
}
249360

250361
// indexStatus is an enum-like type that describes what state the
251362
// outpoint is in. Currently only two possible values.
@@ -324,6 +435,11 @@ const (
324435
// SimpleTaprootFeatureBit indicates that the simple-taproot-chans
325436
// feature bit was negotiated during the lifetime of the channel.
326437
SimpleTaprootFeatureBit ChannelType = 1 << 10
438+
439+
// TapscriptRootBit indicates that this is a MuSig2 channel with a top
440+
// level tapscript commitment. This MUST be set along with the
441+
// SimpleTaprootFeatureBit.
442+
TapscriptRootBit ChannelType = 1 << 11
327443
)
328444

329445
// IsSingleFunder returns true if the channel type if one of the known single
@@ -394,6 +510,12 @@ func (c ChannelType) IsTaproot() bool {
394510
return c&SimpleTaprootFeatureBit == SimpleTaprootFeatureBit
395511
}
396512

513+
// HasTapscriptRoot returns true if the channel is using a top level tapscript
514+
// root commitment.
515+
func (c ChannelType) HasTapscriptRoot() bool {
516+
return c&TapscriptRootBit == TapscriptRootBit
517+
}
518+
397519
// ChannelConstraints represents a set of constraints meant to allow a node to
398520
// limit their exposure, enact flow control and ensure that all HTLCs are
399521
// economically relevant. This struct will be mirrored for both sides of the
@@ -856,6 +978,10 @@ type OpenChannel struct {
856978
// channel that will be useful to our future selves.
857979
Memo []byte
858980

981+
// TapscriptRoot is an optional tapscript root used to derive the MuSig2
982+
// funding output.
983+
TapscriptRoot fn.Option[chainhash.Hash]
984+
859985
// TODO(roasbeef): eww
860986
Db *ChannelStateDB
861987

@@ -4007,32 +4133,9 @@ func putChanInfo(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
40074133
return err
40084134
}
40094135

4010-
// Convert balance fields into uint64.
4011-
localBalance := uint64(channel.InitialLocalBalance)
4012-
remoteBalance := uint64(channel.InitialRemoteBalance)
4013-
4014-
// Create the tlv stream.
4015-
tlvStream, err := tlv.NewStream(
4016-
// Write the RevocationKeyLocator as the first entry in a tlv
4017-
// stream.
4018-
MakeKeyLocRecord(
4019-
keyLocType, &channel.RevocationKeyLocator,
4020-
),
4021-
tlv.MakePrimitiveRecord(
4022-
initialLocalBalanceType, &localBalance,
4023-
),
4024-
tlv.MakePrimitiveRecord(
4025-
initialRemoteBalanceType, &remoteBalance,
4026-
),
4027-
MakeScidRecord(realScidType, &channel.confirmedScid),
4028-
tlv.MakePrimitiveRecord(channelMemoType, &channel.Memo),
4029-
)
4030-
if err != nil {
4031-
return err
4032-
}
4033-
4034-
if err := tlvStream.Encode(&w); err != nil {
4035-
return err
4136+
auxData := newChanAuxDataFromChan(channel)
4137+
if err := auxData.encode(&w); err != nil {
4138+
return fmt.Errorf("unable to encode aux data: %w", err)
40364139
}
40374140

40384141
if err := chanBucket.Put(chanInfoKey, w.Bytes()); err != nil {
@@ -4221,45 +4324,14 @@ func fetchChanInfo(chanBucket kvdb.RBucket, channel *OpenChannel) error {
42214324
}
42224325
}
42234326

4224-
// Create balance fields in uint64, and Memo field as byte slice.
4225-
var (
4226-
localBalance uint64
4227-
remoteBalance uint64
4228-
memo []byte
4229-
)
4230-
4231-
// Create the tlv stream.
4232-
tlvStream, err := tlv.NewStream(
4233-
// Write the RevocationKeyLocator as the first entry in a tlv
4234-
// stream.
4235-
MakeKeyLocRecord(
4236-
keyLocType, &channel.RevocationKeyLocator,
4237-
),
4238-
tlv.MakePrimitiveRecord(
4239-
initialLocalBalanceType, &localBalance,
4240-
),
4241-
tlv.MakePrimitiveRecord(
4242-
initialRemoteBalanceType, &remoteBalance,
4243-
),
4244-
MakeScidRecord(realScidType, &channel.confirmedScid),
4245-
tlv.MakePrimitiveRecord(channelMemoType, &memo),
4246-
)
4247-
if err != nil {
4248-
return err
4327+
var auxData chanAuxData
4328+
if err := auxData.decode(r); err != nil {
4329+
return fmt.Errorf("unable to decode aux data: %w", err)
42494330
}
42504331

4251-
if err := tlvStream.Decode(r); err != nil {
4252-
return err
4253-
}
4254-
4255-
// Attach the balance fields.
4256-
channel.InitialLocalBalance = lnwire.MilliSatoshi(localBalance)
4257-
channel.InitialRemoteBalance = lnwire.MilliSatoshi(remoteBalance)
4258-
4259-
// Attach the memo field if non-empty.
4260-
if len(memo) > 0 {
4261-
channel.Memo = memo
4262-
}
4332+
// Assign all the relevant fields from the aux data into the actual
4333+
// open channel.
4334+
auxData.toOpenChan(channel)
42634335

42644336
channel.Packager = NewChannelPackager(channel.ShortChannelID)
42654337

@@ -4417,6 +4489,25 @@ func deleteThawHeight(chanBucket kvdb.RwBucket) error {
44174489
return chanBucket.Delete(frozenChanKey)
44184490
}
44194491

4492+
// keyLocRecord is a wrapper struct around keychain.KeyLocator to implement the
4493+
// tlv.RecordProducer interface.
4494+
type keyLocRecord struct {
4495+
keychain.KeyLocator
4496+
}
4497+
4498+
// Record creates a Record out of a KeyLocator using the passed Type and the
4499+
// EKeyLocator and DKeyLocator functions. The size will always be 8 as
4500+
// KeyFamily is uint32 and the Index is uint32.
4501+
//
4502+
// NOTE: This is part of the tlv.RecordProducer interface.
4503+
func (k *keyLocRecord) Record() tlv.Record {
4504+
// Note that we set the type here as zero, as when used with a
4505+
// tlv.RecordT, the type param will be used as the type.
4506+
return tlv.MakeStaticRecord(
4507+
0, &k.KeyLocator, 8, EKeyLocator, DKeyLocator,
4508+
)
4509+
}
4510+
44204511
// EKeyLocator is an encoder for keychain.KeyLocator.
44214512
func EKeyLocator(w io.Writer, val interface{}, buf *[8]byte) error {
44224513
if v, ok := val.(*keychain.KeyLocator); ok {
@@ -4445,22 +4536,6 @@ func DKeyLocator(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
44454536
return tlv.NewTypeForDecodingErr(val, "keychain.KeyLocator", l, 8)
44464537
}
44474538

4448-
// MakeKeyLocRecord creates a Record out of a KeyLocator using the passed
4449-
// Type and the EKeyLocator and DKeyLocator functions. The size will always be
4450-
// 8 as KeyFamily is uint32 and the Index is uint32.
4451-
func MakeKeyLocRecord(typ tlv.Type, keyLoc *keychain.KeyLocator) tlv.Record {
4452-
return tlv.MakeStaticRecord(typ, keyLoc, 8, EKeyLocator, DKeyLocator)
4453-
}
4454-
4455-
// MakeScidRecord creates a Record out of a ShortChannelID using the passed
4456-
// Type and the EShortChannelID and DShortChannelID functions. The size will
4457-
// always be 8 for the ShortChannelID.
4458-
func MakeScidRecord(typ tlv.Type, scid *lnwire.ShortChannelID) tlv.Record {
4459-
return tlv.MakeStaticRecord(
4460-
typ, scid, 8, lnwire.EShortChannelID, lnwire.DShortChannelID,
4461-
)
4462-
}
4463-
44644539
// ShutdownInfo contains various info about the shutdown initiation of a
44654540
// channel.
44664541
type ShutdownInfo struct {

channeldb/channel_test.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/davecgh/go-spew/spew"
1818
"github.com/lightningnetwork/lnd/channeldb/models"
1919
"github.com/lightningnetwork/lnd/clock"
20+
"github.com/lightningnetwork/lnd/fn"
2021
"github.com/lightningnetwork/lnd/keychain"
2122
"github.com/lightningnetwork/lnd/kvdb"
2223
"github.com/lightningnetwork/lnd/lnmock"
@@ -172,7 +173,7 @@ func fundingPointOption(chanPoint wire.OutPoint) testChannelOption {
172173
}
173174

174175
// channelIDOption is an option which sets the short channel ID of the channel.
175-
var channelIDOption = func(chanID lnwire.ShortChannelID) testChannelOption {
176+
func channelIDOption(chanID lnwire.ShortChannelID) testChannelOption {
176177
return func(params *testChannelParams) {
177178
params.channel.ShortChannelID = chanID
178179
}
@@ -312,6 +313,9 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
312313
uniqueOutputIndex.Add(1)
313314
op := wire.OutPoint{Hash: key, Index: uniqueOutputIndex.Load()}
314315

316+
var tapscriptRoot chainhash.Hash
317+
copy(tapscriptRoot[:], bytes.Repeat([]byte{1}, 32))
318+
315319
return &OpenChannel{
316320
ChanType: SingleFunderBit | FrozenBit,
317321
ChainHash: key,
@@ -354,6 +358,8 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
354358
ThawHeight: uint32(defaultPendingHeight),
355359
InitialLocalBalance: lnwire.MilliSatoshi(9000),
356360
InitialRemoteBalance: lnwire.MilliSatoshi(3000),
361+
Memo: []byte("test"),
362+
TapscriptRoot: fn.Some(tapscriptRoot),
357363
}
358364
}
359365

contractcourt/chain_watcher.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/davecgh/go-spew/spew"
1717
"github.com/lightningnetwork/lnd/chainntnfs"
1818
"github.com/lightningnetwork/lnd/channeldb"
19+
"github.com/lightningnetwork/lnd/fn"
1920
"github.com/lightningnetwork/lnd/input"
2021
"github.com/lightningnetwork/lnd/lnwallet"
2122
)
@@ -301,8 +302,11 @@ func (c *chainWatcher) Start() error {
301302
err error
302303
)
303304
if chanState.ChanType.IsTaproot() {
305+
fundingOpts := fn.MapOptionZ(
306+
chanState.TapscriptRoot, lnwallet.TapscriptRootToOpt,
307+
)
304308
c.fundingPkScript, _, err = input.GenTaprootFundingScript(
305-
localKey, remoteKey, 0,
309+
localKey, remoteKey, 0, fundingOpts...,
306310
)
307311
if err != nil {
308312
return err

0 commit comments

Comments
 (0)