Skip to content

Commit 1dd4f02

Browse files
authored
Merge pull request #1413 from lightninglabs/group-key-support
[wallet 3/3]: group key support for channel funding
2 parents 8ca9f98 + 5f672a7 commit 1dd4f02

28 files changed

+1833
-1129
lines changed

.github/workflows/main.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ env:
2121

2222
GO_VERSION: '1.23.6'
2323

24-
LITD_ITEST_BRANCH: 'closedchannel-data'
24+
LITD_ITEST_BRANCH: 'group-key-support'
2525

2626
jobs:
2727
#######################

rpcserver.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3561,6 +3561,16 @@ func marshalOutboundParcel(
35613561
return nil, err
35623562
}
35633563

3564+
var proofAsset asset.Asset
3565+
err = proof.SparseDecode(
3566+
bytes.NewReader(out.ProofSuffix),
3567+
proof.AssetLeafRecord(&proofAsset),
3568+
)
3569+
if err != nil {
3570+
return nil, fmt.Errorf("unable to sparse decode "+
3571+
"proof: %w", err)
3572+
}
3573+
35643574
// Marshall the proof delivery status.
35653575
proofDeliveryStatus := marshalOutputProofDeliveryStatus(out)
35663576

@@ -3576,6 +3586,7 @@ func marshalOutboundParcel(
35763586
OutputType: rpcOutType,
35773587
AssetVersion: assetVersion,
35783588
ProofDeliveryStatus: proofDeliveryStatus,
3589+
AssetId: fn.ByteSlice(proofAsset.ID()),
35793590
}
35803591
}
35813592

@@ -7099,7 +7110,7 @@ func (r *rpcServer) FundChannel(ctx context.Context,
70997110
}
71007111

71017112
assetID, groupKey, err := parseAssetSpecifier(
7102-
req.GetAssetId(), "", nil, "",
7113+
req.GetAssetId(), "", req.GetGroupKey(), "",
71037114
)
71047115
if err != nil {
71057116
return nil, fmt.Errorf("error parsing asset specifier: %w", err)

tapchannel/aux_closer.go

Lines changed: 80 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func NewAuxChanCloser(cfg AuxChanCloserCfg) *AuxChanCloser {
104104
// createCloseAlloc is a helper function that creates an allocation for an asset
105105
// close. This does not set a script key, as the script key will be set for each
106106
// packet after the coins have been distributed.
107-
func createCloseAlloc(isLocal, isInitiator bool, outputSum uint64,
107+
func createCloseAlloc(isLocal bool, outputSum uint64,
108108
shutdownMsg tapchannelmsg.AuxShutdownMsg) (*tapsend.Allocation, error) {
109109

110110
// The sort pkScript for the allocation will just be the internal key,
@@ -146,7 +146,6 @@ func createCloseAlloc(isLocal, isInitiator bool, outputSum uint64,
146146

147147
return tapsend.CommitAllocationToRemote
148148
}(),
149-
SplitRoot: isInitiator,
150149
InternalKey: shutdownMsg.AssetInternalKey.Val,
151150
GenScriptKey: scriptKeyGen,
152151
Amount: outputSum,
@@ -157,24 +156,58 @@ func createCloseAlloc(isLocal, isInitiator bool, outputSum uint64,
157156
}, nil
158157
}
159158

160-
// fundingSpendWitness creates a complete witness to spend the OP_TRUE funding
161-
// script of an asset funding output.
162-
func fundingSpendWitness() lfn.Result[wire.TxWitness] {
163-
fundingScriptTree := tapscript.NewChannelFundingScriptTree()
159+
// signCommitVirtualPackets signs the commit virtual packets with the funding
160+
// witness, which is just the script and control block for the OP_TRUE spend.
161+
func signCommitVirtualPackets(ctx context.Context,
162+
vPackets []*tappsbt.VPacket) error {
164163

165-
tapscriptTree := fundingScriptTree.TapscriptTree
166-
ctrlBlock := tapscriptTree.LeafMerkleProofs[0].ToControlBlock(
167-
&input.TaprootNUMSKey,
168-
)
169-
ctrlBlockBytes, err := ctrlBlock.ToBytes()
170-
if err != nil {
171-
return lfn.Errf[wire.TxWitness]("unable to serialize control "+
172-
"block: %w", err)
164+
useUniqueScriptKey := len(vPackets) > 1
165+
for idx := range vPackets {
166+
assetID, err := vPackets[idx].AssetID()
167+
if err != nil {
168+
return fmt.Errorf("unable to get asset ID: %w", err)
169+
}
170+
171+
// First, we'll prepare the funding witness which includes the
172+
// OP_TRUE ctrl block.
173+
fundingWitness, err := tapscript.ChannelFundingSpendWitness(
174+
useUniqueScriptKey, assetID,
175+
)
176+
if err != nil {
177+
return fmt.Errorf("unable to make funding witness: %w",
178+
err)
179+
}
180+
181+
err = tapsend.PrepareOutputAssets(ctx, vPackets[idx])
182+
if err != nil {
183+
return fmt.Errorf("unable to prepare output "+
184+
"assets: %w", err)
185+
}
186+
187+
// With the packets prepared, we'll swap in the correct witness
188+
// for each of them. We need to do this _after_ calling
189+
// PrepareOutputAsset, because that method will overwrite any
190+
// asset in the virtual outputs. Which means we'll also need to
191+
// set the witness on _every_ output of the packet, to make sure
192+
// each split output's root asset reference also gets the
193+
// correct witness.
194+
for outIdx := range vPackets[idx].Outputs {
195+
outAsset := vPackets[idx].Outputs[outIdx].Asset
196+
197+
// There is always only a single input, as we're
198+
// spending a single funding output w/ each vPkt.
199+
const inputIndex = 0
200+
err := outAsset.UpdateTxWitness(
201+
inputIndex, fundingWitness,
202+
)
203+
if err != nil {
204+
return fmt.Errorf("error updating witness: %w",
205+
err)
206+
}
207+
}
173208
}
174209

175-
return lfn.Ok(wire.TxWitness{
176-
tapscript.AnyoneCanSpendScript(), ctrlBlockBytes,
177-
})
210+
return nil
178211
}
179212

180213
// AuxCloseOutputs returns the set of close outputs to use for this co-op close
@@ -273,7 +306,7 @@ func (a *AuxChanCloser) AuxCloseOutputs(
273306
remoteSum := fn.Reduce(commitState.RemoteAssets.Val.Outputs, sumAmounts)
274307
if localSum > 0 {
275308
localAlloc, err = createCloseAlloc(
276-
true, desc.Initiator, localSum, localShutdown,
309+
true, localSum, localShutdown,
277310
)
278311
if err != nil {
279312
return none, err
@@ -285,7 +318,7 @@ func (a *AuxChanCloser) AuxCloseOutputs(
285318
}
286319
if remoteSum > 0 {
287320
remoteAlloc, err = createCloseAlloc(
288-
false, !desc.Initiator, remoteSum, remoteShutdown,
321+
false, remoteSum, remoteShutdown,
289322
)
290323
if err != nil {
291324
return none, err
@@ -378,35 +411,12 @@ func (a *AuxChanCloser) AuxCloseOutputs(
378411
return none, fmt.Errorf("unable to distribute coins: %w", err)
379412
}
380413

381-
// With the vPackets created we'll now prepare all the split
382-
// information encoded in the vPackets.
383-
fundingWitness, err := fundingSpendWitness().Unpack()
384-
if err != nil {
385-
return none, fmt.Errorf("unable to make funding "+
386-
"witness: %w", err)
387-
}
388-
ctx := context.Background()
389-
for idx := range vPackets {
390-
err := tapsend.PrepareOutputAssets(ctx, vPackets[idx])
391-
if err != nil {
392-
return none, fmt.Errorf("unable to prepare output "+
393-
"assets: %w", err)
394-
}
395-
396-
for outIdx := range vPackets[idx].Outputs {
397-
outAsset := vPackets[idx].Outputs[outIdx].Asset
398-
399-
// There is always only a single input, which is the
400-
// funding output.
401-
const inputIndex = 0
402-
err := outAsset.UpdateTxWitness(
403-
inputIndex, fundingWitness,
404-
)
405-
if err != nil {
406-
return none, fmt.Errorf("error updating "+
407-
"witness: %w", err)
408-
}
409-
}
414+
// We can now add the witness for the OP_TRUE spend of the commitment
415+
// output to the vPackets.
416+
ctxb := context.Background()
417+
if err := signCommitVirtualPackets(ctxb, vPackets); err != nil {
418+
return none, fmt.Errorf("error signing commit virtual "+
419+
"packets: %w", err)
410420
}
411421

412422
// With the outputs prepared, we can now create the set of output
@@ -484,8 +494,9 @@ func (a *AuxChanCloser) ShutdownBlob(
484494
none := lfn.None[lnwire.CustomRecords]()
485495

486496
// If there's no custom blob, then we don't need to do anything.
487-
if req.CommitBlob.IsNone() {
488-
log.Debugf("No commit blob for ChannelPoint(%v)", req.ChanPoint)
497+
if req.FundingBlob.IsNone() {
498+
log.Debugf("No funding blob for ChannelPoint(%v)",
499+
req.ChanPoint)
489500
return none, nil
490501
}
491502

@@ -500,16 +511,16 @@ func (a *AuxChanCloser) ShutdownBlob(
500511
log.Infof("Creating shutdown blob for close of ChannelPoint(%v)",
501512
req.ChanPoint)
502513

503-
// Otherwise, we'll decode the commitment, so we can examine the current
504-
// state.
505-
var commitState tapchannelmsg.Commitment
506-
err = lfn.MapOptionZ(req.CommitBlob, func(blob tlv.Blob) error {
507-
c, err := tapchannelmsg.DecodeCommitment(blob)
514+
// Otherwise, we'll decode the funding state, so we can examine the
515+
// different asset IDs in the channel.
516+
var fundingState tapchannelmsg.OpenChannel
517+
err = lfn.MapOptionZ(req.FundingBlob, func(blob tlv.Blob) error {
518+
c, err := tapchannelmsg.DecodeOpenChannel(blob)
508519
if err != nil {
509520
return err
510521
}
511522

512-
commitState = *c
523+
fundingState = *c
513524

514525
return nil
515526
})
@@ -528,38 +539,34 @@ func (a *AuxChanCloser) ShutdownBlob(
528539
return none, err
529540
}
530541

531-
// Next, we'll collect all the assets that we own in this channel.
532-
assets := commitState.LocalAssets.Val.Outputs
542+
// Next, we'll collect all the asset IDs that were committed to the
543+
// channel.
544+
assetIDs := fn.Map(
545+
fundingState.FundedAssets.Val.Outputs,
546+
func(o *tapchannelmsg.AssetOutput) asset.ID {
547+
return o.AssetID.Val
548+
},
549+
)
533550

534551
// Now that we have all the asset IDs, we'll query for a new key for
535552
// each of them which we'll use as both the internal key and the script
536553
// key.
537554
scriptKeys := make(tapchannelmsg.ScriptKeyMap)
538-
for idx := range assets {
539-
channelAsset := assets[idx]
540-
555+
for _, assetID := range assetIDs {
541556
newKey, err := a.cfg.AddrBook.NextScriptKey(
542557
ctx, asset.TaprootAssetsKeyFamily,
543558
)
544559
if err != nil {
545560
return none, err
546561
}
547562

548-
// We now add the a
549-
// TODO(guggero): This only works if there's only a single asset
550-
// in the channel. We need to extend this to support multiple
551-
// assets.
552-
_, err = a.cfg.AddrBook.NewAddressWithKeys(
553-
ctx, address.V1, channelAsset.AssetID.Val,
554-
channelAsset.Amount.Val, newKey, newInternalKey, nil,
555-
*a.cfg.DefaultCourierAddr,
556-
)
563+
err = a.cfg.AddrBook.InsertScriptKey(ctx, newKey, true)
557564
if err != nil {
558-
return none, fmt.Errorf("error adding new address: %w",
559-
err)
565+
return none, fmt.Errorf("error declaring script key: "+
566+
"%w", err)
560567
}
561568

562-
scriptKeys[channelAsset.AssetID.Val] = *newKey.PubKey
569+
scriptKeys[assetID] = *newKey.PubKey
563570
}
564571

565572
// Finally, we'll map the extra shutdown info to a TLV record map we

0 commit comments

Comments
 (0)