1extern crate bitcoincore_rpc;
4extern crate camino;
5extern crate chrono;
6extern crate electrum_client;
7extern crate env_logger;
8extern crate log;
9extern crate ots;
10extern crate rand;
11extern crate reqwest;
12extern crate rs_merkle;
13extern crate thiserror;
14
15pub mod calendar;
16pub mod error;
17pub mod extensions;
18
19use bitcoincore_rpc::RpcApi;
20use calendar::Calendar;
21use error::Error;
22use extensions::{StepExtension, TimestampExtension};
23
24use chrono::DateTime;
25use electrum_client::bitcoin::hashes::Hash;
26use electrum_client::{Client, ElectrumApi};
27use log::{debug, error, info};
28use ots::hex::Hexed;
29use ots::ser::DigestType;
30use ots::{
31 attestation::Attestation,
32 op::Op,
33 timestamp::{Step, StepData},
34 DetachedTimestampFile, Timestamp,
35};
36use rs_merkle::{algorithms::Sha256, MerkleTree};
37use std::convert::TryInto;
38use std::time::Duration;
39
40pub fn info(ots: DetachedTimestampFile) -> Result<String, Error> {
41 Ok(ots.to_string())
42}
43fn verify_against_blockheader(
45 digest: [u8; 32],
46 block_header: electrum_client::bitcoin::block::Header,
47) -> Result<u32, Error> {
48 if digest != block_header.merkle_root.to_byte_array() {
49 return Err(Error::Generic("Merkle root mismatch".to_string()));
50 }
51 Ok(block_header.time)
52}
53
54fn timestamp_to_date(timestamp: i64) -> String {
55 let from = DateTime::from_timestamp(timestamp, 0).unwrap();
56 let date = from.naive_local();
57 date.format("%Y-%m-%d").to_string()
58}
59
60pub struct BitcoinAttestationResult {
61 height: usize,
62 time: u32,
63}
64impl std::fmt::Display for BitcoinAttestationResult {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 write!(
67 f,
68 "Bitcoin block {} attests existence as of {}",
69 self.height,
70 timestamp_to_date(self.time as i64)
71 )
72 }
73}
74
75pub fn verify(
76 ots: DetachedTimestampFile,
77 bitcoin_client: Option<bitcoincore_rpc::Client>,
78) -> Result<BitcoinAttestationResult, Error> {
79 let client = Client::new("tcp://electrum.blockstream.info:50001").unwrap();
80
81 for attestation in ots.timestamp.all_attestations() {
82 match attestation.1 {
83 Attestation::Bitcoin { height } => {
84 let block_header = match bitcoin_client {
85 Some(client) => {
86 let block_hash = client.get_block_hash(height as u64).unwrap();
87 debug!("Attestation block hash: {:?}", block_hash);
88 client.get_block_header(&block_hash).unwrap()
89 }
90 None => {
91 let block_header = client.block_header(height).unwrap();
92 debug!("Attestation block hash: {:?}", block_header.block_hash());
93 block_header
94 }
95 };
96 let time =
97 verify_against_blockheader(attestation.0.try_into().unwrap(), block_header)?;
98 let result = BitcoinAttestationResult { height, time };
99 info!("Success! {}", result);
100 return Ok(BitcoinAttestationResult { height, time });
101 }
102 Attestation::Pending { uri } => {
103 debug!("Ignoring Pending Attestation at {:?}", uri);
104 }
105 Attestation::Unknown { tag: _, data: _ } => {
106 debug!("Ignoring Unknown Attestation");
107 }
108 };
109 }
110 Err(Error::Generic("No bitcoin attestion found".to_string()))
111}
112
113pub fn upgrade(
114 ots: &mut DetachedTimestampFile,
115 calendar_urls: Option<Vec<String>>,
116) -> Result<(), Error> {
117 for attestation in ots.timestamp.all_attestations() {
118 match attestation.1 {
119 Attestation::Bitcoin { height: _ } => {}
120 Attestation::Unknown { tag: _, data: _ } => {}
121 Attestation::Pending { ref uri } => {
122 if calendar_urls
123 .as_ref()
124 .is_some_and(|urls| !urls.contains(uri))
125 {
126 error!("No valid calendar found");
127 continue;
128 }
129 info!("Upgrading to remote calendar {}", uri.to_string());
130 let upgraded = upgrade_timestamp(attestation.0, uri.to_string(), None)?;
131 ots.timestamp.merge(upgraded);
132 }
133 };
134 }
135 Ok(())
136}
137
138fn upgrade_timestamp(
139 commitment: Vec<u8>,
140 calendar_url: String,
141 timeout: Option<Duration>,
142) -> Result<Timestamp, Error> {
143 let res = Calendar {
144 url: calendar_url,
145 timeout: timeout,
146 }
147 .get_timestamp(commitment.clone())
148 .map_err(|err| Error::NetworkError(err))?;
149 let mut deser = ots::ser::Deserializer::new(res);
150 Timestamp::deserialize(&mut deser, commitment).map_err(|err| Error::InvalidOts(err))
151}
152
153fn timestamp_from_merkle(
154 merkle_tree: &MerkleTree<Sha256>,
155 leave: [u8; 32],
156) -> Result<Timestamp, Error> {
157 let index = merkle_tree
158 .leaves()
159 .unwrap()
160 .iter()
161 .position(|l| *l == leave)
162 .unwrap();
163 let proofs = merkle_tree.proof(&[index]);
164 let mut step = Step {
168 data: StepData::Op(Op::Hexlify),
169 output: vec![],
170 next: vec![],
171 };
172 let mut digest = leave.to_vec();
173 for proof in proofs.proof_hashes().iter().enumerate() {
174 let level = proof.0 as u32;
175 let odd = (index as i32 / 2_i32.pow(level)) % 2 == 1;
176 let op = if odd {
177 Op::Prepend(proof.1.to_vec())
178 } else {
179 Op::Append(proof.1.to_vec())
180 };
181 let step_pend = Step {
182 data: StepData::Op(op.clone()),
183 output: op.execute(&digest),
184 next: vec![],
185 };
186 let op = Op::Sha256;
187 digest = op.execute(&step_pend.clone().output);
188 let step_sha256 = Step {
189 data: StepData::Op(op.clone()),
190 output: op.execute(&step_pend.clone().output),
191 next: vec![],
192 };
193 if level == 0 {
194 step = step_pend;
195 } else {
196 step.cat(step_pend);
197 }
198 step.cat(step_sha256);
199 }
200 Ok(Timestamp {
201 start_digest: leave.to_vec(),
202 first_step: step,
203 })
204}
205
206pub fn stamps(
207 digests: Vec<Vec<u8>>,
208 digest_type: DigestType,
209 calendar_urls: Option<Vec<String>>,
210 timeout: Option<Duration>,
211) -> Result<Vec<DetachedTimestampFile>, Error> {
212 let mut merkle_roots: Vec<[u8; 32]> = vec![];
213 let mut file_timestamps: Vec<ots::DetachedTimestampFile> = vec![];
214 for digest in digests {
215 let random: Vec<u8> = (0..16).map(|_| rand::random::<u8>()).collect();
216 let nonce_op = ots::op::Op::Append(random);
217 let nonce_output_digest = nonce_op.execute(&digest);
218 let hash_op = ots::op::Op::Sha256;
219 let hash_output_digest = hash_op.execute(&nonce_output_digest);
220 let file_timestamp = ots::DetachedTimestampFile {
221 digest_type: digest_type,
222 timestamp: ots::Timestamp {
223 start_digest: digest,
224 first_step: Step {
225 data: StepData::Op(nonce_op),
226 output: nonce_output_digest,
227 next: vec![Step {
228 data: StepData::Op(hash_op),
229 output: hash_output_digest.clone(),
230 next: vec![],
231 }],
232 },
233 },
234 };
235 file_timestamps.push(file_timestamp.clone());
237 merkle_roots.push(hash_output_digest.try_into().unwrap());
238 }
239 debug!("file_timestamps {}", file_timestamps[0]);
240 debug!("merkle_roots {:?}", merkle_roots.len());
241 for root in merkle_roots.iter() {
242 debug!("{:?}", Hexed(root));
243 }
244 let merkle_tree = MerkleTree::<Sha256>::from_leaves(&merkle_roots);
245 let merkle_tip = merkle_tree.root().unwrap();
246
247 if file_timestamps.len() > 1 {
248 for ft in file_timestamps.iter_mut().enumerate() {
249 if let Ok(timestamp) = timestamp_from_merkle(&merkle_tree, merkle_roots[ft.0]) {
250 ft.1.timestamp.merge(timestamp);
251 }
252 }
253 }
254 let calendar_urls = match calendar_urls {
255 Some(urls) => urls,
256 None => vec![
257 calendar::APOOL.to_string(),
258 calendar::BPOOL.to_string(),
259 calendar::FINNEY.to_string(),
260 ],
261 };
262
263 let mut calendar_timestamps = vec![];
264 for calendar in calendar_urls {
265 info!("Submitting to remote calendar {}", calendar);
266 let calendar_timestamp = create_timestamp(merkle_tip.to_vec(), calendar.clone(), timeout);
267 match calendar_timestamp {
268 Ok(timestamp) => calendar_timestamps.push(timestamp),
269 Err(e) => error!("Ignoring remote calendar {}: {}", calendar, e.to_string()),
270 }
271 }
272 if calendar_timestamps.is_empty() {
273 return Err(Error::Generic("No valid calendar found".to_string()));
274 }
275 let timestamp: Timestamp;
276 if calendar_timestamps.len() == 1 {
277 timestamp = calendar_timestamps.first().unwrap().clone();
278 } else {
279 let steps = calendar_timestamps
280 .iter()
281 .map(|x| x.first_step.clone())
282 .collect();
283 let fork = Step {
284 data: StepData::Fork,
285 output: merkle_tip.to_vec(),
286 next: steps,
287 };
288 timestamp = Timestamp {
289 start_digest: merkle_tip.to_vec(),
290 first_step: fork,
291 };
292 }
293 for ft in file_timestamps.iter_mut() {
294 ft.timestamp.merge(timestamp.clone());
295 }
296 Ok(file_timestamps)
297}
298
299fn create_timestamp(
300 stamp: Vec<u8>,
301 calendar_url: String,
302 timeout: Option<Duration>,
303) -> Result<Timestamp, Error> {
304 let res = Calendar {
305 url: calendar_url,
306 timeout: timeout,
307 }
308 .submit_calendar(stamp.clone())
309 .map_err(|err| Error::NetworkError(err))?;
310 let mut deser = ots::ser::Deserializer::new(res);
311 Timestamp::deserialize(&mut deser, stamp.to_vec()).map_err(|err| Error::InvalidOts(err))
312}