1use crate::VerificationError;
2use bcx_core::Digest;
3use bcx_wire::WireLimits;
4use core::fmt;
5
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub enum SignatureAlgorithm {
9 Ed25519,
11 MlDsa65,
13 SlhDsaSha2_128s,
15 HybridEd25519MlDsa65,
20}
21
22impl SignatureAlgorithm {
23 pub const ED25519_SIGNATURE_LEN: usize = 64;
25 pub const ML_DSA_65_SIGNATURE_LEN: usize = 3_293;
27 pub const SLH_DSA_SHA2_128S_SIGNATURE_LEN: usize = 7_856;
29 pub const HYBRID_ED25519_ML_DSA_65_SIGNATURE_LEN: usize =
31 Self::ED25519_SIGNATURE_LEN + Self::ML_DSA_65_SIGNATURE_LEN;
32
33 #[must_use]
35 pub const fn expected_signature_len(self) -> usize {
36 match self {
37 Self::Ed25519 => Self::ED25519_SIGNATURE_LEN,
38 Self::MlDsa65 => Self::ML_DSA_65_SIGNATURE_LEN,
39 Self::SlhDsaSha2_128s => Self::SLH_DSA_SHA2_128S_SIGNATURE_LEN,
40 Self::HybridEd25519MlDsa65 => Self::HYBRID_ED25519_ML_DSA_65_SIGNATURE_LEN,
41 }
42 }
43
44 #[must_use]
49 pub fn split_hybrid(self, signature: &[u8]) -> Option<(&[u8], &[u8])> {
50 match self {
51 Self::HybridEd25519MlDsa65
52 if signature.len() == Self::HYBRID_ED25519_ML_DSA_65_SIGNATURE_LEN =>
53 {
54 Some(signature.split_at(Self::ED25519_SIGNATURE_LEN))
55 }
56 Self::Ed25519 | Self::MlDsa65 | Self::SlhDsaSha2_128s | Self::HybridEd25519MlDsa65 => {
57 None
58 }
59 }
60 }
61}
62
63#[derive(Clone, Copy, Debug, Eq, PartialEq)]
65pub struct AlgorithmPolicy<'a> {
66 admitted: &'a [SignatureAlgorithm],
67}
68
69impl<'a> AlgorithmPolicy<'a> {
70 pub const fn new(admitted: &'a [SignatureAlgorithm]) -> Result<Self, VerificationError> {
77 if admitted.is_empty() {
78 Err(VerificationError::EmptyAlgorithmPolicy)
79 } else {
80 Ok(Self { admitted })
81 }
82 }
83
84 #[must_use]
86 pub const fn deny_all() -> Self {
87 Self { admitted: &[] }
88 }
89
90 #[must_use]
92 pub const fn admits(&self, algorithm: SignatureAlgorithm) -> bool {
93 let mut index = 0;
94 while index < self.admitted.len() {
95 if self.admitted[index].eq_const(algorithm) {
96 return true;
97 }
98 index += 1;
99 }
100 false
101 }
102}
103
104#[derive(Clone, Copy, Debug, Eq, PartialEq)]
109pub struct ExactAlgorithmPolicy {
110 admitted: SignatureAlgorithm,
111}
112
113impl ExactAlgorithmPolicy {
114 #[must_use]
116 pub const fn new(admitted: SignatureAlgorithm) -> Self {
117 Self { admitted }
118 }
119
120 #[must_use]
122 pub const fn algorithm(self) -> SignatureAlgorithm {
123 self.admitted
124 }
125
126 #[must_use]
128 pub const fn admits(self, algorithm: SignatureAlgorithm) -> bool {
129 self.admitted.eq_const(algorithm)
130 }
131}
132
133impl SignatureAlgorithm {
134 const fn eq_const(self, other: Self) -> bool {
135 match (self, other) {
136 (Self::Ed25519, Self::Ed25519) => true,
137 (Self::Ed25519, Self::MlDsa65) => false,
138 (Self::Ed25519, Self::SlhDsaSha2_128s) => false,
139 (Self::Ed25519, Self::HybridEd25519MlDsa65) => false,
140 (Self::MlDsa65, Self::Ed25519) => false,
141 (Self::MlDsa65, Self::MlDsa65) => true,
142 (Self::MlDsa65, Self::SlhDsaSha2_128s) => false,
143 (Self::MlDsa65, Self::HybridEd25519MlDsa65) => false,
144 (Self::SlhDsaSha2_128s, Self::Ed25519) => false,
145 (Self::SlhDsaSha2_128s, Self::MlDsa65) => false,
146 (Self::SlhDsaSha2_128s, Self::SlhDsaSha2_128s) => true,
147 (Self::SlhDsaSha2_128s, Self::HybridEd25519MlDsa65) => false,
148 (Self::HybridEd25519MlDsa65, Self::Ed25519) => false,
149 (Self::HybridEd25519MlDsa65, Self::MlDsa65) => false,
150 (Self::HybridEd25519MlDsa65, Self::SlhDsaSha2_128s) => false,
151 (Self::HybridEd25519MlDsa65, Self::HybridEd25519MlDsa65) => true,
152 }
153 }
154}
155
156#[derive(Clone, Copy, Eq, PartialEq)]
158pub struct SignatureEnvelope<'a> {
159 key_id: Digest,
160 algorithm: SignatureAlgorithm,
161 signature: &'a [u8],
162}
163
164impl<'a> SignatureEnvelope<'a> {
165 pub fn new(
167 key_id: Digest,
168 algorithm: SignatureAlgorithm,
169 signature: &'a [u8],
170 limits: WireLimits,
171 ) -> Result<Self, VerificationError> {
172 let envelope = Self {
173 key_id,
174 algorithm,
175 signature,
176 };
177 match envelope.validate(limits) {
178 Ok(()) => Ok(envelope),
179 Err(error) => Err(error),
180 }
181 }
182
183 pub(crate) fn validate(&self, limits: WireLimits) -> Result<(), VerificationError> {
185 if self.key_id.is_zero() {
186 return Err(VerificationError::EmptyKeyId);
187 }
188 if self.signature.is_empty() {
189 return Err(VerificationError::EmptySignature);
190 }
191 if self.signature.len() > limits.maximum_message_len() {
192 return Err(VerificationError::SignatureTooLarge);
193 }
194 if self.signature.len() != self.algorithm.expected_signature_len() {
195 return Err(VerificationError::InvalidSignature);
196 }
197 Ok(())
198 }
199
200 #[must_use]
202 pub const fn key_id(&self) -> Digest {
203 self.key_id
204 }
205
206 #[must_use]
208 pub const fn algorithm(&self) -> SignatureAlgorithm {
209 self.algorithm
210 }
211
212 #[must_use]
214 pub const fn signature(&self) -> &'a [u8] {
215 self.signature
216 }
217}
218
219impl<'a> fmt::Debug for SignatureEnvelope<'a> {
220 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
221 formatter
222 .debug_struct("SignatureEnvelope")
223 .field("key_id", &self.key_id)
224 .field("algorithm", &self.algorithm)
225 .field(
226 "signature",
227 &format_args!("[{} bytes]", self.signature.len()),
228 )
229 .finish()
230 }
231}
232
233#[derive(Clone, Copy, Debug, Eq, PartialEq)]
235pub struct SignedEnvelope<'a, T> {
236 payload: T,
237 signature: SignatureEnvelope<'a>,
238}
239
240impl<'a, T> SignedEnvelope<'a, T> {
241 #[must_use]
243 pub const fn new(payload: T, signature: SignatureEnvelope<'a>) -> Self {
244 Self { payload, signature }
245 }
246
247 pub fn verify_detached_bytes<V: Verifier>(
253 &self,
254 verifier: &V,
255 algorithm_policy: &AlgorithmPolicy<'_>,
256 canonical_payload: &[u8],
257 limits: WireLimits,
258 ) -> Result<(), VerificationError> {
259 if !algorithm_policy.admits(self.signature.algorithm) {
260 return Err(VerificationError::AlgorithmNotAdmitted);
261 }
262 self.verify_admitted_detached_bytes(verifier, canonical_payload, limits)
263 }
264
265 pub fn verify_detached_bytes_exact<V: Verifier>(
270 &self,
271 verifier: &V,
272 algorithm_policy: ExactAlgorithmPolicy,
273 canonical_payload: &[u8],
274 limits: WireLimits,
275 ) -> Result<(), VerificationError> {
276 if !algorithm_policy.admits(self.signature.algorithm) {
277 return Err(VerificationError::AlgorithmNotAdmitted);
278 }
279 self.verify_admitted_detached_bytes(verifier, canonical_payload, limits)
280 }
281
282 fn verify_admitted_detached_bytes<V: Verifier>(
283 &self,
284 verifier: &V,
285 canonical_payload: &[u8],
286 limits: WireLimits,
287 ) -> Result<(), VerificationError> {
288 self.signature.validate(limits)?;
289 if canonical_payload.len() > limits.maximum_message_len() {
290 return Err(VerificationError::PayloadTooLarge);
291 }
292 match self.signature.algorithm {
293 SignatureAlgorithm::HybridEd25519MlDsa65 => verifier
294 .verify_hybrid(&self.signature, canonical_payload)
295 .map(|_| ()),
296 SignatureAlgorithm::Ed25519
297 | SignatureAlgorithm::MlDsa65
298 | SignatureAlgorithm::SlhDsaSha2_128s => {
299 verifier.verify(&self.signature, canonical_payload)
300 }
301 }
302 }
303
304 #[must_use]
306 pub const fn payload(&self) -> &T {
307 &self.payload
308 }
309
310 #[must_use]
312 pub const fn signature(&self) -> SignatureEnvelope<'a> {
313 self.signature
314 }
315}
316
317#[derive(Clone, Copy, Debug, Eq, PartialEq)]
319pub struct HybridVerified(());
320
321pub trait HybridVerifier {
323 fn verify_ed25519(
325 &self,
326 ed25519_signature: &[u8],
327 canonical_payload: &[u8],
328 ) -> Result<(), VerificationError>;
329
330 fn verify_ml_dsa_65(
332 &self,
333 ml_dsa_65_signature: &[u8],
334 canonical_payload: &[u8],
335 ) -> Result<(), VerificationError>;
336
337 fn verify_hybrid(
344 &self,
345 envelope: &SignatureEnvelope<'_>,
346 canonical_payload: &[u8],
347 ) -> Result<HybridVerified, VerificationError> {
348 let (ed25519, ml_dsa_65) = envelope
349 .algorithm()
350 .split_hybrid(envelope.signature())
351 .ok_or(VerificationError::InvalidSignature)?;
352 let ed25519_ok = self.verify_ed25519(ed25519, canonical_payload).is_ok();
353 let ml_dsa_65_ok = self.verify_ml_dsa_65(ml_dsa_65, canonical_payload).is_ok();
354 if ed25519_ok & ml_dsa_65_ok {
355 Ok(HybridVerified(()))
356 } else {
357 Err(VerificationError::InvalidSignature)
358 }
359 }
360}
361
362pub trait Verifier: HybridVerifier {
364 fn verify(
366 &self,
367 envelope: &SignatureEnvelope<'_>,
368 canonical_payload: &[u8],
369 ) -> Result<(), VerificationError>;
370}