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
104impl SignatureAlgorithm {
105 const fn eq_const(self, other: Self) -> bool {
106 match (self, other) {
107 (Self::Ed25519, Self::Ed25519) => true,
108 (Self::Ed25519, Self::MlDsa65) => false,
109 (Self::Ed25519, Self::SlhDsaSha2_128s) => false,
110 (Self::Ed25519, Self::HybridEd25519MlDsa65) => false,
111 (Self::MlDsa65, Self::Ed25519) => false,
112 (Self::MlDsa65, Self::MlDsa65) => true,
113 (Self::MlDsa65, Self::SlhDsaSha2_128s) => false,
114 (Self::MlDsa65, Self::HybridEd25519MlDsa65) => false,
115 (Self::SlhDsaSha2_128s, Self::Ed25519) => false,
116 (Self::SlhDsaSha2_128s, Self::MlDsa65) => false,
117 (Self::SlhDsaSha2_128s, Self::SlhDsaSha2_128s) => true,
118 (Self::SlhDsaSha2_128s, Self::HybridEd25519MlDsa65) => false,
119 (Self::HybridEd25519MlDsa65, Self::Ed25519) => false,
120 (Self::HybridEd25519MlDsa65, Self::MlDsa65) => false,
121 (Self::HybridEd25519MlDsa65, Self::SlhDsaSha2_128s) => false,
122 (Self::HybridEd25519MlDsa65, Self::HybridEd25519MlDsa65) => true,
123 }
124 }
125}
126
127#[derive(Clone, Copy, Eq, PartialEq)]
129pub struct SignatureEnvelope<'a> {
130 key_id: Digest,
131 algorithm: SignatureAlgorithm,
132 signature: &'a [u8],
133}
134
135impl<'a> SignatureEnvelope<'a> {
136 pub fn new(
138 key_id: Digest,
139 algorithm: SignatureAlgorithm,
140 signature: &'a [u8],
141 limits: WireLimits,
142 ) -> Result<Self, VerificationError> {
143 let envelope = Self {
144 key_id,
145 algorithm,
146 signature,
147 };
148 match envelope.validate(limits) {
149 Ok(()) => Ok(envelope),
150 Err(error) => Err(error),
151 }
152 }
153
154 pub(crate) fn validate(&self, limits: WireLimits) -> Result<(), VerificationError> {
156 if self.key_id.is_zero() {
157 return Err(VerificationError::EmptyKeyId);
158 }
159 if self.signature.is_empty() {
160 return Err(VerificationError::EmptySignature);
161 }
162 if self.signature.len() > limits.maximum_message_len() {
163 return Err(VerificationError::SignatureTooLarge);
164 }
165 if self.signature.len() != self.algorithm.expected_signature_len() {
166 return Err(VerificationError::InvalidSignature);
167 }
168 Ok(())
169 }
170
171 #[must_use]
173 pub const fn key_id(&self) -> Digest {
174 self.key_id
175 }
176
177 #[must_use]
179 pub const fn algorithm(&self) -> SignatureAlgorithm {
180 self.algorithm
181 }
182
183 #[must_use]
185 pub const fn signature(&self) -> &'a [u8] {
186 self.signature
187 }
188}
189
190impl<'a> fmt::Debug for SignatureEnvelope<'a> {
191 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
192 formatter
193 .debug_struct("SignatureEnvelope")
194 .field("key_id", &self.key_id)
195 .field("algorithm", &self.algorithm)
196 .field(
197 "signature",
198 &format_args!("[{} bytes]", self.signature.len()),
199 )
200 .finish()
201 }
202}
203
204#[derive(Clone, Copy, Debug, Eq, PartialEq)]
206pub struct SignedEnvelope<'a, T> {
207 payload: T,
208 signature: SignatureEnvelope<'a>,
209}
210
211impl<'a, T> SignedEnvelope<'a, T> {
212 #[must_use]
214 pub const fn new(payload: T, signature: SignatureEnvelope<'a>) -> Self {
215 Self { payload, signature }
216 }
217
218 pub fn verify_detached_bytes<V: Verifier>(
224 &self,
225 verifier: &V,
226 algorithm_policy: &AlgorithmPolicy<'_>,
227 canonical_payload: &[u8],
228 limits: WireLimits,
229 ) -> Result<(), VerificationError> {
230 if !algorithm_policy.admits(self.signature.algorithm) {
231 return Err(VerificationError::AlgorithmNotAdmitted);
232 }
233 self.signature.validate(limits)?;
234 if canonical_payload.len() > limits.maximum_message_len() {
235 return Err(VerificationError::PayloadTooLarge);
236 }
237 match self.signature.algorithm {
238 SignatureAlgorithm::HybridEd25519MlDsa65 => verifier
239 .verify_hybrid(&self.signature, canonical_payload)
240 .map(|_| ()),
241 SignatureAlgorithm::Ed25519
242 | SignatureAlgorithm::MlDsa65
243 | SignatureAlgorithm::SlhDsaSha2_128s => {
244 verifier.verify(&self.signature, canonical_payload)
245 }
246 }
247 }
248
249 #[must_use]
251 pub const fn payload(&self) -> &T {
252 &self.payload
253 }
254
255 #[must_use]
257 pub const fn signature(&self) -> SignatureEnvelope<'a> {
258 self.signature
259 }
260}
261
262#[derive(Clone, Copy, Debug, Eq, PartialEq)]
264pub struct HybridVerified(());
265
266pub trait HybridVerifier {
268 fn verify_ed25519(
270 &self,
271 ed25519_signature: &[u8],
272 canonical_payload: &[u8],
273 ) -> Result<(), VerificationError>;
274
275 fn verify_ml_dsa_65(
277 &self,
278 ml_dsa_65_signature: &[u8],
279 canonical_payload: &[u8],
280 ) -> Result<(), VerificationError>;
281
282 fn verify_hybrid(
289 &self,
290 envelope: &SignatureEnvelope<'_>,
291 canonical_payload: &[u8],
292 ) -> Result<HybridVerified, VerificationError> {
293 let (ed25519, ml_dsa_65) = envelope
294 .algorithm()
295 .split_hybrid(envelope.signature())
296 .ok_or(VerificationError::InvalidSignature)?;
297 let ed25519_ok = self.verify_ed25519(ed25519, canonical_payload).is_ok();
298 let ml_dsa_65_ok = self.verify_ml_dsa_65(ml_dsa_65, canonical_payload).is_ok();
299 if ed25519_ok & ml_dsa_65_ok {
300 Ok(HybridVerified(()))
301 } else {
302 Err(VerificationError::InvalidSignature)
303 }
304 }
305}
306
307pub trait Verifier: HybridVerifier {
309 fn verify(
311 &self,
312 envelope: &SignatureEnvelope<'_>,
313 canonical_payload: &[u8],
314 ) -> Result<(), VerificationError>;
315}