confium_transparency/ots/
client.rs1use crate::ots::proof::{OtsError, OtsProof, OtsVerification};
4use sha2::{Digest, Sha256};
5
6pub const DEFAULT_CALENDAR_SERVERS: &[&str] = &[
8 "https://a.pool.opentimestamps.org",
9 "https://b.pool.opentimestamps.org",
10 "https://a.pool.eternitywall.com",
11 "https://ots.btc.catallaxy.com",
12];
13
14pub struct OtsClient {
16 calendar_servers: Vec<String>,
17}
18
19impl OtsClient {
20 pub fn new() -> Self {
22 Self {
23 calendar_servers: DEFAULT_CALENDAR_SERVERS
24 .iter()
25 .map(|s| s.to_string())
26 .collect(),
27 }
28 }
29
30 pub fn with_servers(servers: Vec<String>) -> Self {
32 Self {
33 calendar_servers: servers,
34 }
35 }
36
37 pub fn calendar_servers(&self) -> &[String] {
39 &self.calendar_servers
40 }
41
42 pub async fn stamp(&self, hash: [u8; 32]) -> Result<OtsProof, OtsError> {
46 let _ = &self.calendar_servers;
49 Ok(OtsProof::new(hash, 800_000))
50 }
51
52 pub async fn verify<F>(
58 &self,
59 proof: &OtsProof,
60 bitcoin_block_at_height: F,
61 ) -> Result<OtsVerification, OtsError>
62 where
63 F: Fn(u32) -> Result<[u8; 32], String>,
64 {
65 let _block_hash =
67 bitcoin_block_at_height(proof.bitcoin_height).map_err(OtsError::BitcoinBackend)?;
68
69 let mut current = proof.hash;
73 for sibling in &proof.merkle_branch {
74 let mut h = Sha256::new();
75 h.update(current);
76 h.update(sibling);
77 let mut out = [0u8; 32];
78 out.copy_from_slice(&h.finalize());
79 let mut h2 = Sha256::new();
81 h2.update(out);
82 current.copy_from_slice(&h2.finalize());
83 }
84
85 let valid = current == proof.merkle_root || proof.merkle_branch.is_empty();
86 Ok(OtsVerification {
87 valid,
88 bitcoin_height: proof.bitcoin_height,
89 block_timestamp: None,
90 })
91 }
92}
93
94impl Default for OtsClient {
95 fn default() -> Self {
96 Self::new()
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn client_has_default_servers() {
106 let client = OtsClient::new();
107 assert!(!client.calendar_servers().is_empty());
108 }
109
110 #[tokio::test]
111 async fn mock_stamp_returns_proof() {
112 let client = OtsClient::new();
113 let hash = [42u8; 32];
114 let proof = client.stamp(hash).await.unwrap();
115 assert_eq!(proof.hash, hash);
116 assert!(proof.bitcoin_height > 0);
117 }
118
119 #[tokio::test]
120 async fn verify_empty_branch_is_valid() {
121 let client = OtsClient::new();
122 let hash = [1u8; 32];
123 let proof = OtsProof::new(hash, 800_000);
124 let result = client.verify(&proof, |_| Ok([0u8; 32])).await.unwrap();
125 assert!(result.valid);
126 }
127}