csv_adapter_bitcoin/
spv.rs1use bitcoin::{hashes::Hash as BitcoinHash, Txid};
4use sha2::{Digest, Sha256};
5use std::collections::HashMap;
6
7#[derive(Clone, Debug)]
9pub struct BlockHeaderEntry {
10 pub hash: [u8; 32],
11 pub height: u64,
12 pub prev_hash: [u8; 32],
13 pub merkle_root: [u8; 32],
14 pub confirmations: u64,
15}
16
17pub struct SpvVerifier {
19 headers: HashMap<[u8; 32], BlockHeaderEntry>,
20 chain_tip: Option<[u8; 32]>,
21 chain_tip_height: u64,
22}
23
24impl SpvVerifier {
25 pub fn new() -> Self {
26 Self {
27 headers: HashMap::new(),
28 chain_tip: None,
29 chain_tip_height: 0,
30 }
31 }
32
33 pub fn add_header(&mut self, entry: BlockHeaderEntry) {
34 if self.chain_tip.is_none() || entry.height > self.chain_tip_height {
35 self.chain_tip = Some(entry.hash);
36 self.chain_tip_height = entry.height;
37 }
38 self.headers.insert(entry.hash, entry);
39 }
40
41 pub fn verify_merkle_proof(
42 &self,
43 txid: &Txid,
44 merkle_root: &[u8; 32],
45 branch: &[[u8; 32]],
46 _index: u32,
47 ) -> bool {
48 let txid_bytes: [u8; 32] = txid.to_raw_hash().to_byte_array();
49 if branch.is_empty() {
50 return txid_bytes == *merkle_root;
51 }
52
53 let mut current = txid_bytes;
54 for sibling in branch {
55 let mut hasher = Sha256::new();
56 hasher.update(current);
57 hasher.update(sibling);
58 let first = hasher.finalize_reset();
59 let mut hasher2 = Sha256::new();
60 hasher2.update(first);
61 current = hasher2.finalize().into();
62 }
63 current == *merkle_root
64 }
65
66 pub fn is_block_in_chain(&self, block_hash: &[u8; 32]) -> bool {
67 self.headers.contains_key(block_hash)
68 }
69
70 pub fn get_confirmations(&self, block_hash: &[u8; 32]) -> Option<u64> {
71 self.headers.get(block_hash).map(|h| h.confirmations)
72 }
73
74 pub fn chain_tip_height(&self) -> u64 {
75 self.chain_tip_height
76 }
77
78 pub fn invalidate_block(&mut self, block_hash: &[u8; 32]) -> Vec<[u8; 32]> {
79 let mut invalidated = Vec::new();
80 if let Some(height) = self.headers.get(block_hash).map(|h| h.height) {
81 let hashes: Vec<_> = self
82 .headers
83 .iter()
84 .filter(|(_, h)| h.height >= height)
85 .map(|(hash, _)| *hash)
86 .collect();
87 for hash in &hashes {
88 self.headers.remove(hash);
89 invalidated.push(*hash);
90 }
91 if let Some(tip) = self.chain_tip {
92 if hashes.contains(&tip) {
93 let new_tip = self
94 .headers
95 .values()
96 .max_by_key(|h| h.height)
97 .map(|h| h.hash);
98 self.chain_tip = new_tip;
99 self.chain_tip_height = self
100 .chain_tip_height
101 .saturating_sub(invalidated.len() as u64);
102 }
103 }
104 }
105 invalidated
106 }
107}
108
109impl Default for SpvVerifier {
110 fn default() -> Self {
111 Self::new()
112 }
113}
114
115pub fn verify_merkle_proof(txid: &Txid, merkle_root: &[u8; 32], branch: &[[u8; 32]]) -> bool {
117 let spv = SpvVerifier::new();
118 spv.verify_merkle_proof(txid, merkle_root, branch, 0)
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use bitcoin::hashes::Hash as BitcoinHash;
125
126 fn test_hash(n: u8) -> [u8; 32] {
127 let mut h = [0u8; 32];
128 h[0] = n;
129 h
130 }
131
132 #[test]
133 fn test_spv_empty() {
134 let spv = SpvVerifier::new();
135 assert_eq!(spv.chain_tip_height(), 0);
136 }
137
138 #[test]
139 fn test_add_header() {
140 let mut spv = SpvVerifier::new();
141 spv.add_header(BlockHeaderEntry {
142 hash: test_hash(1),
143 height: 100,
144 prev_hash: test_hash(0),
145 merkle_root: test_hash(2),
146 confirmations: 1,
147 });
148 assert_eq!(spv.chain_tip_height(), 100);
149 assert!(spv.is_block_in_chain(&test_hash(1)));
150 }
151
152 #[test]
153 fn test_invalidate_block() {
154 let mut spv = SpvVerifier::new();
155 let h1 = test_hash(1);
156 let h2 = test_hash(2);
157 spv.add_header(BlockHeaderEntry {
158 hash: h1,
159 height: 100,
160 prev_hash: test_hash(0),
161 merkle_root: test_hash(3),
162 confirmations: 2,
163 });
164 spv.add_header(BlockHeaderEntry {
165 hash: h2,
166 height: 101,
167 prev_hash: h1,
168 merkle_root: test_hash(3),
169 confirmations: 1,
170 });
171
172 let invalidated = spv.invalidate_block(&h1);
173 assert_eq!(invalidated.len(), 2);
174 assert!(!spv.is_block_in_chain(&h1));
175 }
176
177 #[test]
178 fn test_merkle_proof_single_tx() {
179 let spv = SpvVerifier::new();
180 let txid =
181 Txid::from_raw_hash(bitcoin::hashes::sha256d::Hash::from_slice(&[1u8; 32]).unwrap());
182 let root = [1u8; 32];
183 assert!(spv.verify_merkle_proof(&txid, &root, &[], 0));
184 }
185
186 #[test]
187 fn test_merkle_proof_wrong_root() {
188 let spv = SpvVerifier::new();
189 let txid =
190 Txid::from_raw_hash(bitcoin::hashes::sha256d::Hash::from_slice(&[1u8; 32]).unwrap());
191 let root = [2u8; 32];
192 assert!(!spv.verify_merkle_proof(&txid, &root, &[], 0));
193 }
194}