Skip to content

Commit 96fac8d

Browse files
authored
Merge pull request #1884 from lightninglabs/wip/add-chainporter-validation/add-proof-validation-state
tapfreighter: add `SendStateVerifyPreBroadcast` and supporting proof reanchor helpers
2 parents 0431e24 + 815021f commit 96fac8d

File tree

16 files changed

+453
-34
lines changed

16 files changed

+453
-34
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ require (
3131
github.com/lightninglabs/lndclient v0.20.0-5
3232
github.com/lightninglabs/neutrino/cache v1.1.3
3333
github.com/lightninglabs/taproot-assets/taprpc v1.0.9
34-
github.com/lightningnetwork/lnd v0.20.0-beta
34+
github.com/lightningnetwork/lnd v0.20.0-beta.rc4.0.20251127014118-f8b5cb0e8918
3535
github.com/lightningnetwork/lnd/cert v1.2.2
3636
github.com/lightningnetwork/lnd/clock v1.1.1
3737
github.com/lightningnetwork/lnd/fn/v2 v2.0.9

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,8 +1152,8 @@ github.com/lightninglabs/protobuf-go-hex-display v1.34.2-hex-display h1:w7FM5LH9
11521152
github.com/lightninglabs/protobuf-go-hex-display v1.34.2-hex-display/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
11531153
github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9 h1:6D3LrdagJweLLdFm1JNodZsBk6iU4TTsBBFLQ4yiXfI=
11541154
github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9/go.mod h1:EDqJ3MuZIbMq0QI1czTIKDJ/GS8S14RXPwapHw8cw6w=
1155-
github.com/lightningnetwork/lnd v0.20.0-beta h1:ML+jgJ3UKDGJdUf0m73ZeR/szJKWVtHxpQP+yFC79b8=
1156-
github.com/lightningnetwork/lnd v0.20.0-beta/go.mod h1:8hc55AnE3mMSJ/UAEJZgmhgNCcH0yWaPg0olpxhhp4M=
1155+
github.com/lightningnetwork/lnd v0.20.0-beta.rc4.0.20251127014118-f8b5cb0e8918 h1:FoCUqt9QVJW7LOQHocOBF6kbiybaNyQrbbsx5/FsmWY=
1156+
github.com/lightningnetwork/lnd v0.20.0-beta.rc4.0.20251127014118-f8b5cb0e8918/go.mod h1:8hc55AnE3mMSJ/UAEJZgmhgNCcH0yWaPg0olpxhhp4M=
11571157
github.com/lightningnetwork/lnd/cert v1.2.2 h1:71YK6hogeJtxSxw2teq3eGeuy4rHGKcFf0d0Uy4qBjI=
11581158
github.com/lightningnetwork/lnd/cert v1.2.2/go.mod h1:jQmFn/Ez4zhDgq2hnYSw8r35bqGVxViXhX6Cd7HXM6U=
11591159
github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0=

itest/psbt_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3663,7 +3663,7 @@ func testPsbtRelativeLockTimeSendProofFail(t *harnessTest) {
36633663

36643664
AssertSendEvents(
36653665
t.t, aliceScriptKeyBytes, sendEvents,
3666-
tapfreighter.SendStateStorePreBroadcast,
3666+
tapfreighter.SendStateVerifyPreBroadcast,
36673667
tapfreighter.SendStateWaitTxConf,
36683668
)
36693669

lndservices/chain_bridge.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,45 @@ func (l *LndRpcChainBridge) GetBlock(ctx context.Context,
111111
block, err := l.lnd.ChainKit.GetBlock(ctx, hash)
112112
if err != nil {
113113
return nil, fmt.Errorf(
114-
"unable to retrieve block: %w", err,
114+
"unable to retrieve block (hash=%s): "+
115+
"%w", hash.String(), err,
115116
)
116117
}
117118
return block, nil
118119
},
119120
)
120121
}
121122

123+
// GetBlockByHeight returns a chain block given the block height.
124+
func (l *LndRpcChainBridge) GetBlockByHeight(ctx context.Context,
125+
blockHeight int64) (*wire.MsgBlock, error) {
126+
127+
// First, we need to resolve the block hash at the given height.
128+
blockHash, err := fn.RetryFuncN(
129+
ctx, l.retryConfig, func() (chainhash.Hash, error) {
130+
var zero chainhash.Hash
131+
132+
blockHash, err := l.lnd.ChainKit.GetBlockHash(
133+
ctx, blockHeight,
134+
)
135+
if err != nil {
136+
return zero, fmt.Errorf(
137+
"unable to retrieve block hash: %w",
138+
err,
139+
)
140+
}
141+
142+
return blockHash, nil
143+
},
144+
)
145+
if err != nil {
146+
return nil, fmt.Errorf("unable to retrieve block hash: %w", err)
147+
}
148+
149+
// Now that we have the block hash, we can fetch the block.
150+
return l.GetBlock(ctx, blockHash)
151+
}
152+
122153
// GetBlockHeader returns a block header given its hash.
123154
func (l *LndRpcChainBridge) GetBlockHeader(ctx context.Context,
124155
hash chainhash.Hash) (*wire.BlockHeader, error) {

proof/proof.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@ import (
77
"io"
88

99
"github.com/btcsuite/btcd/blockchain"
10+
"github.com/btcsuite/btcd/btcec/v2"
11+
"github.com/btcsuite/btcd/txscript"
1012
"github.com/btcsuite/btcd/wire"
1113
"github.com/lightninglabs/taproot-assets/asset"
1214
"github.com/lightninglabs/taproot-assets/commitment"
15+
"github.com/lightninglabs/taproot-assets/fn"
1316
"github.com/lightningnetwork/lnd/input"
1417
"github.com/lightningnetwork/lnd/tlv"
1518
)
@@ -553,6 +556,45 @@ func (p *Proof) ToChainAsset() (asset.ChainAsset, error) {
553556
}, nil
554557
}
555558

559+
// TaprootOutputScript derives the taproot output script and taproot key
560+
// anchoring this proof using its inclusion path.
561+
func (p *Proof) TaprootOutputScript() ([]byte, *btcec.PublicKey, error) {
562+
commitmentKeys, err := p.InclusionProof.DeriveByAssetInclusion(
563+
&p.Asset, fn.Ptr(false),
564+
)
565+
if err != nil {
566+
return nil, nil, fmt.Errorf("derive inclusion commitment: %w",
567+
err)
568+
}
569+
570+
tapCommitment, err := commitmentKeys.GetCommitment()
571+
if err != nil {
572+
return nil, nil, fmt.Errorf("get taproot commitment: %w", err)
573+
}
574+
575+
siblingPreimage := p.InclusionProof.CommitmentProof.TapSiblingPreimage
576+
_, sibling, err := commitment.MaybeEncodeTapscriptPreimage(
577+
siblingPreimage,
578+
)
579+
if err != nil {
580+
return nil, nil, fmt.Errorf("encode tapscript sibling: %w", err)
581+
}
582+
583+
tapKey, err := deriveTaprootKeyFromTapCommitment(
584+
tapCommitment, sibling, p.InclusionProof.InternalKey,
585+
)
586+
if err != nil {
587+
return nil, nil, fmt.Errorf("derive taproot key: %w", err)
588+
}
589+
590+
pkScript, err := txscript.PayToTaprootScript(tapKey)
591+
if err != nil {
592+
return nil, nil, fmt.Errorf("derive taproot script: %w", err)
593+
}
594+
595+
return pkScript, tapKey, nil
596+
}
597+
556598
// Ensure Proof implements the tlv.RecordProducer interface.
557599
var _ tlv.RecordProducer = (*Proof)(nil)
558600

proof/proof_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,52 @@ func assertEqualGroupKeyReveal(t *testing.T, expected,
203203
require.FailNow(t, "unexpected group key reveal type")
204204
}
205205

206+
// TestProofTaprootOutputScript ensures the taproot key and script derived from
207+
// a proof match the tapscript root encoded in the inclusion proof.
208+
func TestProofTaprootOutputScript(t *testing.T) {
209+
t.Parallel()
210+
211+
genesis := asset.RandGenesis(t, asset.Normal)
212+
scriptKey := test.RandPubKey(t)
213+
214+
// Build a minimal block with a single transaction so RandProof can
215+
// anchor the proof.
216+
tx := wire.NewMsgTx(2)
217+
tx.AddTxOut(&wire.TxOut{Value: 1})
218+
block := wire.MsgBlock{
219+
Transactions: []*wire.MsgTx{tx},
220+
}
221+
222+
// RandProof gives us a full inclusion proof (with a non-nil sibling)
223+
// that we can use to exercise the derivation logic.
224+
p := RandProof(t, genesis, scriptKey, block, 0, 0)
225+
226+
pkScript, tapKey, err := p.TaprootOutputScript()
227+
require.NoError(t, err)
228+
229+
commitmentProof := p.InclusionProof.CommitmentProof
230+
tapCommitment, err := commitmentProof.Proof.DeriveByAssetInclusion(
231+
&p.Asset,
232+
)
233+
require.NoError(t, err)
234+
235+
_, siblingHash, err := commitment.MaybeEncodeTapscriptPreimage(
236+
commitmentProof.TapSiblingPreimage,
237+
)
238+
require.NoError(t, err)
239+
240+
expectedTapKey, err := deriveTaprootKeyFromTapCommitment(
241+
tapCommitment, siblingHash, p.InclusionProof.InternalKey,
242+
)
243+
require.NoError(t, err)
244+
245+
expectedPkScript, err := txscript.PayToTaprootScript(expectedTapKey)
246+
require.NoError(t, err)
247+
248+
require.Equal(t, expectedTapKey, tapKey)
249+
require.Equal(t, expectedPkScript, pkScript)
250+
}
251+
206252
func assertEqualProof(t *testing.T, expected, actual *Proof) {
207253
t.Helper()
208254

rpcserver.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3169,6 +3169,13 @@ func (r *rpcServer) PublishAndLogTransfer(ctx context.Context,
31693169
anchorTx.FundedPsbt.LockedUTXOs[idx] = op
31703170
}
31713171

3172+
parcelLabel := fmt.Sprintf(
3173+
"pubandlog-%s", anchorTx.FinalTx.TxHash().String(),
3174+
)
3175+
if req.Label != "" {
3176+
parcelLabel = req.Label
3177+
}
3178+
31723179
// We now have everything to ship the pre-anchored parcel using the
31733180
// freighter. This will publish the TX, create the transfer database
31743181
// entries and ship the proofs to the counterparties. It'll also wait
@@ -3177,7 +3184,7 @@ func (r *rpcServer) PublishAndLogTransfer(ctx context.Context,
31773184
resp, err := r.cfg.ChainPorter.RequestShipment(
31783185
tapfreighter.NewPreAnchoredParcel(
31793186
activePackets, passivePackets, anchorTx,
3180-
req.SkipAnchorTxBroadcast, req.Label,
3187+
req.SkipAnchorTxBroadcast, parcelLabel,
31813188
),
31823189
)
31833190
if err != nil {
@@ -3603,9 +3610,14 @@ func (r *rpcServer) SendAsset(ctx context.Context,
36033610
return nil, err
36043611
}
36053612

3613+
label := req.Label
3614+
if req.Label == "" {
3615+
label = fmt.Sprintf("rpcsendasset-%s", tapAddrs[0].String())
3616+
}
3617+
36063618
resp, err := r.cfg.ChainPorter.RequestShipment(
36073619
tapfreighter.NewAddressParcel(
3608-
feeRate, req.Label, req.SkipProofCourierPingCheck,
3620+
feeRate, label, req.SkipProofCourierPingCheck,
36093621
tapAddrs...,
36103622
),
36113623
)

tapchannel/aux_closer.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,8 @@ func (a *AuxChanCloser) AuxCloseOutputs(
259259
"state: %w", err)
260260
}
261261

262+
ctxb := context.Background()
263+
262264
// Each of the co-op close outputs needs to ref a funding input, so
263265
// we'll map a map of asset ID to the funding output now.
264266
inputProofs := make(
@@ -268,6 +270,14 @@ func (a *AuxChanCloser) AuxCloseOutputs(
268270
inputProofs = append(inputProofs, &fundingInput.Proof.Val)
269271
}
270272

273+
err = updateProofsFromShortChanID(
274+
ctxb, a.cfg.ChainBridge, desc.ShortChanID, inputProofs,
275+
)
276+
if err != nil {
277+
return none, fmt.Errorf("unable to update funding proofs: %w",
278+
err)
279+
}
280+
271281
// We'll also decode the shutdown blobs, so we can extract the shutdown
272282
// information (delivery script keys, etc.).
273283
var localShutdown, remoteShutdown tapchannelmsg.AuxShutdownMsg
@@ -454,7 +464,6 @@ func (a *AuxChanCloser) AuxCloseOutputs(
454464

455465
// We can now add the witness for the OP_TRUE spend of the commitment
456466
// output to the vPackets.
457-
ctxb := context.Background()
458467
if err := signCommitVirtualPackets(ctxb, vPackets); err != nil {
459468
return none, fmt.Errorf("error signing commit virtual "+
460469
"packets: %w", err)
@@ -666,8 +675,9 @@ func shipChannelTxn(txSender tapfreighter.Porter, chanTx *wire.MsgTx,
666675
ChainFees: closeFee,
667676
FinalTx: chanTx,
668677
}
678+
parcelLabel := fmt.Sprintf("channel-tx-%s", chanTx.TxHash().String())
669679
preSignedParcel := tapfreighter.NewPreAnchoredParcel(
670-
vPkts, nil, closeAnchor, false, "",
680+
vPkts, nil, closeAnchor, false, parcelLabel,
671681
)
672682
_, err = txSender.RequestShipment(preSignedParcel)
673683
if err != nil {

tapchannel/aux_funding_controller.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1456,8 +1456,11 @@ func (f *FundingController) completeChannelFunding(ctx context.Context,
14561456
ChainFees: int64(chainFees),
14571457
FinalTx: signedFundingTx,
14581458
}
1459+
parcelLabel := fmt.Sprintf(
1460+
"channel-funding-tx-%s", signedFundingTx.TxHash().String(),
1461+
)
14591462
preSignedParcel := tapfreighter.NewPreAnchoredParcel(
1460-
activePkts, passivePkts, anchorTx, false, "",
1463+
activePkts, passivePkts, anchorTx, false, parcelLabel,
14611464
)
14621465
_, err = f.cfg.TxSender.RequestShipment(preSignedParcel)
14631466
if err != nil {

0 commit comments

Comments
 (0)