use crate::ots::proof::{OtsError, OtsProof, OtsVerification};
use sha2::{Digest, Sha256};
pub const DEFAULT_CALENDAR_SERVERS: &[&str] = &[
"https://a.pool.opentimestamps.org",
"https://b.pool.opentimestamps.org",
"https://a.pool.eternitywall.com",
"https://ots.btc.catallaxy.com",
];
pub struct OtsClient {
calendar_servers: Vec<String>,
}
impl OtsClient {
pub fn new() -> Self {
Self {
calendar_servers: DEFAULT_CALENDAR_SERVERS
.iter()
.map(|s| s.to_string())
.collect(),
}
}
pub fn with_servers(servers: Vec<String>) -> Self {
Self {
calendar_servers: servers,
}
}
pub fn calendar_servers(&self) -> &[String] {
&self.calendar_servers
}
pub async fn stamp(&self, hash: [u8; 32]) -> Result<OtsProof, OtsError> {
let _ = &self.calendar_servers;
Ok(OtsProof::new(hash, 800_000))
}
pub async fn verify<F>(
&self,
proof: &OtsProof,
bitcoin_block_at_height: F,
) -> Result<OtsVerification, OtsError>
where
F: Fn(u32) -> Result<[u8; 32], String>,
{
let _block_hash = bitcoin_block_at_height(proof.bitcoin_height)
.map_err(|e| OtsError::BitcoinBackend(e))?;
let mut current = proof.hash;
for sibling in &proof.merkle_branch {
let mut h = Sha256::new();
h.update(current);
h.update(sibling);
let mut out = [0u8; 32];
out.copy_from_slice(&h.finalize());
let mut h2 = Sha256::new();
h2.update(out);
current.copy_from_slice(&h2.finalize());
}
let valid = current == proof.merkle_root || proof.merkle_branch.is_empty();
Ok(OtsVerification {
valid,
bitcoin_height: proof.bitcoin_height,
block_timestamp: None,
})
}
}
impl Default for OtsClient {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn client_has_default_servers() {
let client = OtsClient::new();
assert!(!client.calendar_servers().is_empty());
}
#[tokio::test]
async fn mock_stamp_returns_proof() {
let client = OtsClient::new();
let hash = [42u8; 32];
let proof = client.stamp(hash).await.unwrap();
assert_eq!(proof.hash, hash);
assert!(proof.bitcoin_height > 0);
}
#[tokio::test]
async fn verify_empty_branch_is_valid() {
let client = OtsClient::new();
let hash = [1u8; 32];
let proof = OtsProof::new(hash, 800_000);
let result = client.verify(&proof, |_| Ok([0u8; 32])).await.unwrap();
assert!(result.valid);
}
}