jitash_bdk/wallet/
verify.rs1use std::collections::HashMap;
15use std::fmt;
16
17use bitcoin::consensus::serialize;
18use bitcoin::{OutPoint, Transaction, Txid};
19
20use crate::blockchain::GetTx;
21use crate::database::Database;
22use crate::error::Error;
23
24pub fn verify_tx<D: Database, B: GetTx>(
35 tx: &Transaction,
36 database: &D,
37 blockchain: &B,
38) -> Result<(), VerifyError> {
39 log::debug!("Verifying {}", tx.txid());
40
41 let serialized_tx = serialize(tx);
42 let mut tx_cache = HashMap::<_, Transaction>::new();
43
44 for (index, input) in tx.input.iter().enumerate() {
45 let prev_tx = if let Some(prev_tx) = tx_cache.get(&input.previous_output.txid) {
46 prev_tx.clone()
47 } else if let Some(prev_tx) = database.get_raw_tx(&input.previous_output.txid)? {
48 prev_tx
49 } else if let Some(prev_tx) = blockchain.get_tx(&input.previous_output.txid)? {
50 prev_tx
51 } else {
52 return Err(VerifyError::MissingInputTx(input.previous_output.txid));
53 };
54
55 let spent_output = prev_tx
56 .output
57 .get(input.previous_output.vout as usize)
58 .ok_or(VerifyError::InvalidInput(input.previous_output))?;
59
60 bitcoinconsensus::verify(
61 &spent_output.script_pubkey.to_bytes(),
62 spent_output.value,
63 &serialized_tx,
64 index,
65 )?;
66
67 tx_cache.insert(prev_tx.txid(), prev_tx);
70 }
71
72 Ok(())
73}
74
75#[derive(Debug)]
77pub enum VerifyError {
78 MissingInputTx(Txid),
80 InvalidInput(OutPoint),
82
83 Consensus(bitcoinconsensus::Error),
85
86 Global(Box<Error>),
90}
91
92impl fmt::Display for VerifyError {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 match self {
95 Self::MissingInputTx(txid) => write!(f, "The transaction being spent is not available in the database or the blockchain client: {}", txid),
96 Self::InvalidInput(outpoint) => write!(f, "The transaction being spent doesn't have the requested output: {}", outpoint),
97 Self::Consensus(err) => write!(f, "Consensus error: {:?}", err),
98 Self::Global(err) => write!(f, "Generic error: {}", err),
99 }
100 }
101}
102
103impl std::error::Error for VerifyError {}
104
105impl From<Error> for VerifyError {
106 fn from(other: Error) -> Self {
107 VerifyError::Global(Box::new(other))
108 }
109}
110impl_error!(bitcoinconsensus::Error, Consensus, VerifyError);
111
112#[cfg(test)]
113mod test {
114 use super::*;
115 use crate::database::{BatchOperations, MemoryDatabase};
116 use assert_matches::assert_matches;
117 use bitcoin::consensus::encode::deserialize;
118 use bitcoin::hashes::hex::FromHex;
119 use bitcoin::{Transaction, Txid};
120
121 struct DummyBlockchain;
122
123 impl GetTx for DummyBlockchain {
124 fn get_tx(&self, _txid: &Txid) -> Result<Option<Transaction>, Error> {
125 Ok(None)
126 }
127 }
128
129 #[test]
130 fn test_verify_fail_unsigned_tx() {
131 let prev_tx: Transaction = deserialize(&Vec::<u8>::from_hex("020000000101192dea5e66d444380e106f8e53acb171703f00d43fb6b3ae88ca5644bdb7e1000000006b48304502210098328d026ce138411f957966c1cf7f7597ccbb170f5d5655ee3e9f47b18f6999022017c3526fc9147830e1340e04934476a3d1521af5b4de4e98baf49ec4c072079e01210276f847f77ec8dd66d78affd3c318a0ed26d89dab33fa143333c207402fcec352feffffff023d0ac203000000001976a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988aca4b956050000000017a91494d5543c74a3ee98e0cf8e8caef5dc813a0f34b48768cb0700").unwrap()).unwrap();
133 let signed_tx: Transaction = deserialize(&Vec::<u8>::from_hex("02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700").unwrap()).unwrap();
135
136 let mut database = MemoryDatabase::new();
137 let blockchain = DummyBlockchain;
138
139 let mut unsigned_tx = signed_tx.clone();
140 for input in &mut unsigned_tx.input {
141 input.script_sig = Default::default();
142 input.witness = Default::default();
143 }
144
145 let result = verify_tx(&signed_tx, &database, &blockchain);
146 assert_matches!(result, Err(VerifyError::MissingInputTx(txid)) if txid == prev_tx.txid(),
147 "Error should be a `MissingInputTx` error"
148 );
149
150 database.set_raw_tx(&prev_tx).unwrap();
152
153 let result = verify_tx(&unsigned_tx, &database, &blockchain);
154 assert_matches!(
155 result,
156 Err(VerifyError::Consensus(_)),
157 "Error should be a `Consensus` error"
158 );
159
160 let result = verify_tx(&signed_tx, &database, &blockchain);
161 assert!(
162 result.is_ok(),
163 "Should work since the TX is correctly signed"
164 );
165 }
166}