Skip to main content

csv_adapter_bitcoin/
spv.rs

1//! SPV (Simplified Payment Verification) for Bitcoin
2
3use bitcoin::{hashes::Hash as BitcoinHash, Txid};
4use sha2::{Digest, Sha256};
5use std::collections::HashMap;
6
7/// Block header entry
8#[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
17/// SPV verifier
18pub 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
115/// Standalone merkle proof verification function
116pub 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}