1use bitcoin::util::psbt::serialize::Deserialize;
3use bitcoin::MerkleBlock;
4use bitcoin::Transaction;
5use ipfs_hasher::IpfsHasher;
6use serde_json::{json, Value};
7use sha2::{Digest, Sha256};
8use ssi::did::Document;
9use std::convert::TryInto;
10use std::marker::PhantomData;
11use trustchain_core::commitment::TimestampCommitment;
12use trustchain_core::commitment::{ChainedCommitment, CommitmentChain, CommitmentResult};
13use trustchain_core::commitment::{Commitment, CommitmentError};
14use trustchain_core::commitment::{DIDCommitment, TrivialCommitment};
15use trustchain_core::utils::{HasEndpoints, HasKeys};
16use trustchain_core::verifier::Timestamp;
17
18use crate::sidetree::CoreIndexFile;
19use crate::utils::tx_to_op_return_cid;
20use crate::utils::{decode_block_header, decode_ipfs_content, reverse_endianness};
21use crate::MERKLE_ROOT_KEY;
22use crate::TIMESTAMP_KEY;
23
24const CID_KEY: &str = "cid";
25const DELTAS_KEY: &str = "deltas";
26
27fn ipfs_hasher() -> fn(&[u8]) -> CommitmentResult<String> {
28 |x| Ok(IpfsHasher::default().compute(x))
29}
30
31fn ipfs_decode_candidate_data() -> fn(&[u8]) -> CommitmentResult<Value> {
32 |x| decode_ipfs_content(x, true).map_err(|_| CommitmentError::DataDecodingFailure)
33}
34
35fn block_header_hasher() -> fn(&[u8]) -> CommitmentResult<String> {
36 |x| {
38 let double_hash_hex = hex::encode(Sha256::digest(Sha256::digest(x)));
41 Ok(reverse_endianness(&double_hash_hex).unwrap())
43 }
44}
45
46fn block_header_decoder() -> fn(&[u8]) -> CommitmentResult<Value> {
47 |x| {
48 if x.len() != 80 {
49 return Err(CommitmentError::DataDecodingError(
50 "Error: Bitcoin block header must be 80 bytes.".to_string(),
51 ));
52 };
53 let decoded_header = decode_block_header(x.try_into().map_err(|err| {
54 CommitmentError::DataDecodingError(format!(
55 "Error: Bitcoin block header must be 80 bytes with error: {err}"
56 ))
57 })?);
58
59 match decoded_header {
60 Ok(x) => Ok(x),
61 Err(e) => Err(CommitmentError::DataDecodingError(format!(
62 "Error decoding Bitcoin block header: {}.",
63 e
64 ))),
65 }
66 }
67}
68
69pub struct Incomplete;
71pub struct Complete;
73
74pub struct IpfsIndexFileCommitment<T = Incomplete> {
76 candidate_data: Vec<u8>,
77 expected_data: Option<Value>,
78 _state: PhantomData<T>, }
80
81impl IpfsIndexFileCommitment<Incomplete> {
82 pub fn new(candidate_data: Vec<u8>) -> Self {
83 Self {
84 candidate_data,
85 expected_data: None,
86 _state: PhantomData::<Incomplete>,
87 }
88 }
89}
90
91impl IpfsIndexFileCommitment<Complete> {
92 pub fn new(candidate_data: Vec<u8>, expected_data: Value) -> Self {
93 Self {
94 candidate_data,
95 expected_data: Some(expected_data),
96 _state: PhantomData::<Complete>,
97 }
98 }
99}
100
101impl<T> TrivialCommitment for IpfsIndexFileCommitment<T> {
102 fn hasher(&self) -> fn(&[u8]) -> CommitmentResult<String> {
103 ipfs_hasher()
104 }
105
106 fn candidate_data(&self) -> &[u8] {
107 &self.candidate_data
108 }
109
110 fn decode_candidate_data(&self) -> fn(&[u8]) -> CommitmentResult<Value> {
111 ipfs_decode_candidate_data()
112 }
113
114 fn to_commitment(self: Box<Self>, expected_data: serde_json::Value) -> Box<dyn Commitment> {
115 Box::new(IpfsIndexFileCommitment::<Complete>::new(
116 self.candidate_data,
117 expected_data,
118 ))
119 }
120}
121
122impl Commitment for IpfsIndexFileCommitment<Complete> {
123 fn expected_data(&self) -> &serde_json::Value {
124 self.expected_data.as_ref().unwrap()
126 }
127}
128
129pub struct IpfsChunkFileCommitment<T = Incomplete> {
131 candidate_data: Vec<u8>,
132 delta_index: usize,
133 expected_data: Option<Value>,
134 _state: PhantomData<T>, }
136impl IpfsChunkFileCommitment<Incomplete> {
137 pub fn new(candidate_data: Vec<u8>, delta_index: usize) -> Self {
138 Self {
139 candidate_data,
140 delta_index,
141 expected_data: None,
142 _state: PhantomData::<Incomplete>,
143 }
144 }
145}
146impl IpfsChunkFileCommitment<Complete> {
147 pub fn new(candidate_data: Vec<u8>, delta_index: usize, expected_data: Value) -> Self {
148 Self {
149 candidate_data,
150 delta_index,
151 expected_data: Some(expected_data),
152 _state: PhantomData::<Complete>,
153 }
154 }
155}
156
157impl<T> TrivialCommitment for IpfsChunkFileCommitment<T> {
158 fn hasher(&self) -> fn(&[u8]) -> CommitmentResult<String> {
159 ipfs_hasher()
160 }
161
162 fn candidate_data(&self) -> &[u8] {
163 &self.candidate_data
164 }
165
166 fn filter(&self) -> Option<Box<dyn Fn(&serde_json::Value) -> CommitmentResult<Value>>> {
167 let delta_index = self.delta_index;
170 Some(Box::new(move |value| {
171 if let Value::Object(map) = value {
174 match map.get(DELTAS_KEY) {
175 Some(Value::Array(deltas)) => Ok(deltas.get(delta_index).unwrap().clone()),
176 _ => Err(CommitmentError::DataDecodingFailure),
177 }
178 } else {
179 Err(CommitmentError::DataDecodingFailure)
180 }
181 }))
182 }
183 fn decode_candidate_data(&self) -> fn(&[u8]) -> CommitmentResult<Value> {
184 ipfs_decode_candidate_data()
185 }
186 fn to_commitment(self: Box<Self>, expected_data: serde_json::Value) -> Box<dyn Commitment> {
187 Box::new(IpfsChunkFileCommitment::<Complete>::new(
188 self.candidate_data,
189 self.delta_index,
190 expected_data,
191 ))
192 }
193}
194
195impl Commitment for IpfsChunkFileCommitment<Complete> {
196 fn expected_data(&self) -> &serde_json::Value {
197 self.expected_data.as_ref().unwrap()
199 }
200}
201
202pub struct TxCommitment<T = Incomplete> {
204 candidate_data: Vec<u8>,
205 expected_data: Option<Value>,
206 _state: PhantomData<T>, }
208
209impl TxCommitment<Incomplete> {
210 pub fn new(candidate_data: Vec<u8>) -> Self {
211 Self {
212 candidate_data,
213 expected_data: None,
214 _state: PhantomData::<Incomplete>,
215 }
216 }
217}
218
219impl TxCommitment<Complete> {
220 pub fn new(candidate_data: Vec<u8>, expected_data: Value) -> Self {
221 Self {
222 candidate_data,
223 expected_data: Some(expected_data),
224 _state: PhantomData::<Complete>,
225 }
226 }
227}
228
229impl<T> TrivialCommitment for TxCommitment<T> {
230 fn hasher(&self) -> fn(&[u8]) -> CommitmentResult<String> {
231 |x| {
233 let tx: Transaction = match Deserialize::deserialize(x) {
234 Ok(tx) => tx,
235 Err(e) => {
236 return Err(CommitmentError::FailedToComputeHash(format!(
237 "Failed to deserialize transaction: {}",
238 e
239 )));
240 }
241 };
242 Ok(tx.txid().to_string())
243 }
244 }
245
246 fn candidate_data(&self) -> &[u8] {
247 &self.candidate_data
248 }
249
250 fn decode_candidate_data(&self) -> fn(&[u8]) -> CommitmentResult<Value> {
253 |x| {
254 let tx: Transaction = match Deserialize::deserialize(x) {
256 Ok(tx) => tx,
257 Err(e) => {
258 return Err(CommitmentError::DataDecodingError(format!(
259 "Failed to deserialize transaction: {}",
260 e
261 )));
262 }
263 };
264 let cid = tx_to_op_return_cid(&tx)
266 .map_err(|e| CommitmentError::DataDecodingError(e.to_string()))?;
267 Ok(json!({ CID_KEY: cid }))
268 }
269 }
270 fn to_commitment(self: Box<Self>, expected_data: serde_json::Value) -> Box<dyn Commitment> {
271 Box::new(TxCommitment::<Complete>::new(
272 self.candidate_data,
273 expected_data,
274 ))
275 }
276}
277
278impl Commitment for TxCommitment<Complete> {
279 fn expected_data(&self) -> &serde_json::Value {
280 self.expected_data.as_ref().unwrap()
282 }
283}
284
285pub struct MerkleRootCommitment<T = Incomplete> {
287 candidate_data: Vec<u8>,
288 expected_data: Option<Value>,
289 _state: PhantomData<T>, }
291
292impl MerkleRootCommitment<Incomplete> {
293 pub fn new(candidate_data: Vec<u8>) -> Self {
294 Self {
295 candidate_data,
296 expected_data: None,
297 _state: PhantomData::<Incomplete>,
298 }
299 }
300}
301impl MerkleRootCommitment<Complete> {
302 pub fn new(candidate_data: Vec<u8>, expected_data: Value) -> Self {
303 Self {
304 candidate_data,
305 expected_data: Some(expected_data),
306 _state: PhantomData::<Complete>,
307 }
308 }
309}
310
311impl<T> TrivialCommitment for MerkleRootCommitment<T> {
312 fn hasher(&self) -> fn(&[u8]) -> CommitmentResult<String> {
313 |x| {
315 let merkle_block: MerkleBlock = match bitcoin::consensus::deserialize(x) {
316 Ok(mb) => mb,
317 Err(e) => {
318 return Err(CommitmentError::FailedToComputeHash(format!(
319 "Failed to deserialize MerkleBlock: {:?}",
320 e
321 )));
322 }
323 };
324 match merkle_block.txn.extract_matches(&mut vec![], &mut vec![]) {
326 Ok(merkle_root) => Ok(merkle_root.to_string()),
327 Err(e) => Err(CommitmentError::FailedToComputeHash(format!(
328 "Failed to obtain Merkle root from PartialMerkleTree: {:?}",
329 e
330 ))),
331 }
332 }
333 }
334
335 fn candidate_data(&self) -> &[u8] {
336 &self.candidate_data
337 }
338
339 fn decode_candidate_data(&self) -> fn(&[u8]) -> CommitmentResult<Value> {
341 |x| {
342 let merkle_block: MerkleBlock = match bitcoin::consensus::deserialize(x) {
343 Ok(mb) => mb,
344 Err(e) => {
345 return Err(CommitmentError::DataDecodingError(format!(
346 "Failed to deserialize MerkleBlock: {:?}",
347 e
348 )));
349 }
350 };
351 let hashes_vec: Vec<String> = merkle_block
353 .txn
354 .hashes()
355 .iter()
356 .map(|x| x.to_string())
357 .collect();
358
359 Ok(serde_json::json!(hashes_vec))
361 }
362 }
363
364 fn to_commitment(self: Box<Self>, expected_data: serde_json::Value) -> Box<dyn Commitment> {
365 Box::new(MerkleRootCommitment::<Complete>::new(
366 self.candidate_data,
367 expected_data,
368 ))
369 }
370}
371
372impl Commitment for MerkleRootCommitment<Complete> {
373 fn expected_data(&self) -> &serde_json::Value {
374 self.expected_data.as_ref().unwrap()
376 }
377}
378
379pub struct BlockHashCommitment<T = Incomplete> {
381 candidate_data: Vec<u8>,
382 expected_data: Option<Value>,
383 _state: PhantomData<T>, }
385
386impl BlockHashCommitment<Incomplete> {
387 pub fn new(candidate_data: Vec<u8>) -> Self {
388 Self {
389 candidate_data,
390 expected_data: None,
391 _state: PhantomData::<Incomplete>,
392 }
393 }
394}
395
396impl BlockHashCommitment<Complete> {
397 pub fn new(candidate_data: Vec<u8>, expected_data: Value) -> Self {
398 Self {
399 candidate_data,
400 expected_data: Some(expected_data),
401 _state: PhantomData::<Complete>,
402 }
403 }
404}
405
406impl<T> TrivialCommitment for BlockHashCommitment<T> {
407 fn hasher(&self) -> fn(&[u8]) -> CommitmentResult<String> {
408 block_header_hasher()
409 }
410
411 fn candidate_data(&self) -> &[u8] {
412 &self.candidate_data
413 }
414
415 fn decode_candidate_data(&self) -> fn(&[u8]) -> CommitmentResult<Value> {
417 block_header_decoder()
418 }
419
420 fn filter(&self) -> Option<Box<dyn Fn(&serde_json::Value) -> CommitmentResult<Value>>> {
422 Some(Box::new(move |value| {
423 if let Value::Object(map) = value {
424 match map.get(MERKLE_ROOT_KEY) {
425 Some(Value::String(str)) => Ok(Value::String(str.clone())),
426 _ => Err(CommitmentError::DataDecodingFailure),
427 }
428 } else {
429 Err(CommitmentError::DataDecodingFailure)
430 }
431 }))
432 }
433
434 fn to_commitment(self: Box<Self>, expected_data: serde_json::Value) -> Box<dyn Commitment> {
435 Box::new(BlockHashCommitment::<Complete>::new(
436 self.candidate_data,
437 expected_data,
438 ))
439 }
440}
441
442impl Commitment for BlockHashCommitment<Complete> {
443 fn expected_data(&self) -> &serde_json::Value {
444 self.expected_data.as_ref().unwrap()
446 }
447}
448
449pub struct IONCommitment {
451 did_doc: Document,
452 chained_commitment: ChainedCommitment,
453}
454
455impl IONCommitment {
456 pub fn new(
457 did_doc: Document,
458 chunk_file: Vec<u8>,
459 provisional_index_file: Vec<u8>,
460 core_index_file: Vec<u8>,
461 transaction: Vec<u8>,
462 merkle_proof: Vec<u8>,
463 block_header: Vec<u8>,
464 ) -> CommitmentResult<Self> {
465 let keys = did_doc.get_keys().unwrap_or_default();
467 let endpoints = did_doc.get_endpoints().unwrap_or_default();
468 let expected_data = json!([keys, endpoints]);
469
470 let core_index_file_commitment =
472 IpfsIndexFileCommitment::<Incomplete>::new(core_index_file);
473 let delta_index: usize = serde_json::from_value::<CoreIndexFile>(
474 core_index_file_commitment.commitment_content()?,
475 )?
476 .did_create_operation_index(&did_doc.id)?;
477
478 let chunk_file_commitment =
480 IpfsChunkFileCommitment::<Incomplete>::new(chunk_file, delta_index);
481 let prov_index_file_commitment =
482 IpfsIndexFileCommitment::<Incomplete>::new(provisional_index_file);
483 let tx_commitment = TxCommitment::<Incomplete>::new(transaction);
484 let merkle_root_commitment = MerkleRootCommitment::<Incomplete>::new(merkle_proof);
485 let block_hash_commitment = BlockHashCommitment::<Incomplete>::new(block_header);
486
487 let mut iterated_commitment =
491 ChainedCommitment::new(Box::new(chunk_file_commitment).to_commitment(expected_data));
492 iterated_commitment.append(Box::new(prov_index_file_commitment))?;
493 iterated_commitment.append(Box::new(core_index_file_commitment))?;
494 iterated_commitment.append(Box::new(tx_commitment))?;
495 iterated_commitment.append(Box::new(merkle_root_commitment))?;
496 iterated_commitment.append(Box::new(block_hash_commitment))?;
497
498 Ok(Self {
499 did_doc,
500 chained_commitment: iterated_commitment,
501 })
502 }
503
504 pub fn chained_commitment(&self) -> &ChainedCommitment {
505 &self.chained_commitment
506 }
507}
508
509impl TrivialCommitment for IONCommitment {
511 fn hasher(&self) -> fn(&[u8]) -> CommitmentResult<String> {
512 self.chained_commitment.hasher()
513 }
514
515 fn hash(&self) -> CommitmentResult<String> {
516 self.chained_commitment.hash()
517 }
518
519 fn candidate_data(&self) -> &[u8] {
520 self.chained_commitment.candidate_data()
521 }
522
523 fn decode_candidate_data(&self) -> fn(&[u8]) -> CommitmentResult<Value> {
524 self.chained_commitment.decode_candidate_data()
525 }
526
527 fn to_commitment(self: Box<Self>, _: serde_json::Value) -> Box<dyn Commitment> {
528 self
529 }
530}
531
532impl Commitment for IONCommitment {
534 fn expected_data(&self) -> &serde_json::Value {
535 self.chained_commitment.expected_data()
537 }
538 fn verify(&self, target: &str) -> CommitmentResult<()> {
540 self.chained_commitment.verify(target)?;
542 Ok(())
543 }
544}
545
546impl DIDCommitment for IONCommitment {
547 fn did(&self) -> &str {
548 &self.did_doc.id
549 }
550
551 fn did_document(&self) -> &Document {
552 &self.did_doc
553 }
554
555 fn as_any(&self) -> &dyn std::any::Any {
556 self
557 }
558}
559
560pub struct BlockTimestampCommitment {
563 candidate_data: Vec<u8>,
564 expected_data: Timestamp,
565}
566
567impl BlockTimestampCommitment {
568 pub fn new(candidate_data: Vec<u8>, expected_data: Timestamp) -> CommitmentResult<Self> {
569 Ok(Self {
572 candidate_data,
573 expected_data,
574 })
575 }
576}
577
578impl TrivialCommitment<Timestamp> for BlockTimestampCommitment {
579 fn hasher(&self) -> fn(&[u8]) -> CommitmentResult<String> {
580 block_header_hasher()
581 }
582
583 fn candidate_data(&self) -> &[u8] {
584 &self.candidate_data
585 }
586
587 fn decode_candidate_data(&self) -> fn(&[u8]) -> CommitmentResult<Value> {
589 block_header_decoder()
590 }
591
592 fn filter(&self) -> Option<Box<dyn Fn(&serde_json::Value) -> CommitmentResult<Value>>> {
594 Some(Box::new(move |value| {
595 if let Value::Object(map) = value {
596 match map.get(TIMESTAMP_KEY) {
597 Some(Value::Number(timestamp)) => Ok(Value::Number(timestamp.clone())),
598 _ => Err(CommitmentError::DataDecodingFailure),
599 }
600 } else {
601 Err(CommitmentError::DataDecodingFailure)
602 }
603 }))
604 }
605
606 fn to_commitment(self: Box<Self>, _: Timestamp) -> Box<dyn Commitment<Timestamp>> {
607 self
608 }
609}
610
611impl Commitment<Timestamp> for BlockTimestampCommitment {
612 fn expected_data(&self) -> &Timestamp {
613 &self.expected_data
614 }
615}
616
617impl TimestampCommitment for BlockTimestampCommitment {}
618
619#[cfg(test)]
620mod tests {
621 use bitcoin::util::psbt::serialize::Serialize;
622 use bitcoin::BlockHash;
623 use ipfs_api_backend_hyper::IpfsClient;
624 use std::str::FromStr;
625 use trustchain_core::{data::TEST_ROOT_DOCUMENT, utils::json_contains};
626
627 use super::*;
628 use crate::{
629 data::TEST_BLOCK_HEADER_HEX,
630 utils::{block_header, merkle_proof, query_ipfs, transaction},
631 };
632
633 #[test]
634 fn test_block_timestamp_commitment() {
635 let expected_data: Timestamp = 1666265405;
636 let candidate_data = hex::decode(TEST_BLOCK_HEADER_HEX).unwrap();
637 let target = BlockTimestampCommitment::new(candidate_data.clone(), expected_data).unwrap();
638 target.verify_content().unwrap();
639 let pow_hash = "000000000000000eaa9e43748768cd8bf34f43aaa03abd9036c463010a0c6e7f";
640 target.verify(pow_hash).unwrap();
641
642 let bad_expected_data: Timestamp = 1666265406;
644 let target = BlockTimestampCommitment::new(candidate_data, bad_expected_data).unwrap();
645 match target.verify_content() {
646 Err(CommitmentError::FailedContentVerification(s1, s2)) => {
647 assert_eq!(
648 (s1, s2),
649 (format!("{bad_expected_data}"), format!("{expected_data}"))
650 )
651 }
652 _ => panic!(),
653 };
654 match target.verify(pow_hash) {
655 Err(CommitmentError::FailedContentVerification(s1, s2)) => {
656 assert_eq!(
657 (s1, s2),
658 (format!("{bad_expected_data}"), format!("{expected_data}"))
659 )
660 }
661 _ => panic!(),
662 };
663 }
664
665 #[test]
666 fn test_block_hash_commitment_filter() {
667 let expected_data =
670 json!("7dce795209d4b5051da3f5f5293ac97c2ec677687098062044654111529cad69");
671 let candidate_data = hex::decode(TEST_BLOCK_HEADER_HEX).unwrap();
672 let target = BlockHashCommitment::<Complete>::new(candidate_data, expected_data);
673 target.verify_content().unwrap();
674 let pow_hash = "000000000000000eaa9e43748768cd8bf34f43aaa03abd9036c463010a0c6e7f";
675 target.verify(pow_hash).unwrap();
676 }
677
678 #[tokio::test]
679 #[ignore = "Integration test requires IPFS"]
680 async fn test_extract_suffix_idx() {
681 let target = "QmRvgZm4J3JSxfk4wRjE2u2Hi2U7VmobYnpqhqH5QP6J97";
682 let ipfs_client = IpfsClient::default();
683 let candidate_data = query_ipfs(target, &ipfs_client).await.unwrap();
684 let core_index_file_commitment = IpfsIndexFileCommitment::<Incomplete>::new(candidate_data);
685 let operation_idx = serde_json::from_value::<CoreIndexFile>(
686 core_index_file_commitment.commitment_content().unwrap(),
687 )
688 .unwrap()
689 .did_create_operation_index("did:ion:test:EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A");
690
691 assert_eq!(1, operation_idx.unwrap());
692 }
693
694 #[tokio::test]
695 #[ignore = "Integration test requires IPFS"]
696 async fn test_ipfs_commitment() {
697 let target = "QmRvgZm4J3JSxfk4wRjE2u2Hi2U7VmobYnpqhqH5QP6J97";
698
699 let ipfs_client = IpfsClient::default();
700
701 let candidate_data_ = query_ipfs(target, &ipfs_client).await.unwrap();
702 let candidate_data = candidate_data_.clone();
703 let expected_data =
705 r#"{"provisionalIndexFileUri":"QmfXAa2MsHspcTSyru4o1bjPQELLi62sr2pAKizFstaxSs"}"#;
706 let expected_data: serde_json::Value = serde_json::from_str(expected_data).unwrap();
707 let commitment = IpfsIndexFileCommitment::<Complete>::new(candidate_data, expected_data);
708 assert!(commitment.verify(target).is_ok());
709
710 let bad_target = "QmRvgZm4J3JSxfk4wRjE2u2Hi2U7VmobYnpqhqH5QP6J98";
712 assert!(commitment.verify(bad_target).is_err());
713 match commitment.verify(bad_target) {
714 Err(CommitmentError::FailedHashVerification(..)) => (),
715 _ => panic!("Expected FailedHashVerification error."),
716 }
717
718 let bad_expected_data =
720 r#"{"provisionalIndexFileUri":"PmfXAa2MsHspcTSyru4o1bjPQELLi62sr2pAKizFstaxSs"}"#;
721 let bad_expected_data = serde_json::from_str(bad_expected_data).unwrap();
722 let candidate_data = candidate_data_;
723 let commitment =
724 IpfsIndexFileCommitment::<Complete>::new(candidate_data, bad_expected_data);
725 assert!(commitment.verify(target).is_err());
726 match commitment.verify(target) {
727 Err(CommitmentError::FailedContentVerification(..)) => (),
728 _ => panic!("Expected FailedContentVerification error."),
729 };
730 }
731
732 #[test]
733 #[ignore = "Integration test requires Bitcoin Core"]
734 fn test_tx_commitment() {
735 let target = "9dc43cca950d923442445340c2e30bc57761a62ef3eaf2417ec5c75784ea9c2c";
736
737 let block_hash_str = "000000000000000eaa9e43748768cd8bf34f43aaa03abd9036c463010a0c6e7f";
739 let block_hash = BlockHash::from_str(block_hash_str).unwrap();
740 let tx = transaction(&block_hash, 3, None).unwrap();
741
742 let cid_str = "QmRvgZm4J3JSxfk4wRjE2u2Hi2U7VmobYnpqhqH5QP6J97";
744 let expected_str = format!(r#"{{"{}":"{}"}}"#, CID_KEY, cid_str);
745 let expected_data: serde_json::Value = serde_json::from_str(&expected_str).unwrap();
746 let candidate_data = Serialize::serialize(&tx);
747
748 let commitment = TxCommitment::<Complete>::new(candidate_data, expected_data);
749 assert!(commitment.verify(target).is_ok());
750
751 let bad_target = "8dc43cca950d923442445340c2e30bc57761a62ef3eaf2417ec5c75784ea9c2c";
753 assert!(commitment.verify(bad_target).is_err());
754 match commitment.verify(bad_target) {
755 Err(CommitmentError::FailedHashVerification(..)) => (),
756 _ => panic!("Expected FailedHashVerification error."),
757 };
758
759 let bad_cid_str = "PmRvgZm4J3JSxfk4wRjE2u2Hi2U7VmobYnpqhqH5QP6J97";
761 let bad_expected_str = format!(r#"{{"{}":"{}"}}"#, CID_KEY, bad_cid_str);
762 let bad_expected_data: serde_json::Value = serde_json::from_str(&bad_expected_str).unwrap();
763 let candidate_data = Serialize::serialize(&tx);
764 let commitment = TxCommitment::<Complete>::new(candidate_data, bad_expected_data);
765 assert!(commitment.verify(target).is_err());
766 match commitment.verify(target) {
767 Err(CommitmentError::FailedContentVerification(..)) => (),
768 _ => panic!("Expected FailedContentVerification error."),
769 };
770 }
771
772 #[test]
773 #[ignore = "Integration test requires Bitcoin Core"]
774 fn test_merkle_root_commitment() {
775 let target = "7dce795209d4b5051da3f5f5293ac97c2ec677687098062044654111529cad69";
778 let block_hash_str = "000000000000000eaa9e43748768cd8bf34f43aaa03abd9036c463010a0c6e7f";
780
781 let txid_str = "9dc43cca950d923442445340c2e30bc57761a62ef3eaf2417ec5c75784ea9c2c";
783 let expected_data = serde_json::json!(txid_str);
784
785 let block_hash = BlockHash::from_str(block_hash_str).unwrap();
787 let tx_index = 3;
788 let tx = transaction(&block_hash, tx_index, None).unwrap();
789
790 let candidate_data_ = merkle_proof(&tx, &block_hash, None).unwrap();
792 let candidate_data = candidate_data_.clone();
793
794 let commitment = MerkleRootCommitment::<Complete>::new(candidate_data, expected_data);
795 assert!(commitment.verify(target).is_ok());
796
797 let bad_target = "8dce795209d4b5051da3f5f5293ac97c2ec677687098062044654111529cad69";
799 assert!(commitment.verify(bad_target).is_err());
800 match commitment.verify(bad_target) {
801 Err(CommitmentError::FailedHashVerification(..)) => (),
802 _ => panic!("Expected FailedHashVerification error."),
803 };
804
805 let bad_txid_str = "2dc43cca950d923442445340c2e30bc57761a62ef3eaf2417ec5c75784ea9c2c";
807 let bad_expected_data = serde_json::json!(bad_txid_str);
808 let candidate_data = candidate_data_;
809 let commitment = MerkleRootCommitment::<Complete>::new(candidate_data, bad_expected_data);
810 assert!(commitment.verify(target).is_err());
811 match commitment.verify(target) {
812 Err(CommitmentError::FailedContentVerification(..)) => (),
813 _ => panic!("Expected FailedContentVerification error."),
814 };
815 }
816
817 #[test]
818 #[ignore = "Integration test requires Bitcoin Core"]
819 fn test_block_hash_commitment() {
820 let target = "000000000000000eaa9e43748768cd8bf34f43aaa03abd9036c463010a0c6e7f";
822 let block_hash = BlockHash::from_str(target).unwrap();
823
824 let merkle_root_str = "7dce795209d4b5051da3f5f5293ac97c2ec677687098062044654111529cad69";
827 let expected_data = json!(merkle_root_str);
828
829 let block_header = block_header(&block_hash, None).unwrap();
831 let candidate_data = bitcoin::consensus::serialize(&block_header);
832 let commitment =
833 BlockHashCommitment::<Complete>::new(candidate_data.clone(), expected_data);
834 commitment.verify(target).unwrap();
835
836 let bad_target = "100000000000000eaa9e43748768cd8bf34f43aaa03abd9036c463010a0c6e7f";
838 assert!(commitment.verify(bad_target).is_err());
839 match commitment.verify(bad_target) {
840 Err(CommitmentError::FailedHashVerification(..)) => (),
841 _ => panic!("Expected FailedHashVerification error."),
842 };
843
844 let bad_merkle_root_str =
846 "6dce795209d4b5051da3f5f5293ac97c2ec677687098062044654111529cad69";
847 let bad_expected_data = json!(bad_merkle_root_str);
848 let commitment =
849 BlockHashCommitment::<Complete>::new(candidate_data.clone(), bad_expected_data);
850 assert!(commitment.verify(target).is_err());
851 match commitment.verify(target) {
852 Err(CommitmentError::FailedContentVerification(..)) => (),
853 _ => panic!("Expected FailedContentVerification error."),
854 };
855
856 let wrong_expected_data_commitment =
859 BlockHashCommitment::<Complete>::new(candidate_data.clone(), json!(1666265405));
860 assert!(wrong_expected_data_commitment.verify(target).is_err());
861
862 let expected_data = 1666265405;
864 let commitment =
865 BlockTimestampCommitment::new(candidate_data.clone(), expected_data).unwrap();
866 commitment.verify_content().unwrap();
867 commitment.verify(target).unwrap();
868 let bad_expected_data = 1666265406;
869 let commitment = BlockTimestampCommitment::new(candidate_data, bad_expected_data).unwrap();
870 assert!(commitment.verify_content().is_err());
871 assert!(commitment.verify(target).is_err());
872 }
873
874 #[tokio::test]
875 #[ignore = "Integration test requires IPFS and Bitcoin Core"]
876 async fn test_ion_commitment() {
877 let did_doc = Document::from_json(TEST_ROOT_DOCUMENT).unwrap();
878
879 let ipfs_client = IpfsClient::default();
880
881 let chunk_file_cid = "QmWeK5PbKASyNjEYKJ629n6xuwmarZTY6prd19ANpt6qyN";
882 let chunk_file = query_ipfs(chunk_file_cid, &ipfs_client).await.unwrap();
883
884 let prov_index_file_cid = "QmfXAa2MsHspcTSyru4o1bjPQELLi62sr2pAKizFstaxSs";
885 let prov_index_file = query_ipfs(prov_index_file_cid, &ipfs_client).await.unwrap();
886
887 let core_index_file_cid = "QmRvgZm4J3JSxfk4wRjE2u2Hi2U7VmobYnpqhqH5QP6J97";
888 let core_index_file = query_ipfs(core_index_file_cid, &ipfs_client).await.unwrap();
889
890 let block_hash_str = "000000000000000eaa9e43748768cd8bf34f43aaa03abd9036c463010a0c6e7f";
891 let block_hash = BlockHash::from_str(block_hash_str).unwrap();
892 let tx_index = 3;
893 let tx = transaction(&block_hash, tx_index, None).unwrap();
894 let transaction = Serialize::serialize(&tx);
895
896 let merkle_proof = merkle_proof(&tx, &block_hash, None).unwrap();
897
898 let block_header = block_header(&block_hash, None).unwrap();
899 let block_header = bitcoin::consensus::serialize(&block_header);
900
901 let commitment = IONCommitment::new(
902 did_doc,
903 chunk_file,
904 prov_index_file,
905 core_index_file,
906 transaction,
907 merkle_proof,
908 block_header,
909 )
910 .unwrap();
911
912 let expected_data = commitment.chained_commitment.expected_data();
913
914 println!("{:?}", expected_data);
915 match expected_data {
917 serde_json::Value::Array(arr) => {
918 assert_eq!(arr.len(), 2);
919 }
920 _ => panic!("Expected JSON Array."),
921 }
922
923 let commitments = commitment.chained_commitment.commitments();
925
926 let chunk_file_commitment = commitments.first().unwrap();
929 assert_eq!(chunk_file_commitment.hash().unwrap(), chunk_file_cid);
930 assert_eq!(expected_data, chunk_file_commitment.expected_data());
931
932 assert!(&chunk_file_commitment.verify(chunk_file_cid).is_ok());
934
935 let prov_index_file_commitment = commitments.get(1).unwrap();
938 assert_eq!(
939 prov_index_file_commitment.hash().unwrap(),
940 prov_index_file_cid
941 );
942 assert!(json_contains(
943 &json!(chunk_file_cid),
944 prov_index_file_commitment.expected_data()
945 ));
946
947 assert!(&prov_index_file_commitment
949 .verify(prov_index_file_cid)
950 .is_ok());
951
952 let core_index_file_commitment = commitments.get(2).unwrap();
955 assert_eq!(
956 core_index_file_commitment.hash().unwrap(),
957 core_index_file_cid
958 );
959 assert!(json_contains(
960 &json!(prov_index_file_cid),
961 core_index_file_commitment.expected_data()
962 ));
963
964 assert!(&core_index_file_commitment
966 .verify(core_index_file_cid)
967 .is_ok());
968
969 let tx_commitment = commitments.get(3).unwrap();
972 let tx_id = "9dc43cca950d923442445340c2e30bc57761a62ef3eaf2417ec5c75784ea9c2c";
973 assert_eq!(tx_commitment.hash().unwrap(), tx_id);
974 assert!(json_contains(
975 &json!(core_index_file_cid),
976 tx_commitment.expected_data()
977 ));
978
979 assert!(&tx_commitment.verify(tx_id).is_ok());
981
982 let merkle_root_commitment = commitments.get(4).unwrap();
985 let merkle_root = "7dce795209d4b5051da3f5f5293ac97c2ec677687098062044654111529cad69";
986 assert_eq!(merkle_root_commitment.hash().unwrap(), merkle_root);
987 assert!(json_contains(
988 &json!(tx_id),
989 merkle_root_commitment.expected_data()
990 ));
991
992 assert!(&merkle_root_commitment.verify(merkle_root).is_ok());
994
995 let block_hash_commitment = commitments.get(5).unwrap();
998 assert_eq!(block_hash_commitment.hash().unwrap(), block_hash_str);
999 assert!(json_contains(
1000 &json!(merkle_root),
1001 block_hash_commitment.expected_data()
1002 ));
1003
1004 assert!(&merkle_root_commitment.verify(merkle_root).is_ok());
1006
1007 assert!(commitment.chained_commitment.verify_content().is_ok());
1009 assert!(commitment.chained_commitment.verify(block_hash_str).is_ok());
1010
1011 assert!(commitment.verify(block_hash_str).is_ok());
1013 }
1014}