Skip to content

Commit 00fcdb5

Browse files
committed
tapfreighter: enforce split root witnesses pre-broadcast
1 parent 707bcc2 commit 00fcdb5

File tree

2 files changed

+134
-0
lines changed

2 files changed

+134
-0
lines changed

tapfreighter/chain_porter.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,6 +1421,12 @@ func (p *ChainPorter) verifyVPackets(ctx context.Context,
14211421
return fmt.Errorf("verify packet input proofs "+
14221422
"(vpkt_idx=%d): %w", pktIdx, err)
14231423
}
1424+
1425+
err = verifySplitCommitmentWitnesses(*vPkt)
1426+
if err != nil {
1427+
return fmt.Errorf("verify split commitment "+
1428+
"witnesses (vpkt_idx=%d): %w", pktIdx, err)
1429+
}
14241430
}
14251431

14261432
return nil
@@ -1458,6 +1464,45 @@ func (p *ChainPorter) verifyPacketInputProofs(ctx context.Context,
14581464
return nil
14591465
}
14601466

1467+
// verifySplitCommitmentWitnesses ensures split leaf outputs embed a split root
1468+
// that actually carries a witness. Split leaves intentionally keep their own
1469+
// TxWitness empty and rely on the embedded root witness for validation.
1470+
func verifySplitCommitmentWitnesses(vPkt tappsbt.VPacket) error {
1471+
for outIdx := range vPkt.Outputs {
1472+
vOut := vPkt.Outputs[outIdx]
1473+
1474+
if vOut.Asset == nil ||
1475+
!vOut.Asset.HasSplitCommitmentWitness() {
1476+
1477+
continue
1478+
}
1479+
1480+
splitCommitment := vOut.Asset.PrevWitnesses[0].SplitCommitment
1481+
if splitCommitment == nil {
1482+
return fmt.Errorf("output missing split commitment "+
1483+
"(output_idx=%d)", outIdx)
1484+
}
1485+
1486+
root := &splitCommitment.RootAsset
1487+
if len(root.PrevWitnesses) == 0 {
1488+
return fmt.Errorf("output split root has no prev "+
1489+
"witnesses (output_idx=%d)", outIdx)
1490+
}
1491+
1492+
hasWitness := fn.Any(
1493+
root.PrevWitnesses, func(wit asset.Witness) bool {
1494+
return len(wit.TxWitness) > 0
1495+
},
1496+
)
1497+
if !hasWitness {
1498+
return fmt.Errorf("output split root witness empty "+
1499+
"(output_idx=%d)", outIdx)
1500+
}
1501+
}
1502+
1503+
return nil
1504+
}
1505+
14611506
// stateStep attempts to step through the state machine to complete a Taproot
14621507
// Asset transfer.
14631508
func (p *ChainPorter) stateStep(currentPkg sendPackage) (*sendPackage, error) {

tapfreighter/chain_porter_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import (
66
"testing"
77
"time"
88

9+
"github.com/btcsuite/btcd/wire"
910
"github.com/btcsuite/btclog/v2"
11+
"github.com/lightninglabs/taproot-assets/asset"
12+
"github.com/lightninglabs/taproot-assets/tappsbt"
13+
"github.com/stretchr/testify/require"
1014
)
1115

1216
func TestRunChainPorter(t *testing.T) {
@@ -19,3 +23,88 @@ func init() {
1923
logger := btclog.NewSLogger(btclog.NewDefaultHandler(os.Stdout))
2024
UseLogger(logger.SubSystem(Subsystem))
2125
}
26+
27+
// TestVerifySplitCommitmentWitnesses exercises the split witness verifier with
28+
// table-driven vPacket fixtures.
29+
func TestVerifySplitCommitmentWitnesses(t *testing.T) {
30+
t.Parallel()
31+
32+
testCases := []struct {
33+
name string
34+
vPkt func() tappsbt.VPacket
35+
expectError bool
36+
}{
37+
{
38+
name: "split leaf with root witness passes",
39+
vPkt: func() tappsbt.VPacket {
40+
root := asset.Asset{
41+
PrevWitnesses: []asset.Witness{{
42+
PrevID: &asset.ZeroPrevID,
43+
TxWitness: wire.TxWitness{{1}},
44+
}},
45+
}
46+
47+
prevWitnesses := []asset.Witness{{
48+
PrevID: &asset.ZeroPrevID,
49+
SplitCommitment: &asset.SplitCommitment{
50+
RootAsset: root,
51+
},
52+
}}
53+
splitLeaf := &asset.Asset{
54+
PrevWitnesses: prevWitnesses,
55+
}
56+
57+
return tappsbt.VPacket{
58+
Outputs: []*tappsbt.VOutput{{
59+
Asset: splitLeaf,
60+
}},
61+
}
62+
},
63+
expectError: false,
64+
},
65+
{
66+
name: "split leaf missing root witness fails",
67+
vPkt: func() tappsbt.VPacket {
68+
root := asset.Asset{
69+
PrevWitnesses: []asset.Witness{{
70+
PrevID: &asset.ZeroPrevID,
71+
TxWitness: wire.TxWitness{},
72+
}},
73+
}
74+
75+
prevWitnesses := []asset.Witness{{
76+
PrevID: &asset.ZeroPrevID,
77+
SplitCommitment: &asset.SplitCommitment{
78+
RootAsset: root,
79+
},
80+
}}
81+
splitLeaf := &asset.Asset{
82+
PrevWitnesses: prevWitnesses,
83+
}
84+
85+
return tappsbt.VPacket{
86+
Outputs: []*tappsbt.VOutput{{
87+
Asset: splitLeaf,
88+
}},
89+
}
90+
},
91+
expectError: true,
92+
},
93+
}
94+
95+
for _, tc := range testCases {
96+
tc := tc
97+
98+
t.Run(tc.name, func(t *testing.T) {
99+
t.Parallel()
100+
101+
err := verifySplitCommitmentWitnesses(tc.vPkt())
102+
if tc.expectError {
103+
require.Error(t, err)
104+
return
105+
}
106+
107+
require.NoError(t, err)
108+
})
109+
}
110+
}

0 commit comments

Comments
 (0)