Skip to main content

bcx_core/
lib.rs

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}