Skip to content

Commit

Permalink
add verification for signed txs
Browse files Browse the repository at this point in the history
  • Loading branch information
PowVT committed Sep 12, 2024
1 parent dea9fe2 commit 402bc16
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 14 deletions.
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition = "2021"

[dependencies]
anyhow = "1.0.79"
bitcoin = "0.31.1"
bitcoin = { version = "0.31.1", features = ["bitcoinconsensus"] }
bitcoincore-rpc = "0.18.0"
clap = { version = "4.4.18", features = ["derive"] }
env_logger = "0.10.0"
Expand Down
21 changes: 17 additions & 4 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
help:
RUST_LOG=info ./target/release/btc-dev-utils -h

# get current block height
get-block-height:
RUST_LOG=info ./target/release/btc-dev-utils get-block-height

# get new wallet
new-wallet wallet_name="default_wallet" address_type="bech32m":
RUST_LOG=info ./target/release/btc-dev-utils -w {{ wallet_name }} -z {{ address_type }} new-wallet
new-wallet wallet_name="default_wallet":
RUST_LOG=info ./target/release/btc-dev-utils -w {{ wallet_name }} new-wallet

# get wallet info
get-wallet-info wallet_name="default_wallet":
Expand All @@ -26,8 +27,8 @@ new-multisig required_signatures="2" wallet_names="default_wallet1,default_walle
RUST_LOG=info ./target/release/btc-dev-utils -n {{ required_signatures }} -v {{ wallet_names }} -m {{ multisig_name }} new-multisig

# get new wallet address
get-new-address wallet_name="default_wallet":
RUST_LOG=info ./target/release/btc-dev-utils -w {{ wallet_name }} get-new-address
get-new-address wallet_name="default_wallet" address_type="bech32m":
RUST_LOG=info ./target/release/btc-dev-utils -w {{ wallet_name }} -z {{ address_type }} get-new-address

# get address info
get-address-info wallet_name="default_wallet" address="address":
Expand Down Expand Up @@ -61,6 +62,14 @@ list-unspent wallet_name="default_wallet":
get-tx txid="txid":
RUST_LOG=info ./target/release/btc-dev-utils -i {{ txid }} get-tx

# get details about an unspent transaction output
get-tx-out txid="txid" vout="0":
RUST_LOG=info ./target/release/btc-dev-utils -i {{ txid }} -o {{ vout }} get-tx-out

# decode raw transaction
decode-raw-tx tx_hex="tx_hex":
RUST_LOG=info ./target/release/btc-dev-utils -t {{ tx_hex }} decode-raw-tx

# create a signed BTC transaction
sign-tx wallet_name="default_wallet" recipient="recpient_address" amount="49.99" fee_amount="0.01" utxo_strat="fifo":
RUST_LOG=info ./target/release/btc-dev-utils -w {{ wallet_name }} -r {{ recipient }} -x {{ amount }} -f {{ fee_amount }} -y {{ utxo_strat }} sign-tx
Expand Down Expand Up @@ -101,6 +110,10 @@ finalize-psbt psbt="combined_psbt_hex":
finalize-psbt-and-broadcast psbt="combined_psbt_hex":
RUST_LOG=info ./target/release/btc-dev-utils -p {{ psbt }} finalize-psbt-and-broadcast

# Verify a signed transaction
verify-signed-tx tx_hex="tx_hex":
RUST_LOG=info ./target/release/btc-dev-utils -t {{ tx_hex }} verify-signed-tx

# create and ordinals inscription
inscribe-ord:
RUST_LOG=info ./target/release/btc-dev-utils inscribe-ord
Expand Down
14 changes: 10 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
use std::{fs, thread, time::Duration, path::Path};

use log::{error, info};
use modules::verification::verify_signed_tx;
use serde_json::Value;
use clap::Parser;

use modules::bitcoind_client::{
analyze_psbt,
broadcast_tx_wrapper,
combine_psbts,
decode_psbt,
combine_psbts, decode_psbt,
decode_raw_tx,
finalize_psbt,
finalize_psbt_and_broadcast,
get_block_height,
get_spendable_balance,
get_tx, rescan_blockchain
get_tx_out_wrapper,
get_tx_wrapper,
rescan_blockchain
};

use modules::wallet_ops::{
Expand Down Expand Up @@ -71,8 +74,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Action::GetSpendableBalance => get_spendable_balance(&args.address, &settings),
Action::MineBlocks => mine_blocks_wrapper(&args.wallet_name, args.blocks, &settings),
Action::ListUnspent => list_unspent(&args.wallet_name, &settings),
Action::GetTx => get_tx(&args.txid, &settings),
Action::GetTx => get_tx_wrapper(&args.txid, &settings),
Action::GetTxOut => get_tx_out_wrapper(&args.txid, args.vout, Some(args.confirmations), &settings),
Action::SignTx => sign_tx_wrapper(&args.wallet_name, &args.recipient, args.amount, args.fee_amount, args.utxo_strat, &settings),
Action::DecodeRawTx => decode_raw_tx(&args.tx_hex, &settings),
Action::BroadcastTx => broadcast_tx_wrapper( &args.tx_hex, args.max_fee_rate, &settings),
Action::SendBtc => send_btc(&args.wallet_name, &args.recipient, args.amount, &settings),
Action::CreatePsbt => create_psbt(&args.wallet_name, &args.recipient, args.amount, args.fee_amount, args.utxo_strat, &settings),
Expand All @@ -82,6 +87,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Action::CombinePsbts => combine_psbts(&args.psbts, &settings),
Action::FinalizePsbt => finalize_psbt(&args.psbt_hex, &settings),
Action::FinalizePsbtAndBroadcast => finalize_psbt_and_broadcast(&args.psbt_hex, &settings),
Action::VerifySignedTx => verify_signed_tx(&args.tx_hex, &settings),
Action::InscribeOrd => regtest_inscribe_ord(&settings)
}
}
Expand Down
56 changes: 52 additions & 4 deletions src/modules/bitcoind_client.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::str::FromStr;

use bitcoin::{Address, Amount};
use bitcoincore_rpc::json::ScanTxOutRequest;
use bitcoincore_rpc::json::{GetRawTransactionResult, GetTxOutResult, ScanTxOutRequest};
use bitcoincore_rpc::{json::FinalizePsbtResult, RpcApi, RawTx, Client};

use log::info;
Expand Down Expand Up @@ -40,17 +40,56 @@ pub fn rescan_blockchain(settings: &Settings) -> Result<(), Box<dyn std::error::
}

/// Transaction Ops

pub fn get_tx(txid: &str, settings: &Settings) -> Result<(), Box<dyn std::error::Error>> {
let client: Client = create_rpc_client(settings, None);
pub fn get_tx(txid: &str, settings: &Settings) -> Result<GetRawTransactionResult, Box<dyn std::error::Error>> {
let client: Client = create_rpc_client(settings, None);

let txid_converted = bitcoin::Txid::from_str(txid)?;
let tx = client.get_raw_transaction_info(&txid_converted, None)?;

Ok(tx)
}

pub fn get_tx_wrapper(txid: &str, settings: &Settings) -> Result<(), Box<dyn std::error::Error>> {
let tx = get_tx(txid, settings)?;

info!("{:#?}", tx);

Ok(())
}

pub fn get_tx_out(txid: &str, vout: u32, confirmations: Option<u32>, settings: &Settings) -> Result<GetTxOutResult, Box<dyn std::error::Error>> {
let client: Client = create_rpc_client(settings, None);

let txid_converted = bitcoin::Txid::from_str(txid)?;
let tx_out = client.get_tx_out(&txid_converted, vout, None)?; // None = include_mempool

match tx_out {
Some(tx_out) => {
if let Some(confirmations) = confirmations {
if tx_out.confirmations >= confirmations {
Ok(tx_out)
} else {
Err(format!("TxOut not enough confirmations").into())
}
} else {
Ok(tx_out)
}
},
None => {
Err(format!("TxOut not found").into())
},
}
}

pub fn get_tx_out_wrapper(txid: &str, vout: u32, confirmations: Option<u32>, settings: &Settings) -> Result<(), Box<dyn std::error::Error>> {
let tx_out = get_tx_out(txid, vout, confirmations,settings)?;

info!("{:#?}", tx_out);

Ok(())
}

pub fn broadcast_tx(client: &Client, tx_hex: &str, max_fee_rate: Option<f64>) -> Result<String, Box<dyn std::error::Error>> {
let max_fee_rate = match max_fee_rate {
Some(fee_rate) => {
Expand Down Expand Up @@ -78,6 +117,15 @@ pub fn broadcast_tx_wrapper(tx_hex: &str, max_fee_rate: f64, settings: &Settings
Ok(())
}

pub fn decode_raw_tx(tx_hex: &str, settings: &Settings) -> Result<(), Box<dyn std::error::Error>> {
let client = create_rpc_client(settings, None);

let tx = client.decode_raw_transaction(tx_hex, None)?;
info!("{:#?}", tx);

Ok(())
}

/// PSBT Ops

pub fn decode_psbt(psbt: &str, settings: &Settings) -> Result<(), Box<dyn std::error::Error>> {
Expand Down
3 changes: 2 additions & 1 deletion src/modules/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod bitcoind_client;
pub mod bitcoind_conn;
pub mod wallet;
pub mod wallet_ops;
pub mod wallet_ops;
pub mod verification;
57 changes: 57 additions & 0 deletions src/modules/verification.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use bitcoin::{consensus::deserialize, OutPoint, Transaction, TxOut};

use crate::{modules::bitcoind_client::get_tx, settings::Settings};

use super::bitcoind_client::get_tx_out;

pub fn verify_signed_tx(tx_hex: &str, settings: &Settings) -> Result<(), Box<dyn std::error::Error>> {
let tx: Transaction = deserialize(&hex::decode(tx_hex)?)?;

println!("Verifying transaction: {}", tx.txid());
println!("Number of inputs: {}", tx.input.len());

// Check if UTXOs are still unspent
for (index, input) in tx.input.iter().enumerate() {
println!("Checking UTXO for input {}", index);
match is_utxo_unspent(&input.previous_output, settings) {
Ok(true) => println!("UTXO for input {} is unspent", index), // UTXO is unspent, continue
Ok(false) => return Err(format!("UTXO for input {} has already been spent", index).into()),
Err(e) => return Err(format!("Error checking UTXO for input {}: {}", index, e).into()),
}
}

// Create a closure to fetch the TxOut for each input
let mut spent = |outpoint: &OutPoint| -> Option<TxOut> {
match get_tx(&outpoint.txid.to_string(), settings) {
Ok(prev_tx) => prev_tx.vout.get(outpoint.vout as usize).map(|output| {
TxOut {
value: output.value,
script_pubkey: bitcoin::ScriptBuf::from(output.script_pub_key.hex.clone()),
}
}),
Err(_) => None,
}
};

// Verify the transaction
tx.verify(&mut spent).map_err(|e| {format!("Transaction verification failed: {:?}", e)})?;

println!("Transaction verified successfully");

Ok(())
}

fn is_utxo_unspent(outpoint: &OutPoint, settings: &Settings) -> Result<bool, Box<dyn std::error::Error>> {
let txid = outpoint.txid.to_string();

match get_tx_out(&txid, outpoint.vout, None, settings) {
Ok(_) => Ok(true), // UTXO exists and is unspent
Err(e) => {
if e.to_string().contains("TxOut not found") {
Ok(false) // UTXO doesn't exist (already spent)
} else {
Err(format!("Error checking UTXO: {}", e).into())
}
}
}
}
11 changes: 11 additions & 0 deletions src/utils/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ pub struct Cli {
#[arg(short='l', long, value_delimiter = ',', default_value = "cHNidP8BAH0CAAAAAbAip9TqQ,cHNidP8BAH0CAAAAAbAip9TqQ")]
pub psbts: Vec<String>,

/// Vout
#[arg(short='o', long, default_value = "0")]
pub vout: u32,

/// Transaction confirmations
#[arg(short='c', long, default_value = "0")]
pub confirmations: u32,

#[command(subcommand)]
pub action: Action,
}
Expand All @@ -109,7 +117,9 @@ pub enum Action {
MineBlocks,
ListUnspent,
GetTx,
GetTxOut,
SignTx,
DecodeRawTx,
BroadcastTx,
SendBtc,
CreatePsbt,
Expand All @@ -119,6 +129,7 @@ pub enum Action {
CombinePsbts,
FinalizePsbt,
FinalizePsbtAndBroadcast,
VerifySignedTx,
InscribeOrd
}

Expand Down

0 comments on commit 402bc16

Please sign in to comment.