1use std::fmt;
2
3use bsv_rs::transaction::MerklePath;
4
5use crate::{Hash, Height, TxId};
6
7#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct Header {
10 pub hash: Hash,
12 pub merkle_root: Hash,
14}
15
16#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct HeaderError(pub String);
19
20impl fmt::Display for HeaderError {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 f.write_str(&self.0)
23 }
24}
25impl std::error::Error for HeaderError {}
26
27#[derive(Clone, Debug, PartialEq, Eq)]
29pub enum CheckError {
30 InvalidProof(String),
32 Headers(HeaderError),
34 Unavailable(Height),
36 RootMismatch(Height),
38 TxidMismatch,
40}
41impl fmt::Display for CheckError {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 write!(f, "{self:?}")
44 }
45}
46impl std::error::Error for CheckError {}
47
48#[derive(Clone, Debug)]
50pub struct Proof {
51 txid: TxId,
52 path: MerklePath,
53}
54impl PartialEq for Proof {
55 fn eq(&self, other: &Self) -> bool {
56 self.txid == other.txid
57 && self.path.block_height == other.path.block_height
58 && self.path.path == other.path.path
59 }
60}
61impl Eq for Proof {}
62
63impl Proof {
64 pub fn new(txid: impl Into<TxId>, path: MerklePath) -> Result<Self, CheckError> {
66 let txid = txid.into().to_ascii_lowercase();
67 if txid.len() != 64 || !txid.bytes().all(|c| c.is_ascii_hexdigit()) {
68 return Err(CheckError::InvalidProof(
69 "txid must be 32 bytes of hex".into(),
70 ));
71 }
72 let path = MerklePath::new_unchecked(path.block_height, path.path)
73 .map_err(|e| CheckError::InvalidProof(e.to_string()))?;
74 path.compute_root(Some(&txid))
75 .map_err(|e| CheckError::InvalidProof(e.to_string()))?;
76 Ok(Self { txid, path })
77 }
78 pub fn txid(&self) -> &str {
80 &self.txid
81 }
82 pub fn height(&self) -> Height {
84 self.path.block_height
85 }
86 pub fn path(&self) -> &MerklePath {
88 &self.path
89 }
90 pub fn root(&self) -> Result<Hash, CheckError> {
92 self.path
93 .compute_root(Some(&self.txid))
94 .map_err(|e| CheckError::InvalidProof(e.to_string()))
95 }
96}
97
98#[derive(Clone, Debug, PartialEq, Eq)]
115pub struct CheckedProof {
116 proof: Proof,
117 header: Header,
118 root: Hash,
119 depth: u64,
120}
121impl CheckedProof {
122 pub fn proof(&self) -> &Proof {
124 &self.proof
125 }
126 pub fn header(&self) -> &Header {
128 &self.header
129 }
130 pub fn root(&self) -> &str {
132 &self.root
133 }
134 pub fn height(&self) -> Height {
136 self.proof.height()
137 }
138 pub fn depth(&self) -> u64 {
140 self.depth
141 }
142}
143
144pub trait Headers {
147 fn header_at(&self, height: Height) -> Result<Option<Header>, HeaderError>;
149 fn tip_height(&self) -> Result<Height, HeaderError>;
151 fn check(&self, proof: Proof) -> Result<CheckedProof, CheckError> {
155 let root = proof.root()?;
156 let height = proof.height();
157 let header = self
158 .header_at(height)
159 .map_err(CheckError::Headers)?
160 .ok_or(CheckError::Unavailable(height))?;
161 let tip = self.tip_height().map_err(CheckError::Headers)?;
162 if tip < height {
163 return Err(CheckError::Unavailable(height));
164 }
165 if header.merkle_root != root {
166 return Err(CheckError::RootMismatch(height));
167 }
168 Ok(CheckedProof {
169 proof,
170 header,
171 root,
172 depth: u64::from(tip) - u64::from(height) + 1,
173 })
174 }
175}
176
177pub(crate) fn check_snapshot<H: Headers + ?Sized>(
178 headers: &H,
179 proof: Proof,
180) -> Result<CheckedProof, CheckError> {
181 struct Snapshot<'a, H: ?Sized>(&'a H);
182
183 impl<H: Headers + ?Sized> Headers for Snapshot<'_, H> {
184 fn header_at(&self, height: Height) -> Result<Option<Header>, HeaderError> {
185 self.0.header_at(height)
186 }
187
188 fn tip_height(&self) -> Result<Height, HeaderError> {
189 self.0.tip_height()
190 }
191 }
192
193 Headers::check(&Snapshot(headers), proof)
194}