1#![no_std]
2#![doc = "Core identifiers and validation primitives for BCX."]
3
4#[cfg(test)]
5extern crate std;
6
7mod error;
8mod ids;
9
10pub use error::ValidationError;
11pub use ids::{
12 CapabilityRef, CheckpointId, Digest, EventId, NativeBindingId, Nonce, OperationSequence,
13 PolicyEpoch, PolicyId, ProfileId, ProofSuiteId, RealmId, StatementId, SubjectId,
14};
15
16#[cfg(test)]
17mod tests {
18 use super::*;
19 use std::{format, string::String};
20
21 #[test]
22 fn zero_digest_is_detected() {
23 let digest = Digest::new([0; Digest::LEN]);
24 assert!(digest.is_zero());
25 }
26
27 #[test]
28 fn digest_constant_shape_equality_matches_structural_equality() {
29 let left = Digest::new([7; Digest::LEN]);
30 let same = Digest::new([7; Digest::LEN]);
31 let different = Digest::new([8; Digest::LEN]);
32
33 assert!(left.ct_eq(&same));
34 assert!(!left.ct_eq(&different));
35 }
36
37 #[test]
38 fn nonce_debug_is_redacted() {
39 let nonce = Nonce::new([3; Nonce::LEN]);
40
41 assert_eq!(
42 nonce.map(|value| format!("{value:?}")),
43 Ok(String::from("Nonce(..)"))
44 );
45 }
46
47 #[test]
48 fn nonce_rejects_zero_and_compares_constant_shape() {
49 let left = Nonce::new([9; Nonce::LEN]);
50 let same = Nonce::new([9; Nonce::LEN]);
51 let different = Nonce::new([8; Nonce::LEN]);
52
53 assert_eq!(Nonce::new([0; Nonce::LEN]), Err(ValidationError::ZeroValue));
54 assert!(matches!((&left, &same), (Ok(a), Ok(b)) if a.ct_eq(b)));
55 assert!(matches!((&left, &different), (Ok(a), Ok(b)) if !a.ct_eq(b)));
56 }
57
58 #[test]
59 fn operation_sequence_rejects_zero() {
60 assert_eq!(OperationSequence::new(0), Err(ValidationError::ZeroValue));
61 assert_eq!(OperationSequence::new(7).map(OperationSequence::get), Ok(7));
62 }
63
64 #[test]
65 fn operation_sequence_checks_monotonic_successor() {
66 let previous = OperationSequence::new(7);
67 let next = previous.and_then(OperationSequence::next);
68
69 assert_eq!(next.map(OperationSequence::get), Ok(8));
70 assert!(matches!(
71 (OperationSequence::new(8), OperationSequence::new(7)),
72 (Ok(current), Ok(previous)) if current.immediately_follows(previous)
73 ));
74 assert_eq!(
75 OperationSequence::new(u64::MAX).and_then(OperationSequence::next),
76 Err(ValidationError::TooLarge)
77 );
78 }
79
80 #[test]
81 fn statement_and_checkpoint_ids_require_digest_length() {
82 assert_eq!(StatementId::new(&[]), Err(ValidationError::Empty));
83 assert_eq!(
84 StatementId::new(&[1; Digest::LEN - 1]),
85 Err(ValidationError::Malformed)
86 );
87 assert_eq!(
88 CheckpointId::new(&[1; Digest::LEN + 1]),
89 Err(ValidationError::TooLarge)
90 );
91
92 let statement = StatementId::new(&[1; Digest::LEN]);
93 assert_eq!(statement.map(|id| id.len()), Ok(Digest::LEN));
94 }
95
96 #[test]
97 fn public_identifier_constructors_reject_zero_values() {
98 assert_eq!(
99 StatementId::new(&[0; Digest::LEN]),
100 Err(ValidationError::ZeroValue)
101 );
102 assert_eq!(SubjectId::new(&[0]), Err(ValidationError::ZeroValue));
103 assert_eq!(RealmId::new(&[0]), Err(ValidationError::ZeroValue));
104 assert_eq!(ProfileId::new(&[0]), Err(ValidationError::ZeroValue));
105 assert_eq!(ProofSuiteId::new(&[0]), Err(ValidationError::ZeroValue));
106 assert_eq!(PolicyId::new(&[0]), Err(ValidationError::ZeroValue));
107 assert_eq!(
108 CheckpointId::new(&[0; Digest::LEN]),
109 Err(ValidationError::ZeroValue)
110 );
111 assert_eq!(NativeBindingId::new(&[0]), Err(ValidationError::ZeroValue));
112 }
113
114 #[test]
115 fn bounded_identifiers_validate_lengths_and_preserve_bytes() -> Result<(), ValidationError> {
116 let subject = SubjectId::new(b"subject:invoice:123")?;
117 let realm = RealmId::new(b"realm:valkyoth")?;
118 let profile = ProfileId::new(b"bcx-core")?;
119 let proof_suite = ProofSuiteId::new(b"ed25519")?;
120 let policy = PolicyId::new(b"strict")?;
121 let native = NativeBindingId::new(b"fluxheim/request")?;
122
123 assert_eq!(subject.as_bytes(), b"subject:invoice:123");
124 assert_eq!(realm.as_bytes(), b"realm:valkyoth");
125 assert_eq!(profile.as_bytes(), b"bcx-core");
126 assert_eq!(proof_suite.as_bytes(), b"ed25519");
127 assert_eq!(policy.as_bytes(), b"strict");
128 assert_eq!(native.as_bytes(), b"fluxheim/request");
129 assert_eq!(
130 SubjectId::new(&[1; SubjectId::MAX_LEN + 1]),
131 Err(ValidationError::TooLarge)
132 );
133 Ok(())
134 }
135}