use std::error::Error;
use bitcoin::hashes::{sha256d, Hash};
use bitcoin::{BlockHeader, Transaction, TxMerkleNode, Txid};
use thiserror::Error;
use bitcoin::merkle::PartialMerkleTree;
use bitcoin::consensus::{Decodable, Encodable};
use std::io::Cursor;
use crate::bitcoin::error::{BitcoinError, BitcoinResult};
use crate::security::constant_time;
#[derive(Debug, Error)]
pub enum SpvError {
#[error("Invalid merkle proof: {0}")]
InvalidMerkleProof(String),
#[error("Invalid block header: {0}")]
InvalidBlockHeader(String),
#[error("Invalid transaction: {0}")]
InvalidTransaction(String),
#[error("Bitcoin hash error: {0}")]
HashError(String),
#[error("Deserialization error: {0}")]
DeserializationError(String),
#[error("Proof verification error: {0}")]
VerificationError(String),
#[error("Other error: {0}")]
Other(String),
#[error("Merkle root mismatch")]
MerkleRootMismatch,
#[error("Transaction not found in merkle proof")]
MissingTransaction,
}
#[derive(Debug, Clone)]
pub struct SpvProof {
pub tx_id: Txid,
pub tx_data: Option<Transaction>,
pub block_header: BlockHeader,
pub merkle_proof: PartialMerkleTree,
pub tx_index: usize,
}
impl SpvProof {
pub fn new(
tx_id: Txid,
tx_data: Option<Transaction>,
block_header: BlockHeader,
merkle_proof: PartialMerkleTree,
tx_index: usize,
) -> Self {
Self {
tx_id,
tx_data,
block_header,
merkle_proof,
tx_index,
}
}
pub fn from_raw(
tx_id: &[u8],
tx_data: Option<&[u8]>,
block_header: &[u8],
merkle_proof: &[u8],
tx_index: usize,
) -> Result<Self, SpvError> {
let tx_id = Txid::from_slice(tx_id)
.map_err(|e| SpvError::InvalidTransaction(format!("Invalid txid: {}", e)))?;
let tx_data = if let Some(data) = tx_data {
let tx = bitcoin::consensus::deserialize(data)
.map_err(|e| SpvError::DeserializationError(format!("Invalid transaction data: {}", e)))?;
Some(tx)
} else {
None
};
let block_header = bitcoin::consensus::deserialize(block_header)
.map_err(|e| SpvError::InvalidBlockHeader(format!("Invalid block header: {}", e)))?;
let merkle_proof = PartialMerkleTree::consensus_decode(Cursor::new(merkle_proof))
.map_err(|e| SpvError::InvalidMerkleProof(format!("Invalid merkle proof: {}", e)))?;
Ok(Self {
tx_id,
tx_data,
block_header,
merkle_proof,
tx_index,
})
}
pub fn verify(&self) -> Result<bool, SpvError> {
if let Some(tx) = &self.tx_data {
let actual_txid = tx.txid();
if actual_txid != self.tx_id {
return Err(SpvError::VerificationError(
"Transaction does not match the provided txid".to_string()
));
}
}
let merkle_root = verify_merkle_proof(
&self.tx_id.to_byte_array(),
&self.merkle_proof.merkle_root().to_byte_array(),
&self.merkle_proof.consensus_encode(&mut Vec::new())?
)?;
let header_merkle_root = self.block_header.merkle_root.to_byte_array();
let computed_merkle_root = merkle_root.to_byte_array();
let equal = constant_time::constant_time_eq(&header_merkle_root, &computed_merkle_root);
Ok(equal)
}
pub fn serialize(&self) -> Result<Vec<u8>, SpvError> {
let mut buffer = Vec::new();
self.block_header.consensus_encode(&mut buffer)?;
self.tx_id.consensus_encode(&mut buffer)?;
self.merkle_proof.consensus_encode(&mut buffer)?;
(self.tx_index as u32).consensus_encode(&mut buffer)?;
Ok(buffer)
}
pub fn deserialize(data: &[u8]) -> Result<Self, SpvError> {
let mut cursor = Cursor::new(data);
let block_header = BlockHeader::consensus_decode(&mut cursor)?;
let tx_id = Txid::consensus_decode(&mut cursor)?;
let merkle_proof = PartialMerkleTree::consensus_decode(&mut cursor)?;
let tx_index = u32::consensus_decode(&mut cursor)? as usize;
Ok(Self {
block_header,
tx_id,
merkle_proof,
tx_index,
})
}
}
pub fn verify_merkle_proof(
tx_id: &[u8],
merkle_root: &[u8],
proof: &[u8],
) -> Result<TxMerkleNode, SpvError> {
let tx_merkle_node = match TxMerkleNode::from_slice(tx_id) {
Ok(node) => node,
Err(_) => return Err(SpvError::Other("Invalid transaction hash".to_string())),
};
let partial_merkle_tree = match PartialMerkleTree::consensus_decode(Cursor::new(proof)) {
Ok(tree) => tree,
Err(_) => return Err(SpvError::InvalidMerkleProof("Failed to deserialize merkle proof".to_string())),
};
let mut matched_txids = Vec::new();
let mut indices = Vec::new();
if !partial_merkle_tree.extract_matches(&mut matched_txids, &mut indices) {
return Err(SpvError::InvalidMerkleProof("Failed to extract matches from merkle proof".to_string()));
}
if !matched_txids.contains(&tx_merkle_node) {
return Err(SpvError::MissingTransaction);
}
let expected_root = match TxMerkleNode::from_slice(merkle_root) {
Ok(node) => node,
Err(_) => return Err(SpvError::Other("Invalid merkle root".to_string())),
};
if partial_merkle_tree.merkle_root() != expected_root {
return Err(SpvError::MerkleRootMismatch);
}
Ok(tx_merkle_node)
}
pub fn verify_tx_inclusion(
tx_id_hex: &str,
block_header_hex: &str,
merkle_proof_hex: &[&str],
tx_index: u32,
) -> Result<bool, SpvError> {
let tx_id = hex::decode(tx_id_hex)
.map_err(|e| SpvError::InvalidTransaction(format!("Invalid txid hex: {}", e)))?;
let block_header = hex::decode(block_header_hex)
.map_err(|e| SpvError::InvalidBlockHeader(format!("Invalid block header hex: {}", e)))?;
let merkle_proof = merkle_proof_hex.iter()
.map(|h| hex::decode(h))
.collect::<Result<Vec<_>, _>>()
.map_err(|e| SpvError::InvalidMerkleProof(format!("Invalid merkle path hex: {}", e)))?;
let proof = SpvProof::from_raw(&tx_id, None, BlockHeader::from_slice(&block_header)?, PartialMerkleTree::from_txids(&[Txid::from_slice(&tx_id)?], &[Txid::from_slice(&tx_id)?]), tx_index as usize)?;
proof.verify()
}
pub fn verify_bitcoin_payment(
tx_id_hex: &str,
block_header_hex: &str,
merkle_proof_hex: &[&str],
tx_index: u32,
_confirmations_required: u64,
) -> Result<bool, SpvError> {
verify_tx_inclusion(tx_id_hex, block_header_hex, merkle_proof_hex, tx_index)
}
#[cfg(test)]
mod tests {
use super::*;
use bitcoin::hashes::hex::FromHex;
#[test]
fn test_compute_merkle_parent() {
let left_hex = "b67e5c13dd78c212e64e2fa8d153c6f6a5cc741a1ec9c9fb3045f9854c881ae4";
let right_hex = "1b4741e858a7b7c0a851a35c43858bc8902c0a91b5bd7043b9a27e8b00e2a8e2";
let expected_parent_hex = "eb42a05772f296e9fe8a7f0d8a7c9abad734cc7dd31799a2b12a728a5d4ad891";
let left = TxMerkleNode::from_hex(left_hex)?;
let right = TxMerkleNode::from_hex(right_hex)?;
let expected_parent = TxMerkleNode::from_hex(expected_parent_hex)?;
let computed_parent = verify_merkle_proof(&left.to_byte_array(), &right.to_byte_array(), &[left.to_byte_array(), right.to_byte_array()])?;
assert_eq!(computed_parent, expected_parent);
}
#[test]
fn test_verify_merkle_proof() {
let tx_id_hex = "b67e5c13dd78c212e64e2fa8d153c6f6a5cc741a1ec9c9fb3045f9854c881ae4";
let tx_id = Txid::from_hex(tx_id_hex)?;
let merkle_path_hex = [
"1b4741e858a7b7c0a851a35c43858bc8902c0a91b5bd7043b9a27e8b00e2a8e2",
"9d28bd159e5ec3c21e1b305454231bd10033a4dd324b5d9c0bb29c60d4d7b4f8",
];
let merkle_path = merkle_path_hex.iter()
.map(|h| TxMerkleNode::from_hex(h)?)
.collect::<Vec<_>>();
let expected_root_hex = "eb98e9a0a41c33a68f53cf547ba78f349c6522f2c41ccec2934e3b324d0a67e2";
let expected_root = TxMerkleNode::from_hex(expected_root_hex)?;
let computed_root = verify_merkle_proof(&merkle_path[0].to_byte_array(), &merkle_path[1].to_byte_array(), &merkle_path.iter().map(|node| node.to_byte_array()).collect::<Vec<_>>())?;
assert_eq!(computed_root, expected_root);
}
#[test]
fn test_spv_proof_verification() {
let tx_id_hex = "b67e5c13dd78c212e64e2fa8d153c6f6a5cc741a1ec9c9fb3045f9854c881ae4";
let tx_id = Txid::from_hex(tx_id_hex)?;
let merkle_path_hex = [
"1b4741e858a7b7c0a851a35c43858bc8902c0a91b5bd7043b9a27e8b00e2a8e2",
"9d28bd159e5ec3c21e1b305454231bd10033a4dd324b5d9c0bb29c60d4d7b4f8",
];
let merkle_path = merkle_path_hex.iter()
.map(|h| TxMerkleNode::from_hex(h)?)
.collect::<Vec<_>>();
let root_hex = "eb98e9a0a41c33a68f53cf547ba78f349c6522f2c41ccec2934e3b324d0a67e2";
let root = TxMerkleNode::from_hex(root_hex)?;
let mut header = BlockHeader::default();
header.merkle_root = root;
let proof = SpvProof::new(tx_id, None, header, PartialMerkleTree::from_txids(&[tx_id], &[tx_id]), 0);
let result = proof.verify()?;
assert!(result);
}
}