From 0d0fb3e33efaa6bb932daf8c693f4425eba9ea85 Mon Sep 17 00:00:00 2001
From: Satoshi Otomakan <satoshiotomakan8@gmail.com>
Date: Thu, 5 Dec 2024 21:10:06 +0100
Subject: [PATCH] [BRC20]: Fix bug when revealing BRC20 with extra P2TR

---
 .../tw_utxo/src/modules/sighash_computer.rs   | 14 ++--
 .../chains/bitcoin/bitcoin_sign/brc20.rs      | 74 +++++++++++++++++++
 2 files changed, 82 insertions(+), 6 deletions(-)

diff --git a/rust/frameworks/tw_utxo/src/modules/sighash_computer.rs b/rust/frameworks/tw_utxo/src/modules/sighash_computer.rs
index 0bcfb626960..f2ee8880590 100644
--- a/rust/frameworks/tw_utxo/src/modules/sighash_computer.rs
+++ b/rust/frameworks/tw_utxo/src/modules/sighash_computer.rs
@@ -60,11 +60,11 @@ where
             .input_args()
             .iter()
             .enumerate()
-            .map(|(input_index, utxo)| {
+            .map(|(signing_input_index, utxo)| {
                 let signing_method = utxo.signing_method;
 
                 let utxo_args = UtxoPreimageArgs {
-                    input_index,
+                    input_index: signing_input_index,
                     script_pubkey: utxo.script_pubkey.clone(),
                     amount: utxo.amount,
                     // TODO move `leaf_hash_code_separator` to `UtxoTaprootPreimageArgs`.
@@ -90,12 +90,14 @@ where
                         let tr_spent_script_pubkeys: Vec<Script> = unsigned_tx
                             .input_args()
                             .iter()
-                            .map(|utxo| {
-                                if utxo.signing_method == SigningMethod::Taproot {
-                                    // Taproot UTXOs scriptPubkeys should be signed as is.
+                            .enumerate()
+                            .map(|(i, utxo)| {
+                                if i == signing_input_index {
+                                    // Use the scriptPubkey required to spend this UTXO.
                                     utxo.script_pubkey.clone()
                                 } else {
-                                    // Use the original scriptPubkey declared in the unspent output.
+                                    // Use the original scriptPubkey declared in the unspent output for other UTXOs
+                                    // (different from that we sign at this iteration).
                                     utxo.prevout_script_pubkey.clone()
                                 }
                             })
diff --git a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/brc20.rs b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/brc20.rs
index 5836744205a..b81777faef6 100644
--- a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/brc20.rs
+++ b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/brc20.rs
@@ -196,3 +196,77 @@ fn test_bitcoin_sign_brc20_transfer() {
             fee: 3000,
         });
 }
+
+/// Fixes `{"error":"-26: non-mandatory-script-verify-flag (Invalid Schnorr signature)"}` error.
+#[test]
+fn test_bitcoin_sign_brc20_reveal_with_extra_p2tr_input() {
+    // bc1puq428nh4eynlqph8gynwdtqg4je0hc03gp2ptgsf4c5ylxz0ll2sd34gk7
+    let alice_pk_bytes = "8efa479919269076eb331c304fff187b9d7aa60d1f6cd3d6b12a151a52f22582"
+        .decode_hex()
+        .unwrap();
+    let alice_private_key = schnorr::PrivateKey::try_from(alice_pk_bytes.as_slice()).unwrap();
+    let alice_pubkey = alice_private_key.public().compressed();
+    let my_address = "bc1puq428nh4eynlqph8gynwdtqg4je0hc03gp2ptgsf4c5ylxz0ll2sd34gk7";
+
+    let commit_txid = "164b459a49c5e3a817028df7f4545585874feff3985e48d6ff6792989a4823a8";
+    let commit_utxo = Proto::Input {
+        out_point: input::out_point(commit_txid, 0),
+        value: 546,
+        sighash_type: SIGHASH_ALL,
+        claiming_script: input::brc20_inscribe(alice_pubkey.to_vec(), "duna", "0.001"),
+        ..Default::default()
+    };
+
+    // Extra P2TR UTXO is used to cover transaction fee.
+    let p2tr_utxo = "164b459a49c5e3a817028df7f4545585874feff3985e48d6ff6792989a4823a8";
+    let extra_p2tr = Proto::Input {
+        out_point: input::out_point(p2tr_utxo, 1),
+        value: 11_210,
+        sighash_type: SIGHASH_ALL,
+        claiming_script: input::receiver_address(my_address),
+        ..Default::default()
+    };
+
+    let out1 = Proto::Output {
+        value: 546,
+        to_recipient: output::to_address(my_address),
+    };
+    let change_output = Proto::Output {
+        value: 0,
+        to_recipient: output::to_address(my_address),
+    };
+
+    let builder = Proto::TransactionBuilder {
+        version: Proto::TransactionVersion::V2,
+        inputs: vec![commit_utxo, extra_p2tr],
+        outputs: vec![out1],
+        change_output: Some(change_output),
+        input_selector: Proto::InputSelector::UseAll,
+        dust_policy: dust_threshold(DUST),
+        fee_per_vb: 9,
+        ..Default::default()
+    };
+
+    let signing = Proto::SigningInput {
+        private_keys: vec![alice_pk_bytes.into()],
+        chain_info: btc_info(),
+        // We enable deterministic Schnorr signatures here
+        dangerous_use_fixed_schnorr_rng: true,
+        transaction: TransactionOneof::builder(builder),
+        ..Default::default()
+    };
+
+    // https://www.blockchain.com/explorer/transactions/btc/113dfc827e4535dccc6aa7fcff5482b4de0fb2ab70f52c44c12c12bca3be5847
+    sign::BitcoinSignHelper::new(&signing)
+        .coin(CoinType::Bitcoin)
+        .sign(sign::Expected {
+            encoded: "02000000000102a823489a989267ffd6485e98f3ef4f87855554f4f78d0217a8e3c5499a454b160000000000ffffffffa823489a989267ffd6485e98f3ef4f87855554f4f78d0217a8e3c5499a454b160100000000ffffffff022202000000000000225120e02aa3cef5c927f006e74126e6ac08acb2fbe1f1405415a209ae284f984fffd52d23000000000000225120e02aa3cef5c927f006e74126e6ac08acb2fbe1f1405415a209ae284f984fffd50340b90b099a8facd5d4e6008990d180f9afeeb07d62452570a6e700ca0f7968577da6113cb201e9ac3584caa04898cdc7113cce4df06604b8f294a3959d216d364f5e0063036f7264010118746578742f706c61696e3b636861727365743d7574662d38003a7b2270223a226272632d3230222c226f70223a227472616e73666572222c227469636b223a2264756e61222c22616d74223a22302e303031227d6821c02146f58256fcc00ef86a0e53fc14e943bbea2c7972b598b58178fdd6fa3ef79201401c5e54a0ead877e52146e42f8d197f4c7be84c7d2479a75f33128f15777584bfec410c3e130b4504fe061991a78365add223a3dfb5ca79a988f9ffcf46039b3100000000",
+            txid: "113dfc827e4535dccc6aa7fcff5482b4de0fb2ab70f52c44c12c12bca3be5847",
+            inputs: vec![546, 11_210],
+            outputs: vec![546, 9_005],
+            // `vsize` is different from the estimated value due to the signatures der serialization.
+            vsize: 244,
+            weight: 975,
+            fee: 2_205,
+        });
+}