//! One receipt proved as a COSE Receipt (RFC 9942, log-seal ยง5.6): the //! RFC 9162 inclusion algorithm over every leaf of every small tree, and a //! receipt that proves against the root a checkpoint of the same size states. use ed25519_dalek::SigningKey; use jcp_logseal::cose::{cose_receipt, sha256_path, sha256_root_from_path, verify_cose_receipt, CoseError}; use jcp_logseal::note::{checkpoint, read_checkpoint, sha256_root}; use jcp_schema::ContentId; #[test] fn every_leaf_of_every_small_tree_proves_and_nothing_else_does() { let ids: Vec = (0..40u32).map(|i| ContentId::of_bytes(&i.to_le_bytes())).collect(); for n in 1..=ids.len() { let root = sha256_root(&ids[..n]); for i in 0..n { let path = sha256_path(&ids[..n], i); assert_eq!(sha256_root_from_path(&ids[i], i as u64, n as u64, &path), Some(root), "{i} of {n}"); if n > 1 { assert_ne!(sha256_root_from_path(&ids[(i + 1) % n], i as u64, n as u64, &path), Some(root), "another leaf at {i} of {n}"); let mut bent = path.clone(); bent[0][0] ^= 1; assert_ne!(sha256_root_from_path(&ids[i], i as u64, n as u64, &bent), Some(root)); assert_eq!(sha256_root_from_path(&ids[i], i as u64, n as u64, &path[..path.len() - 1]), None, "a path cut short"); } } assert_eq!(sha256_root_from_path(&ids[0], n as u64, n as u64, &[]), None, "an index past the tree"); } } #[test] fn a_cose_receipt_proves_one_receipt_against_the_checkpoints_root() { let entries: Vec> = (0..9).map(|i| format!("receipt {i}").into_bytes()).collect(); let key = SigningKey::from_bytes(&[7; 32]); let receipt = cose_receipt(&entries, 9, 4, &key, b"jcp.example/steward").unwrap(); let entry = ContentId::of_bytes(&entries[4]); let proved = verify_cose_receipt(&receipt, &entry, &key.verifying_key()).unwrap(); assert_eq!((proved[0].tree_size, proved[0].leaf_index), (9, 4)); let (_, _, checkpoint_root) = read_checkpoint(&checkpoint("jcp.example/steward", &entries, 9).unwrap()).unwrap(); assert_eq!(proved[0].root, checkpoint_root, "one root, two projections"); // another receipt, another key, a changed signature: none verifies let other = ContentId::of_bytes(&entries[5]); assert_eq!(verify_cose_receipt(&receipt, &other, &key.verifying_key()), Err(CoseError::BadSignature)); assert_eq!(verify_cose_receipt(&receipt, &entry, &SigningKey::from_bytes(&[8; 32]).verifying_key()), Err(CoseError::BadSignature)); let mut bent = receipt.clone(); let last = bent.len() - 1; bent[last] ^= 1; assert_eq!(verify_cose_receipt(&bent, &entry, &key.verifying_key()), Err(CoseError::BadSignature)); // another verifiable data structure than RFC9162_SHA256 let at = receipt.windows(4).position(|w| w == [0x19, 0x01, 0x8b, 0x01]).expect("vds in the protected header"); let mut other_vds = receipt.clone(); other_vds[at + 3] = 0x02; assert_eq!(verify_cose_receipt(&other_vds, &entry, &key.verifying_key()), Err(CoseError::Unsupported("vds"))); let mut trailing = receipt.clone(); trailing.push(0); assert!(matches!(verify_cose_receipt(&trailing, &entry, &key.verifying_key()), Err(CoseError::Malformed(_)))); assert_eq!(cose_receipt(&entries, 9, 9, &key, b"k"), Err(CoseError::BadProof)); }