@@ -1090,6 +1090,84 @@ func assetOutputToVPacket(fundingInputProofs map[asset.ID]*proof.Proof,
10901090 return nil
10911091}
10921092
1093+ // reanchorAssetOutputs updates each asset output proof so that it references
1094+ // the actual commitment transaction output. Proofs are initially built using
1095+ // a synthetic commitment template. Once the real commitment transaction is
1096+ // known, we rewrite the proof's anchor transaction and output indexes.
1097+ func reanchorAssetOutputs (ctx context.Context ,
1098+ chainBridge tapgarden.ChainBridge , commitTx wire.MsgTx ,
1099+ commitTxBlockHeight uint32 , outputs []* cmsg.AssetOutput ) error {
1100+
1101+ if len (outputs ) == 0 {
1102+ return nil
1103+ }
1104+
1105+ // Only fetch block data if any proof is missing it.
1106+ proofBlockParams , err := proofParamsForCommitTx (
1107+ ctx , chainBridge , commitTxBlockHeight , commitTx ,
1108+ )
1109+ if err != nil {
1110+ return fmt .Errorf ("constructing proof block params: %w" , err )
1111+ }
1112+
1113+ for _ , output := range outputs {
1114+ p := & output .Proof .Val
1115+
1116+ // Derive the Taproot output script for this proof so we can
1117+ // locate the correct output index in the real commitment
1118+ // transaction.
1119+ pkScript , tapKey , err := p .TaprootOutputScript ()
1120+ if err != nil {
1121+ return err
1122+ }
1123+
1124+ var idx = - 1
1125+ for outIdx , txOut := range commitTx .TxOut {
1126+ if bytes .Equal (txOut .PkScript , pkScript ) {
1127+ idx = outIdx
1128+ break
1129+ }
1130+ }
1131+
1132+ if idx < 0 {
1133+ return fmt .Errorf ("no matching commit output found " +
1134+ "for asset %x (tap_key=%x)" , output .AssetID .Val ,
1135+ schnorr .SerializePubKey (tapKey ))
1136+ }
1137+
1138+ err = p .UpdateTransitionProof (& proofBlockParams )
1139+ if err != nil {
1140+ return fmt .Errorf ("failed to populate proof block: %w" ,
1141+ err )
1142+ }
1143+
1144+ // Ensure the anchor transaction actually spends the previous
1145+ // asset outpoint. Return an error if the stored PrevOut doesn't
1146+ // match any input.
1147+ if len (commitTx .TxIn ) == 0 {
1148+ return fmt .Errorf ("commit tx %v has no inputs" ,
1149+ commitTx .TxHash ())
1150+ }
1151+ prevMatches := false
1152+ for _ , txIn := range commitTx .TxIn {
1153+ if txIn .PreviousOutPoint == p .PrevOut {
1154+ prevMatches = true
1155+ break
1156+ }
1157+ }
1158+ if ! prevMatches {
1159+ return fmt .Errorf ("commit tx does not spend PrevOut " +
1160+ "(txid=%s, prev_out=%s)" ,
1161+ commitTx .TxHash ().String (), p .PrevOut .String ())
1162+ }
1163+
1164+ p .AnchorTx = commitTx
1165+ p .InclusionProof .OutputIndex = uint32 (idx )
1166+ }
1167+
1168+ return nil
1169+ }
1170+
10931171// anchorOutputAllocations is a helper function that creates a set of
10941172// allocations for the anchor outputs. We'll use this later to create the proper
10951173// exclusion proofs.
@@ -1846,11 +1924,21 @@ func (a *AuxSweeper) resolveContract(
18461924 "skipping" , req .CommitTx .TxHash ())
18471925 }
18481926
1927+ if req .CommitTx == nil {
1928+ return lfn .Errf [returnType ]("no commitment transaction " +
1929+ "found for chan_point=%v" , req .ChanPoint )
1930+ }
1931+ commitTx := * req .CommitTx
1932+
18491933 // The input proofs above were made originally using the fake commit tx
1850- // as an anchor. We now know the real commit tx, so we'll swap that in
1851- // to ensure the outpoints used below are correct.
1852- for _ , assetOut := range assetOutputs {
1853- assetOut .Proof .Val .AnchorTx = * req .CommitTx
1934+ // as an anchor. We now know the real commit tx, so we'll bind each
1935+ // proof to the actual commitment output that carries the asset.
1936+ if err := reanchorAssetOutputs (
1937+ ctx , a .cfg .ChainBridge , commitTx , req .CommitTxBlockHeight ,
1938+ assetOutputs ,
1939+ ); err != nil {
1940+ return lfn .Errf [returnType ]("unable to re-anchor asset " +
1941+ "outputs: %w" , err )
18541942 }
18551943
18561944 log .Infof ("Sweeping %v asset outputs (second_level=%v): %v" ,
0 commit comments