@@ -27,6 +27,7 @@ import (
2727 "github.com/lightninglabs/taproot-assets/fn"
2828 "github.com/lightninglabs/taproot-assets/tappsbt"
2929 "github.com/lightninglabs/taproot-assets/tapscript"
30+ lfn "github.com/lightningnetwork/lnd/fn"
3031 "github.com/lightningnetwork/lnd/input"
3132 "github.com/lightningnetwork/lnd/lnwallet/btcwallet"
3233 "golang.org/x/exp/maps"
@@ -1030,9 +1031,10 @@ func addKeyTweaks(unknowns []*psbt.Unknown, desc *lndclient.SignDescriptor) {
10301031func CreateOutputCommitments (
10311032 packets []* tappsbt.VPacket ) (tappsbt.OutputCommitments , error ) {
10321033
1033- // We create an empty output commitment map, keyed by the anchor output
1034- // index.
1035- outputCommitments := make (tappsbt.OutputCommitments )
1034+ // Inputs must be unique.
1035+ if err := AssertInputsUnique (packets ); err != nil {
1036+ return nil , err
1037+ }
10361038
10371039 // We require all outputs that reference the same anchor output to be
10381040 // identical, otherwise some assumptions in the code below don't hold.
@@ -1052,6 +1054,10 @@ func CreateOutputCommitments(
10521054 return nil , err
10531055 }
10541056
1057+ // We create an empty output commitment map, keyed by the anchor output
1058+ // index.
1059+ outputCommitments := make (tappsbt.OutputCommitments )
1060+
10551061 // And now we commit each packet to the respective anchor output
10561062 // commitments.
10571063 for _ , vPkt := range packets {
@@ -1544,6 +1550,26 @@ func AssertInputAnchorsEqual(packets []*tappsbt.VPacket) error {
15441550 return nil
15451551}
15461552
1553+ // AssertInputsUnique makes sure that every input across all virtual packets is
1554+ // referencing a unique input asset, which is identified by the input PrevID.
1555+ func AssertInputsUnique (packets []* tappsbt.VPacket ) error {
1556+ // PrevIDs are comparable enough to serve as a map key without hashing.
1557+ inputs := make (lfn.Set [asset.PrevID ])
1558+
1559+ for _ , vPkt := range packets {
1560+ for _ , vIn := range vPkt .Inputs {
1561+ if inputs .Contains (vIn .PrevID ) {
1562+ return fmt .Errorf ("input %v is duplicated" ,
1563+ vIn .PrevID )
1564+ }
1565+
1566+ inputs .Add (vIn .PrevID )
1567+ }
1568+ }
1569+
1570+ return nil
1571+ }
1572+
15471573// ExtractUnSpendable extracts all tombstones and burns from the active input
15481574// commitment.
15491575func ExtractUnSpendable (c * commitment.TapCommitment ) []* asset.Asset {
0 commit comments