From d5e964c724c6d17f26347b2abd5df53902f55f99 Mon Sep 17 00:00:00 2001 From: Erick Cestari Date: Mon, 5 Oct 2026 11:08:29 -0300 Subject: [PATCH 1/3] bitcoin: extract the signrawtransactionwithwallet call Lets a sign-only caller that does not require complete signing reuse it. --- smite/src/bitcoin.rs | 56 +++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/smite/src/bitcoin.rs b/smite/src/bitcoin.rs index 2aa0a1cf..4c576f8b 100644 --- a/smite/src/bitcoin.rs +++ b/smite/src/bitcoin.rs @@ -49,6 +49,15 @@ pub struct TxBlockPosition { pub tx_index: u32, } +/// Parsed response from `signrawtransactionwithwallet `. +#[derive(Deserialize)] +struct SignRawTransactionResponse { + /// Consensus-serialized transaction with every signable input signed. + hex: String, + /// Whether every input now has a complete signature set. + complete: bool, +} + /// Parsed response from `getrawtransaction 1`. #[derive(Deserialize)] struct RawTransactionInfo { @@ -295,6 +304,30 @@ impl BitcoinCli { .expect("getnewaddress should return a valid address") } + /// Runs `signrawtransactionwithwallet` and returns its parsed response. + /// + /// # Panics + /// + /// - If `bitcoin-cli signrawtransactionwithwallet` fails to execute or + /// exits non-zero. + /// - If the command succeeds but its output is not valid JSON. + fn sign_raw_transaction_with_wallet(&self, tx: &Transaction) -> SignRawTransactionResponse { + let signed_out = self + .run() + .arg("signrawtransactionwithwallet") + .arg(serialize_hex(tx)) + .output() + .expect("bitcoin-cli signrawtransactionwithwallet should not fail"); + assert!( + signed_out.status.success(), + "bitcoin-cli signrawtransactionwithwallet failed: {}", + String::from_utf8_lossy(&signed_out.stderr) + ); + + serde_json::from_slice(&signed_out.stdout) + .expect("signrawtransactionwithwallet should return valid JSON") + } + /// Signs and broadcasts a transaction, unless it is already confirmed. /// /// If the signed transaction is accepted by the mempool, it is broadcast @@ -319,12 +352,6 @@ impl BitcoinCli { /// - If the broadcasted txid does not match the given transaction's txid. #[must_use] pub fn sign_and_broadcast_tx(&self, tx: &Transaction) -> Option { - #[derive(Deserialize)] - struct SignRawTransactionResponse { - hex: String, - complete: bool, - } - // A confirmed transaction may be broadcast again by the fuzzer. Its // inputs are spent, so the wallet can no longer fully sign it, skip // signing and broadcasting it again. @@ -333,22 +360,7 @@ impl BitcoinCli { return None; } - let tx_hex = serialize_hex(tx); - - let signed_out = self - .run() - .arg("signrawtransactionwithwallet") - .arg(&tx_hex) - .output() - .expect("bitcoin-cli signrawtransactionwithwallet should not fail"); - assert!( - signed_out.status.success(), - "bitcoin-cli signrawtransactionwithwallet failed: {}", - String::from_utf8_lossy(&signed_out.stderr) - ); - - let signed_tx: SignRawTransactionResponse = serde_json::from_slice(&signed_out.stdout) - .expect("signrawtransactionwithwallet should return valid JSON"); + let signed_tx = self.sign_raw_transaction_with_wallet(tx); assert!( signed_tx.complete, "signrawtransactionwithwallet returned complete=false" From 27b66739a2738a4fdd4bbeb4587d611a83072ba1 Mon Sep 17 00:00:00 2001 From: Erick Cestari Date: Mon, 5 Oct 2026 11:08:33 -0300 Subject: [PATCH 2/3] bitcoin: sign transactions without broadcasting them Channel establishment v2 needs our own witnesses for tx_signatures, which the wallet can only produce as a partial signing of the shared transaction. --- smite/src/bitcoin.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/smite/src/bitcoin.rs b/smite/src/bitcoin.rs index 4c576f8b..6c95ecf6 100644 --- a/smite/src/bitcoin.rs +++ b/smite/src/bitcoin.rs @@ -6,7 +6,7 @@ use std::path::PathBuf; use std::process::Command; use std::str::FromStr; -use bitcoin::consensus::encode::serialize_hex; +use bitcoin::consensus::encode::{deserialize_hex, serialize_hex}; use bitcoin::{Address, Amount, Network, OutPoint, ScriptBuf, Transaction, Txid}; use serde::{Deserialize, Serialize}; @@ -304,6 +304,23 @@ impl BitcoinCli { .expect("getnewaddress should return a valid address") } + /// Signs the wallet-owned inputs of `tx` and leaves the rest untouched, so + /// unlike [`Self::sign_and_broadcast_tx`] signing need not be complete. + /// + /// # Panics + /// + /// - If `bitcoin-cli signrawtransactionwithwallet` fails to execute or + /// exits non-zero, e.g. for a transaction with no inputs, which bitcoind + /// cannot decode. + /// - If the command succeeds but its output is not valid JSON, or its `hex` + /// field does not decode as a transaction. + #[must_use] + pub fn sign_tx(&self, tx: &Transaction) -> Transaction { + let signed = self.sign_raw_transaction_with_wallet(tx); + deserialize_hex(&signed.hex) + .expect("signrawtransactionwithwallet should return a valid transaction") + } + /// Runs `signrawtransactionwithwallet` and returns its parsed response. /// /// # Panics From 88b50f1391ca3aeac9cc895932d5314cb144c9a5 Mon Sep 17 00:00:00 2001 From: Erick Cestari Date: Mon, 5 Oct 2026 11:08:34 -0300 Subject: [PATCH 3/3] bitcoin: fetch raw transactions by txid Channel establishment v2 needs the previous transaction bytes for tx_add_input's prevtx. --- smite/src/bitcoin.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/smite/src/bitcoin.rs b/smite/src/bitcoin.rs index 6c95ecf6..ad2805b9 100644 --- a/smite/src/bitcoin.rs +++ b/smite/src/bitcoin.rs @@ -67,6 +67,8 @@ struct RawTransactionInfo { confirmations: u32, /// Omitted while the transaction is unconfirmed (in the mempool). blockhash: Option, + /// Consensus-serialized transaction, always present. + hex: String, } /// Connection info for invoking `bitcoin-cli` against the regtest `bitcoind` @@ -506,6 +508,20 @@ impl BitcoinCli { .map_or(0, |info| info.confirmations) } + /// Returns the consensus-serialized transaction with the given txid, or + /// `None` if it is unknown to the node. + /// + /// # Panics + /// + /// - If the `bitcoin-cli getrawtransaction` command fails to execute. + /// - If the command succeeds but its output is not valid JSON or its `hex` + /// field is not valid hex. + #[must_use] + pub fn get_raw_transaction(&self, txid: Txid) -> Option> { + let info = self.get_raw_transaction_info(txid)?; + Some(hex::decode(&info.hex).expect("getrawtransaction should return valid hex")) + } + /// Returns the position of the confirmed transaction with the given txid, /// or `None` if it is unconfirmed (in the mempool) or unknown to the node /// (e.g. not broadcast yet).