Skip to main content

bcx_core/
ids.rs

1use crate::ValidationError;
2use subtle::ConstantTimeEq;
3use zeroize::Zeroize;
4
5/// Fixed-width digest used for protocol commitments.
6#[derive(Clone, Copy, Eq)]
7pub struct Digest([u8; Self::LEN]);
8
9impl Digest {
10    /// Digest byte length for the first BCX profile.
11    pub const LEN: usize = 32;
12
13    /// Creates a digest from raw bytes.
14    #[must_use]
15    pub const fn new(bytes: [u8; Self::LEN]) -> Self {
16        Self(bytes)
17    }
18
19    /// Returns the digest as bytes.
20    #[must_use]
21    pub const fn as_bytes(&self) -> &[u8; Self::LEN] {
22        &self.0
23    }
24
25    /// Returns true when every byte is zero.
26    #[must_use]
27    pub fn is_zero(&self) -> bool {
28        bool::from(self.0.ct_eq(&[0u8; Self::LEN]))
29    }
30
31    /// Compares two digests without data-dependent early exit.
32    ///
33    /// This is also used by `PartialEq` to avoid byte-by-byte early exit.
34    #[must_use]
35    pub fn ct_eq(&self, other: &Self) -> bool {
36        bool::from(self.0.ct_eq(&other.0))
37    }
38}
39
40impl PartialEq for Digest {
41    fn eq(&self, other: &Self) -> bool {
42        self.ct_eq(other)
43    }
44}
45
46impl core::hash::Hash for Digest {
47    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
48        core::hash::Hash::hash(&self.0, state);
49    }
50}
51
52impl core::fmt::Debug for Digest {
53    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
54        formatter.write_str("Digest(..)")
55    }
56}
57
58/// Globally unique event identifier within a trust domain.
59#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
60pub struct EventId(Digest);
61
62impl EventId {
63    /// Creates an event identifier when the digest is non-zero.
64    pub fn new(digest: Digest) -> Result<Self, ValidationError> {
65        if digest.is_zero() {
66            Err(ValidationError::ZeroValue)
67        } else {
68            Ok(Self(digest))
69        }
70    }
71
72    /// Returns the underlying digest commitment.
73    #[must_use]
74    pub const fn digest(&self) -> Digest {
75        self.0
76    }
77}
78
79/// Reference to a capability object or capability commitment.
80#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
81pub struct CapabilityRef(Digest);
82
83impl CapabilityRef {
84    /// Creates a capability reference when the digest is non-zero.
85    pub fn new(digest: Digest) -> Result<Self, ValidationError> {
86        if digest.is_zero() {
87            Err(ValidationError::ZeroValue)
88        } else {
89            Ok(Self(digest))
90        }
91    }
92
93    /// Returns the underlying digest commitment.
94    #[must_use]
95    pub const fn digest(&self) -> Digest {
96        self.0
97    }
98}
99
100/// Reference to a policy epoch.
101#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
102pub struct PolicyEpoch(Digest);
103
104impl PolicyEpoch {
105    /// Creates a policy epoch when the digest is non-zero.
106    pub fn new(digest: Digest) -> Result<Self, ValidationError> {
107        if digest.is_zero() {
108            Err(ValidationError::ZeroValue)
109        } else {
110            Ok(Self(digest))
111        }
112    }
113
114    /// Returns the underlying digest commitment.
115    #[must_use]
116    pub const fn digest(&self) -> Digest {
117        self.0
118    }
119}
120
121/// Per-issuer operation sequence used for replay detection.
122#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
123pub struct OperationSequence(u64);
124
125impl OperationSequence {
126    /// Creates a non-zero operation sequence.
127    pub const fn new(value: u64) -> Result<Self, ValidationError> {
128        if value == 0 {
129            Err(ValidationError::ZeroValue)
130        } else {
131            Ok(Self(value))
132        }
133    }
134
135    /// Returns the raw sequence number.
136    #[must_use]
137    pub const fn get(self) -> u64 {
138        self.0
139    }
140
141    /// Returns the next sequence value.
142    pub const fn next(self) -> Result<Self, ValidationError> {
143        match self.0.checked_add(1) {
144            Some(value) => Ok(Self(value)),
145            None => Err(ValidationError::TooLarge),
146        }
147    }
148
149    /// Returns true when this sequence immediately follows `previous`.
150    #[must_use]
151    pub const fn immediately_follows(self, previous: Self) -> bool {
152        match previous.0.checked_add(1) {
153            Some(expected) => self.0 == expected,
154            None => false,
155        }
156    }
157}
158
159/// Nonce bytes carried by signed invocations and WHY queries.
160///
161/// `Nonce` intentionally does not implement `Clone` or `Copy`; duplicating
162/// nonce bytes increases the number of plaintext copies that must be cleared.
163#[derive(Eq)]
164pub struct Nonce([u8; Self::LEN]);
165
166impl Nonce {
167    /// Nonce byte length for the first BCX profile.
168    pub const LEN: usize = 16;
169
170    /// Creates a nonce from non-zero raw bytes.
171    pub fn new(bytes: [u8; Self::LEN]) -> Result<Self, ValidationError> {
172        if bool::from(bytes.ct_eq(&[0u8; Self::LEN])) {
173            Err(ValidationError::ZeroValue)
174        } else {
175            Ok(Self(bytes))
176        }
177    }
178
179    /// Returns the nonce as bytes.
180    #[must_use]
181    pub const fn as_bytes(&self) -> &[u8; Self::LEN] {
182        &self.0
183    }
184
185    /// Compares two nonces without data-dependent early exit.
186    #[must_use]
187    pub fn ct_eq(&self, other: &Self) -> bool {
188        bool::from(self.0.ct_eq(&other.0))
189    }
190}
191
192impl PartialEq for Nonce {
193    fn eq(&self, other: &Self) -> bool {
194        self.ct_eq(other)
195    }
196}
197
198impl core::hash::Hash for Nonce {
199    /// Hashes nonce bytes for non-adversarial collection use.
200    ///
201    /// This hash operation is not constant-time. Replay caches for
202    /// high-assurance deployments should use a keyed structure that accounts
203    /// for timing and bucket-collision behavior instead of treating `Hash` as
204    /// a security boundary.
205    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
206        core::hash::Hash::hash(&self.0, state);
207    }
208}
209
210impl Drop for Nonce {
211    fn drop(&mut self) {
212        self.0.zeroize();
213    }
214}
215
216impl core::fmt::Debug for Nonce {
217    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
218        formatter.write_str("Nonce(..)")
219    }
220}