use bitcoin::{
hashes::{hex::ToHex, Hash},
TxMerkleNode, Txid,
};
pub(crate) struct Proof {
proof: Vec<TxMerkleNode>,
position: usize,
}
impl Proof {
pub(crate) fn create(txids: &[Txid], position: usize) -> Self {
assert!(position < txids.len());
let mut offset = position;
let mut hashes: Vec<TxMerkleNode> = txids
.iter()
.map(|txid| TxMerkleNode::from_hash(txid.as_hash()))
.collect();
let mut proof = vec![];
while hashes.len() > 1 {
if hashes.len() % 2 != 0 {
let last = *hashes.last().unwrap();
hashes.push(last);
}
offset = if offset % 2 == 0 {
offset + 1
} else {
offset - 1
};
proof.push(hashes[offset]);
offset /= 2;
hashes = hashes
.chunks(2)
.map(|pair| {
let left = pair[0];
let right = pair[1];
let input = [&left[..], &right[..]].concat();
TxMerkleNode::hash(&input)
})
.collect()
}
Self { proof, position }
}
pub(crate) fn to_hex(&self) -> Vec<String> {
self.proof.iter().map(|node| node.to_hex()).collect()
}
pub(crate) fn position(&self) -> usize {
self.position
}
}
#[cfg(test)]
mod tests {
use bitcoin::{consensus::encode::deserialize, Block, Txid};
use std::path::Path;
use super::Proof;
#[test]
fn test_merkle() {
let proof = Proof::create(
&load_block_txids("000000003d0ecda04e04130209d3d17dd43c19ede60061a4a059a2315a6c9561"),
157,
);
assert_eq!(
proof.to_hex(),
vec![
"1975e29bfb3990e7d59cdb1059f8e0b0e5300f4908d4dd043d578bb267c9163b"
]
);
let proof = Proof::create(
&load_block_txids("0000000017189e6944eb114bd9538b7b469b1a88a33542244a7de726d7a0245d"),
6,
);
assert_eq!(
proof.to_hex(),
vec![
"0174ba0fe3ea5370af33fd9f02ba28a3d166d3726e6ac208e083f83d8cfa4df0"
]
);
}
fn load_block_txids(block_hash_hex: &str) -> Vec<Txid> {
let path = Path::new("src")
.join("tests")
.join("blocks")
.join(block_hash_hex);
let data = std::fs::read(path).unwrap();
let block: Block = deserialize(&data).unwrap();
block.txdata.iter().map(|tx| tx.txid()).collect()
}
}