1use std::collections::BTreeMap;
46
47use metamorphic_crypto::hash::sha3_512_with_context;
48
49use crate::commitment::{COMMITMENT_LEN, COMMITMENT_OPENING_LEN, Commitment, Opening};
50use crate::error::{Error, Result};
51use crate::vrf::{Vrf, VrfProof, VrfPublicKey, VrfSecretKey};
52
53const TREE_DEPTH: usize = 256;
56const NODE_LEN: usize = 64;
58const INDEX_LEN: usize = 32;
60
61#[derive(Debug, Clone, PartialEq, Eq, Hash)]
69pub struct Namespace(String);
70
71impl Namespace {
72 pub fn parse(namespace: &str) -> Result<Self> {
78 if namespace.is_empty() || !namespace.bytes().all(|b| b.is_ascii_graphic() && b != b'/') {
79 return Err(Error::MalformedNamespace(format!(
80 "namespace must be non-empty printable ASCII without '/': {namespace:?}"
81 )));
82 }
83 Ok(Self(namespace.to_string()))
84 }
85
86 #[must_use]
88 pub fn as_str(&self) -> &str {
89 &self.0
90 }
91
92 #[must_use]
94 pub fn commitment_label(&self) -> String {
95 format!("{}/coniks-commitment/v1", self.0)
96 }
97
98 fn leaf_label(&self) -> String {
99 format!("{}/coniks-leaf/v1", self.0)
100 }
101
102 fn node_label(&self) -> String {
103 format!("{}/coniks-node/v1", self.0)
104 }
105
106 fn empty_label(&self) -> String {
107 format!("{}/coniks-empty/v1", self.0)
108 }
109
110 #[must_use]
114 pub fn vrf_input(&self, identity: &[u8]) -> Vec<u8> {
115 let mut input = Vec::with_capacity(4 + self.0.len() + identity.len());
116 input.extend_from_slice(&(self.0.len() as u32).to_be_bytes());
117 input.extend_from_slice(self.0.as_bytes());
118 input.extend_from_slice(identity);
119 input
120 }
121}
122
123fn index_bit(index: &[u8; INDEX_LEN], i: usize) -> u8 {
126 (index[i / 8] >> (7 - (i % 8))) & 1
127}
128
129struct TreeHasher {
134 leaf_label: String,
135 node_label: String,
136 suite_id: u8,
137 empty: Vec<[u8; NODE_LEN]>,
140}
141
142impl TreeHasher {
143 fn new(namespace: &Namespace, suite_id: u8) -> Self {
144 let empty_leaf: [u8; NODE_LEN] = sha3_512_with_context(&namespace.empty_label(), &[]);
145 let node_label = namespace.node_label();
146
147 let mut empty = vec![[0u8; NODE_LEN]; TREE_DEPTH + 1];
149 empty[TREE_DEPTH] = empty_leaf;
150 for d in (0..TREE_DEPTH).rev() {
151 empty[d] = node_hash(&node_label, &empty[d + 1], &empty[d + 1]);
152 }
153
154 Self {
155 leaf_label: namespace.leaf_label(),
156 node_label,
157 suite_id,
158 empty,
159 }
160 }
161
162 fn leaf_hash(&self, index: &[u8; INDEX_LEN], commitment: &Commitment) -> [u8; NODE_LEN] {
166 let mut buf = [0u8; 1 + INDEX_LEN + COMMITMENT_LEN];
167 buf[0] = self.suite_id;
168 buf[1..1 + INDEX_LEN].copy_from_slice(index);
169 buf[1 + INDEX_LEN..].copy_from_slice(commitment.as_bytes());
170 sha3_512_with_context(&self.leaf_label, &buf)
171 }
172
173 fn empty_leaf(&self) -> [u8; NODE_LEN] {
174 self.empty[TREE_DEPTH]
175 }
176
177 fn subtree(&self, depth: usize, leaves: &[(&[u8; INDEX_LEN], &Commitment)]) -> [u8; NODE_LEN] {
180 if leaves.is_empty() {
181 return self.empty[depth];
182 }
183 if depth == TREE_DEPTH {
184 let (index, commitment) = leaves[0];
186 return self.leaf_hash(index, commitment);
187 }
188 let (left, right): (Vec<_>, Vec<_>) = leaves
189 .iter()
190 .partition(|(index, _)| index_bit(index, depth) == 0);
191 let l = self.subtree(depth + 1, &left);
192 let r = self.subtree(depth + 1, &right);
193 node_hash(&self.node_label, &l, &r)
194 }
195
196 fn collect_path(
200 &self,
201 depth: usize,
202 target: &[u8; INDEX_LEN],
203 leaves: &[(&[u8; INDEX_LEN], &Commitment)],
204 out: &mut Vec<Option<[u8; NODE_LEN]>>,
205 ) {
206 if depth == TREE_DEPTH {
207 return;
208 }
209 let (left, right): (Vec<_>, Vec<_>) = leaves
210 .iter()
211 .copied()
212 .partition(|(index, _)| index_bit(index, depth) == 0);
213 let (on_path, sibling) = if index_bit(target, depth) == 0 {
214 (left, right)
215 } else {
216 (right, left)
217 };
218 let sibling_hash = self.subtree(depth + 1, &sibling);
219 out.push(if sibling_hash == self.empty[depth + 1] {
220 None
221 } else {
222 Some(sibling_hash)
223 });
224 self.collect_path(depth + 1, target, &on_path, out);
225 }
226
227 fn recompute_root(
230 &self,
231 target: &[u8; INDEX_LEN],
232 leaf_node: [u8; NODE_LEN],
233 siblings: &[Option<[u8; NODE_LEN]>],
234 ) -> [u8; NODE_LEN] {
235 let mut current = leaf_node;
236 for depth in (0..TREE_DEPTH).rev() {
237 let sibling = siblings[depth].unwrap_or(self.empty[depth + 1]);
238 current = if index_bit(target, depth) == 0 {
239 node_hash(&self.node_label, ¤t, &sibling)
240 } else {
241 node_hash(&self.node_label, &sibling, ¤t)
242 };
243 }
244 current
245 }
246}
247
248fn node_hash(node_label: &str, left: &[u8; NODE_LEN], right: &[u8; NODE_LEN]) -> [u8; NODE_LEN] {
251 let mut buf = [0u8; 2 * NODE_LEN];
252 buf[..NODE_LEN].copy_from_slice(left);
253 buf[NODE_LEN..].copy_from_slice(right);
254 sha3_512_with_context(node_label, &buf)
255}
256
257#[derive(Debug, Clone, PartialEq, Eq)]
260pub struct AuthPath {
261 siblings: Vec<Option<[u8; NODE_LEN]>>,
262}
263
264impl AuthPath {
265 fn to_bytes(&self) -> Vec<u8> {
269 let mut bitmap = [0u8; TREE_DEPTH / 8];
270 let mut hashes = Vec::new();
271 for (d, sibling) in self.siblings.iter().enumerate() {
272 if let Some(h) = sibling {
273 bitmap[d / 8] |= 1 << (7 - (d % 8));
274 hashes.extend_from_slice(h);
275 }
276 }
277 let mut out = Vec::with_capacity(bitmap.len() + hashes.len());
278 out.extend_from_slice(&bitmap);
279 out.extend_from_slice(&hashes);
280 out
281 }
282
283 fn parse(bytes: &[u8]) -> Result<(Self, usize)> {
286 let bitmap_len = TREE_DEPTH / 8;
287 if bytes.len() < bitmap_len {
288 return Err(Error::MalformedConiksProof(
289 "authentication path shorter than its bitmap".into(),
290 ));
291 }
292 let bitmap = &bytes[..bitmap_len];
293 let present: usize = bitmap.iter().map(|b| b.count_ones() as usize).sum();
294 let needed = bitmap_len + present * NODE_LEN;
295 if bytes.len() < needed {
296 return Err(Error::MalformedConiksProof(format!(
297 "authentication path: bitmap implies {present} sibling hashes ({needed} bytes), \
298 only {} available",
299 bytes.len()
300 )));
301 }
302
303 let mut siblings = Vec::with_capacity(TREE_DEPTH);
304 let mut offset = bitmap_len;
305 for d in 0..TREE_DEPTH {
306 let set = (bitmap[d / 8] >> (7 - (d % 8))) & 1 == 1;
307 if set {
308 let mut h = [0u8; NODE_LEN];
309 h.copy_from_slice(&bytes[offset..offset + NODE_LEN]);
310 offset += NODE_LEN;
311 siblings.push(Some(h));
312 } else {
313 siblings.push(None);
314 }
315 }
316 Ok((Self { siblings }, needed))
317 }
318}
319
320fn push_lp(out: &mut Vec<u8>, bytes: &[u8]) {
322 out.extend_from_slice(&(bytes.len() as u32).to_be_bytes());
323 out.extend_from_slice(bytes);
324}
325
326fn read_lp<'a>(bytes: &'a [u8], offset: usize, what: &str) -> Result<(&'a [u8], usize)> {
329 if bytes.len() < offset + 4 {
330 return Err(Error::MalformedConiksProof(format!(
331 "{what}: missing 4-byte length prefix"
332 )));
333 }
334 let len = u32::from_be_bytes(bytes[offset..offset + 4].try_into().unwrap()) as usize;
335 let start = offset + 4;
336 if bytes.len() < start + len {
337 return Err(Error::MalformedConiksProof(format!(
338 "{what}: length prefix {len} overruns available bytes"
339 )));
340 }
341 Ok((&bytes[start..start + len], start + len))
342}
343
344const TAG_ABSENCE: u8 = 0x00;
345const TAG_PRESENCE: u8 = 0x01;
346
347#[derive(Debug, Clone, PartialEq, Eq)]
351pub struct LookupProof {
352 vrf_proof: VrfProof,
353 value: Vec<u8>,
354 opening: Opening,
355 auth_path: AuthPath,
356}
357
358impl LookupProof {
359 #[must_use]
362 pub fn value(&self) -> &[u8] {
363 &self.value
364 }
365
366 #[must_use]
369 pub fn to_bytes(&self) -> Vec<u8> {
370 let mut out = vec![TAG_PRESENCE];
371 push_lp(&mut out, self.vrf_proof.as_bytes());
372 push_lp(&mut out, &self.value);
373 out.extend_from_slice(self.opening.as_bytes());
374 out.extend_from_slice(&self.auth_path.to_bytes());
375 out
376 }
377
378 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
384 let Some((&tag, rest)) = bytes.split_first() else {
385 return Err(Error::MalformedConiksProof("empty proof".into()));
386 };
387 if tag != TAG_PRESENCE {
388 return Err(Error::MalformedConiksProof(format!(
389 "expected presence tag 0x01, got {tag:#04x}"
390 )));
391 }
392 let (vrf_proof, off) = read_lp(rest, 0, "vrf proof")?;
393 let (value, off) = read_lp(rest, off, "value")?;
394 if rest.len() < off + COMMITMENT_OPENING_LEN {
395 return Err(Error::MalformedConiksProof("missing opening".into()));
396 }
397 let mut opening = [0u8; COMMITMENT_OPENING_LEN];
398 opening.copy_from_slice(&rest[off..off + COMMITMENT_OPENING_LEN]);
399 let (auth_path, consumed) = AuthPath::parse(&rest[off + COMMITMENT_OPENING_LEN..])?;
400 if off + COMMITMENT_OPENING_LEN + consumed != rest.len() {
401 return Err(Error::MalformedConiksProof(
402 "trailing bytes after authentication path".into(),
403 ));
404 }
405 Ok(Self {
406 vrf_proof: VrfProof::from_bytes(vrf_proof.to_vec()),
407 value: value.to_vec(),
408 opening: Opening::from_bytes(opening),
409 auth_path,
410 })
411 }
412}
413
414#[derive(Debug, Clone, PartialEq, Eq)]
418pub struct AbsenceProof {
419 vrf_proof: VrfProof,
420 auth_path: AuthPath,
421}
422
423impl AbsenceProof {
424 #[must_use]
426 pub fn to_bytes(&self) -> Vec<u8> {
427 let mut out = vec![TAG_ABSENCE];
428 push_lp(&mut out, self.vrf_proof.as_bytes());
429 out.extend_from_slice(&self.auth_path.to_bytes());
430 out
431 }
432
433 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
439 let Some((&tag, rest)) = bytes.split_first() else {
440 return Err(Error::MalformedConiksProof("empty proof".into()));
441 };
442 if tag != TAG_ABSENCE {
443 return Err(Error::MalformedConiksProof(format!(
444 "expected absence tag 0x00, got {tag:#04x}"
445 )));
446 }
447 let (vrf_proof, off) = read_lp(rest, 0, "vrf proof")?;
448 let (auth_path, consumed) = AuthPath::parse(&rest[off..])?;
449 if off + consumed != rest.len() {
450 return Err(Error::MalformedConiksProof(
451 "trailing bytes after authentication path".into(),
452 ));
453 }
454 Ok(Self {
455 vrf_proof: VrfProof::from_bytes(vrf_proof.to_vec()),
456 auth_path,
457 })
458 }
459}
460
461#[derive(Debug, Clone, PartialEq, Eq)]
463pub enum LookupResult {
464 Present(LookupProof),
466 Absent(AbsenceProof),
468}
469
470struct DirectoryEntry {
471 value: Vec<u8>,
472 opening: Opening,
473}
474
475pub struct ConiksDirectory {
481 namespace: Namespace,
482 vrf: Box<dyn Vrf>,
483 vrf_secret: VrfSecretKey,
484 vrf_public: VrfPublicKey,
485 hasher: TreeHasher,
486 entries: BTreeMap<Vec<u8>, DirectoryEntry>,
487 leaves: BTreeMap<[u8; INDEX_LEN], Commitment>,
488}
489
490impl ConiksDirectory {
491 #[must_use]
494 pub fn new(namespace: Namespace, vrf: Box<dyn Vrf>) -> Self {
495 let (vrf_secret, vrf_public) = vrf.generate_keypair();
496 Self::with_secret_key(namespace, vrf, vrf_secret, vrf_public)
497 }
498
499 pub fn from_secret_key(
506 namespace: Namespace,
507 vrf: Box<dyn Vrf>,
508 vrf_secret: VrfSecretKey,
509 ) -> Result<Self> {
510 let vrf_public = vrf.derive_public_key(&vrf_secret)?;
511 Ok(Self::with_secret_key(
512 namespace, vrf, vrf_secret, vrf_public,
513 ))
514 }
515
516 fn with_secret_key(
517 namespace: Namespace,
518 vrf: Box<dyn Vrf>,
519 vrf_secret: VrfSecretKey,
520 vrf_public: VrfPublicKey,
521 ) -> Self {
522 let hasher = TreeHasher::new(&namespace, vrf.suite_id());
523 Self {
524 namespace,
525 vrf,
526 vrf_secret,
527 vrf_public,
528 hasher,
529 entries: BTreeMap::new(),
530 leaves: BTreeMap::new(),
531 }
532 }
533
534 #[must_use]
536 pub fn namespace(&self) -> &Namespace {
537 &self.namespace
538 }
539
540 #[must_use]
542 pub fn vrf_public_key(&self) -> &VrfPublicKey {
543 &self.vrf_public
544 }
545
546 pub fn insert(&mut self, identity: &[u8], value: &[u8]) -> Result<()> {
553 let alpha = self.namespace.vrf_input(identity);
554 let proof = self.vrf.prove(&self.vrf_secret, &alpha)?;
555 let index = self.vrf.proof_to_output(&proof)?.index();
556
557 let (commitment, opening) =
558 crate::commitment::commit(&self.namespace.commitment_label(), value);
559
560 self.leaves.insert(index, commitment);
561 self.entries.insert(
562 identity.to_vec(),
563 DirectoryEntry {
564 value: value.to_vec(),
565 opening,
566 },
567 );
568 Ok(())
569 }
570
571 #[must_use]
574 pub fn root(&self) -> [u8; NODE_LEN] {
575 let leaves: Vec<_> = self.leaves.iter().collect();
576 self.hasher.subtree(0, &leaves)
577 }
578
579 pub fn lookup(&self, identity: &[u8]) -> Result<LookupResult> {
585 let alpha = self.namespace.vrf_input(identity);
586 let vrf_proof = self.vrf.prove(&self.vrf_secret, &alpha)?;
587 let index = self.vrf.proof_to_output(&vrf_proof)?.index();
588
589 let all: Vec<_> = self.leaves.iter().collect();
590 let mut siblings = Vec::with_capacity(TREE_DEPTH);
591 self.hasher.collect_path(0, &index, &all, &mut siblings);
592 let auth_path = AuthPath { siblings };
593
594 match self.entries.get(identity) {
595 Some(entry) => Ok(LookupResult::Present(LookupProof {
596 vrf_proof,
597 value: entry.value.clone(),
598 opening: entry.opening.clone(),
599 auth_path,
600 })),
601 None => Ok(LookupResult::Absent(AbsenceProof {
602 vrf_proof,
603 auth_path,
604 })),
605 }
606 }
607}
608
609fn verified_index(
611 vrf: &dyn Vrf,
612 namespace: &Namespace,
613 vrf_public: &VrfPublicKey,
614 identity: &[u8],
615 vrf_proof: &VrfProof,
616) -> Result<[u8; INDEX_LEN]> {
617 let alpha = namespace.vrf_input(identity);
618 match vrf.verify(vrf_public, &alpha, vrf_proof)? {
619 Some(output) => Ok(output.index()),
620 None => Err(Error::VrfProofInvalid),
621 }
622}
623
624pub fn verify_lookup(
637 vrf: &dyn Vrf,
638 namespace: &Namespace,
639 vrf_public: &VrfPublicKey,
640 root: &[u8; NODE_LEN],
641 identity: &[u8],
642 proof: &LookupProof,
643) -> Result<Vec<u8>> {
644 let index = verified_index(vrf, namespace, vrf_public, identity, &proof.vrf_proof)?;
645
646 let commitment = crate::commitment::commit_with_opening(
647 &namespace.commitment_label(),
648 &proof.value,
649 &proof.opening,
650 );
651 let hasher = TreeHasher::new(namespace, vrf.suite_id());
652 let leaf = hasher.leaf_hash(&index, &commitment);
653 let recomputed = hasher.recompute_root(&index, leaf, &proof.auth_path.siblings);
654
655 if &recomputed == root {
656 Ok(proof.value.clone())
657 } else {
658 Err(Error::ConiksRootMismatch)
659 }
660}
661
662pub fn verify_absence(
674 vrf: &dyn Vrf,
675 namespace: &Namespace,
676 vrf_public: &VrfPublicKey,
677 root: &[u8; NODE_LEN],
678 identity: &[u8],
679 proof: &AbsenceProof,
680) -> Result<()> {
681 let index = verified_index(vrf, namespace, vrf_public, identity, &proof.vrf_proof)?;
682
683 let hasher = TreeHasher::new(namespace, vrf.suite_id());
684 let recomputed = hasher.recompute_root(&index, hasher.empty_leaf(), &proof.auth_path.siblings);
685
686 if &recomputed == root {
687 Ok(())
688 } else {
689 Err(Error::ConiksRootMismatch)
690 }
691}
692
693#[cfg(all(test, not(target_arch = "wasm32")))]
694mod tests {
695 use super::*;
696 use crate::vrf::Ecvrf;
697
698 fn dir() -> ConiksDirectory {
699 ConiksDirectory::new(Namespace::parse("acme").unwrap(), Box::new(Ecvrf))
700 }
701
702 #[test]
703 fn namespace_validation() {
704 assert!(Namespace::parse("acme").is_ok());
705 assert!(Namespace::parse("mosslet").is_ok());
706 assert!(Namespace::parse("").is_err());
707 assert!(Namespace::parse("has/slash").is_err());
708 assert!(Namespace::parse("has space").is_err());
709 }
710
711 #[test]
712 fn present_lookup_verifies() {
713 let mut d = dir();
714 d.insert(b"alice", b"alice-value").unwrap();
715 d.insert(b"bob", b"bob-value").unwrap();
716 let root = d.root();
717
718 let LookupResult::Present(proof) = d.lookup(b"alice").unwrap() else {
719 panic!("alice should be present");
720 };
721 let value = verify_lookup(
722 &Ecvrf,
723 d.namespace(),
724 d.vrf_public_key(),
725 &root,
726 b"alice",
727 &proof,
728 )
729 .unwrap();
730 assert_eq!(value, b"alice-value");
731 }
732
733 #[test]
734 fn absent_lookup_verifies() {
735 let mut d = dir();
736 d.insert(b"alice", b"v").unwrap();
737 let root = d.root();
738
739 let LookupResult::Absent(proof) = d.lookup(b"carol").unwrap() else {
740 panic!("carol should be absent");
741 };
742 verify_absence(
743 &Ecvrf,
744 d.namespace(),
745 d.vrf_public_key(),
746 &root,
747 b"carol",
748 &proof,
749 )
750 .unwrap();
751 }
752
753 #[test]
754 fn empty_directory_absence_verifies() {
755 let d = dir();
756 let root = d.root();
757 let LookupResult::Absent(proof) = d.lookup(b"nobody").unwrap() else {
758 panic!("absent");
759 };
760 verify_absence(
761 &Ecvrf,
762 d.namespace(),
763 d.vrf_public_key(),
764 &root,
765 b"nobody",
766 &proof,
767 )
768 .unwrap();
769 }
770
771 #[test]
772 fn tampered_value_is_rejected() {
773 let mut d = dir();
774 d.insert(b"alice", b"real").unwrap();
775 let root = d.root();
776 let LookupResult::Present(mut proof) = d.lookup(b"alice").unwrap() else {
777 panic!("present");
778 };
779 proof.value = b"forged".to_vec();
780 assert_eq!(
781 verify_lookup(
782 &Ecvrf,
783 d.namespace(),
784 d.vrf_public_key(),
785 &root,
786 b"alice",
787 &proof
788 ),
789 Err(Error::ConiksRootMismatch)
790 );
791 }
792
793 #[test]
794 fn absence_proof_for_present_identity_is_rejected() {
795 let mut d = dir();
798 d.insert(b"alice", b"v").unwrap();
799 let root = d.root();
800 let LookupResult::Present(present) = d.lookup(b"alice").unwrap() else {
801 panic!("present");
802 };
803 let forged = AbsenceProof {
805 vrf_proof: VrfProof::from_bytes(present.to_bytes()[5..5].to_vec()),
806 auth_path: present.auth_path.clone(),
807 };
808 assert!(
811 verify_absence(
812 &Ecvrf,
813 d.namespace(),
814 d.vrf_public_key(),
815 &root,
816 b"alice",
817 &forged
818 )
819 .is_err()
820 );
821 }
822
823 #[test]
824 fn cross_namespace_proof_is_rejected() {
825 let mut d = dir();
826 d.insert(b"alice", b"v").unwrap();
827 let root = d.root();
828 let LookupResult::Present(proof) = d.lookup(b"alice").unwrap() else {
829 panic!("present");
830 };
831 let other = Namespace::parse("evil").unwrap();
834 assert!(
835 verify_lookup(&Ecvrf, &other, d.vrf_public_key(), &root, b"alice", &proof).is_err()
836 );
837 }
838
839 #[test]
840 fn wrong_root_is_rejected() {
841 let mut d = dir();
842 d.insert(b"alice", b"v").unwrap();
843 let LookupResult::Present(proof) = d.lookup(b"alice").unwrap() else {
844 panic!("present");
845 };
846 let bad_root = [0u8; NODE_LEN];
847 assert_eq!(
848 verify_lookup(
849 &Ecvrf,
850 d.namespace(),
851 d.vrf_public_key(),
852 &bad_root,
853 b"alice",
854 &proof
855 ),
856 Err(Error::ConiksRootMismatch)
857 );
858 }
859
860 #[test]
861 fn proofs_round_trip_through_bytes() {
862 let mut d = dir();
863 d.insert(b"alice", b"alice-value").unwrap();
864 let root = d.root();
865
866 let LookupResult::Present(present) = d.lookup(b"alice").unwrap() else {
867 panic!("present");
868 };
869 let reparsed = LookupProof::from_bytes(&present.to_bytes()).unwrap();
870 assert_eq!(reparsed, present);
871 assert_eq!(
872 verify_lookup(
873 &Ecvrf,
874 d.namespace(),
875 d.vrf_public_key(),
876 &root,
877 b"alice",
878 &reparsed
879 )
880 .unwrap(),
881 b"alice-value"
882 );
883
884 let LookupResult::Absent(absent) = d.lookup(b"carol").unwrap() else {
885 panic!("absent");
886 };
887 let reparsed_absent = AbsenceProof::from_bytes(&absent.to_bytes()).unwrap();
888 assert_eq!(reparsed_absent, absent);
889 verify_absence(
890 &Ecvrf,
891 d.namespace(),
892 d.vrf_public_key(),
893 &root,
894 b"carol",
895 &reparsed_absent,
896 )
897 .unwrap();
898 }
899
900 #[test]
901 fn malformed_proof_bytes_are_rejected() {
902 assert!(LookupProof::from_bytes(&[]).is_err());
903 assert!(LookupProof::from_bytes(&[TAG_ABSENCE]).is_err()); assert!(AbsenceProof::from_bytes(&[TAG_PRESENCE]).is_err()); assert!(AbsenceProof::from_bytes(&[TAG_ABSENCE, 0, 0]).is_err()); }
907
908 #[test]
909 fn index_is_namespace_scoped() {
910 let ns_a = Namespace::parse("a").unwrap();
912 let ns_b = Namespace::parse("b").unwrap();
913 assert_ne!(ns_a.vrf_input(b"alice"), ns_b.vrf_input(b"alice"));
914 }
915
916 use proptest::prelude::*;
917
918 proptest! {
919 #![proptest_config(ProptestConfig::with_cases(24))]
924
925 #[test]
926 fn random_directory_presence_and_absence(
927 present_ids in proptest::collection::vec(any::<u64>(), 1..9),
928 absent_id: u64,
929 ) {
930 prop_assume!(!present_ids.contains(&absent_id));
931 let mut d = dir();
932 for id in &present_ids {
933 d.insert(&id.to_be_bytes(), format!("v{id}").as_bytes()).unwrap();
934 }
935 let root = d.root();
936
937 for id in &present_ids {
939 let key = id.to_be_bytes();
940 let LookupResult::Present(proof) = d.lookup(&key).unwrap() else {
941 prop_assert!(false, "expected present");
942 unreachable!();
943 };
944 let value = verify_lookup(
945 &Ecvrf, d.namespace(), d.vrf_public_key(), &root, &key, &proof,
946 ).unwrap();
947 prop_assert_eq!(value, format!("v{id}").into_bytes());
948 }
949
950 let key = absent_id.to_be_bytes();
952 let LookupResult::Absent(proof) = d.lookup(&key).unwrap() else {
953 prop_assert!(false, "expected absent");
954 unreachable!();
955 };
956 verify_absence(
957 &Ecvrf, d.namespace(), d.vrf_public_key(), &root, &key, &proof,
958 ).unwrap();
959 }
960 }
961}