Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 77 additions & 22 deletions tapfreighter/chain_porter.go
Original file line number Diff line number Diff line change
Expand Up @@ -1408,15 +1408,35 @@ func (p *ChainPorter) prelimCheckAddrParcel(addrParcel AddressParcel) error {
return nil
}

// verifyPacketInputProofs ensures that each virtual packet's inputs reference
// a valid Taproot Asset commitment before the package is broadcast.
func (p *ChainPorter) verifyPacketInputProofs(ctx context.Context,
// verifyVPackets performs various verification checks on the given virtual
// packets.
func (p *ChainPorter) verifyVPackets(ctx context.Context,
packets []*tappsbt.VPacket) error {

if len(packets) == 0 {
return nil
for pktIdx := range packets {
vPkt := packets[pktIdx]

err := p.verifyPacketInputProofs(ctx, *vPkt)
if err != nil {
return fmt.Errorf("verify packet input proofs "+
"(vpkt_idx=%d): %w", pktIdx, err)
}

err = verifySplitCommitmentWitnesses(*vPkt)
if err != nil {
return fmt.Errorf("verify split commitment "+
"witnesses (vpkt_idx=%d): %w", pktIdx, err)
}
}

return nil
}

// verifyPacketInputProofs ensures that each virtual packet's inputs reference
// a valid Taproot Asset commitment before the package is broadcast.
func (p *ChainPorter) verifyPacketInputProofs(ctx context.Context,
vPkt tappsbt.VPacket) error {

headerVerifier := tapgarden.GenHeaderVerifier(ctx, p.cfg.ChainBridge)
vCtx := proof.VerifierCtx{
HeaderVerifier: headerVerifier,
Expand All @@ -1426,21 +1446,57 @@ func (p *ChainPorter) verifyPacketInputProofs(ctx context.Context,
IgnoreChecker: p.cfg.IgnoreChecker,
}

for pktIdx := range packets {
vPkt := packets[pktIdx]
for inputIdx := range vPkt.Inputs {
assetProof := vPkt.Inputs[inputIdx].Proof
if assetProof == nil {
return fmt.Errorf("packet %d input %d proof "+
"is nil", pktIdx, inputIdx)
}
for inputIdx := range vPkt.Inputs {
assetProof := vPkt.Inputs[inputIdx].Proof
if assetProof == nil {
return fmt.Errorf("packet input proof is nil "+
"(input_idx=%d)", inputIdx)
}

_, err := assetProof.VerifyProofIntegrity(ctx, vCtx)
if err != nil {
return fmt.Errorf("unable to verify "+
"inclusion proof for packet %d "+
"input %d: %w", pktIdx, inputIdx, err)
}
_, err := assetProof.VerifyProofIntegrity(ctx, vCtx)
if err != nil {
return fmt.Errorf("unable to verify "+
"inclusion proof for packet input "+
"(input_idx=%d): %w", inputIdx, err)
}
}

return nil
}

// verifySplitCommitmentWitnesses ensures split leaf outputs embed a split root
// that actually carries a witness. Split leaves intentionally keep their own
// TxWitness empty and rely on the embedded root witness for validation.
func verifySplitCommitmentWitnesses(vPkt tappsbt.VPacket) error {
for outIdx := range vPkt.Outputs {
vOut := vPkt.Outputs[outIdx]

if vOut.Asset == nil ||
!vOut.Asset.HasSplitCommitmentWitness() {

continue
}

splitCommitment := vOut.Asset.PrevWitnesses[0].SplitCommitment
if splitCommitment == nil {
return fmt.Errorf("output missing split commitment "+
"(output_idx=%d)", outIdx)
}

root := &splitCommitment.RootAsset
if len(root.PrevWitnesses) == 0 {
return fmt.Errorf("output split root has no prev "+
"witnesses (output_idx=%d)", outIdx)
}

hasWitness := fn.Any(
root.PrevWitnesses, func(wit asset.Witness) bool {
return len(wit.TxWitness) > 0
},
)
if !hasWitness {
return fmt.Errorf("output split root witness empty "+
"(output_idx=%d)", outIdx)
}
}

Expand Down Expand Up @@ -1697,12 +1753,11 @@ func (p *ChainPorter) stateStep(currentPkg sendPackage) (*sendPackage, error) {
allPackets = append(allPackets, currentPkg.VirtualPackets...)
allPackets = append(allPackets, currentPkg.PassiveAssets...)

err := p.verifyPacketInputProofs(ctx, allPackets)
err := p.verifyVPackets(ctx, allPackets)
if err != nil {
p.unlockInputs(ctx, &currentPkg)

return nil, fmt.Errorf("unable to verify input "+
"proofs: %w", err)
return nil, fmt.Errorf("verifying vPackets: %w", err)
}

currentPkg.SendState = SendStateStorePreBroadcast
Expand Down
89 changes: 89 additions & 0 deletions tapfreighter/chain_porter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import (
"testing"
"time"

"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btclog/v2"
"github.com/lightninglabs/taproot-assets/asset"
"github.com/lightninglabs/taproot-assets/tappsbt"
"github.com/stretchr/testify/require"
)

func TestRunChainPorter(t *testing.T) {
Expand All @@ -19,3 +23,88 @@ func init() {
logger := btclog.NewSLogger(btclog.NewDefaultHandler(os.Stdout))
UseLogger(logger.SubSystem(Subsystem))
}

// TestVerifySplitCommitmentWitnesses exercises the split witness verifier with
// table-driven vPacket fixtures.
func TestVerifySplitCommitmentWitnesses(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
vPkt func() tappsbt.VPacket
expectError bool
}{
{
name: "split leaf with root witness passes",
vPkt: func() tappsbt.VPacket {
root := asset.Asset{
PrevWitnesses: []asset.Witness{{
PrevID: &asset.ZeroPrevID,
TxWitness: wire.TxWitness{{1}},
}},
}

prevWitnesses := []asset.Witness{{
PrevID: &asset.ZeroPrevID,
SplitCommitment: &asset.SplitCommitment{
RootAsset: root,
},
}}
splitLeaf := &asset.Asset{
PrevWitnesses: prevWitnesses,
}

return tappsbt.VPacket{
Outputs: []*tappsbt.VOutput{{
Asset: splitLeaf,
}},
}
},
expectError: false,
},
{
name: "split leaf missing root witness fails",
vPkt: func() tappsbt.VPacket {
root := asset.Asset{
PrevWitnesses: []asset.Witness{{
PrevID: &asset.ZeroPrevID,
TxWitness: wire.TxWitness{},
}},
}

prevWitnesses := []asset.Witness{{
PrevID: &asset.ZeroPrevID,
SplitCommitment: &asset.SplitCommitment{
RootAsset: root,
},
}}
splitLeaf := &asset.Asset{
PrevWitnesses: prevWitnesses,
}

return tappsbt.VPacket{
Outputs: []*tappsbt.VOutput{{
Asset: splitLeaf,
}},
}
},
expectError: true,
},
}

for _, tc := range testCases {
tc := tc

t.Run(tc.name, func(t *testing.T) {
t.Parallel()

err := verifySplitCommitmentWitnesses(tc.vPkt())
if tc.expectError {
require.Error(t, err)
return
}

require.NoError(t, err)
})
}
}
Loading