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 #[must_use]
72 pub const fn new(admitted: &'a [SignatureAlgorithm]) -> Self {
73 Self { admitted }
74 }
75
76 #[must_use]
78 pub const fn admits(&self, algorithm: SignatureAlgorithm) -> bool {
79 let mut index = 0;
80 while index < self.admitted.len() {
81 if self.admitted[index].eq_const(algorithm) {
82 return true;
83 }
84 index += 1;
85 }
86 false
87 }
88}
89
90impl SignatureAlgorithm {
91 const fn eq_const(self, other: Self) -> bool {
92 match (self, other) {
93 (Self::Ed25519, Self::Ed25519) => true,
94 (Self::Ed25519, Self::MlDsa65) => false,
95 (Self::Ed25519, Self::SlhDsaSha2_128s) => false,
96 (Self::Ed25519, Self::HybridEd25519MlDsa65) => false,
97 (Self::MlDsa65, Self::Ed25519) => false,
98 (Self::MlDsa65, Self::MlDsa65) => true,
99 (Self::MlDsa65, Self::SlhDsaSha2_128s) => false,
100 (Self::MlDsa65, Self::HybridEd25519MlDsa65) => false,
101 (Self::SlhDsaSha2_128s, Self::Ed25519) => false,
102 (Self::SlhDsaSha2_128s, Self::MlDsa65) => false,
103 (Self::SlhDsaSha2_128s, Self::SlhDsaSha2_128s) => true,
104 (Self::SlhDsaSha2_128s, Self::HybridEd25519MlDsa65) => false,
105 (Self::HybridEd25519MlDsa65, Self::Ed25519) => false,
106 (Self::HybridEd25519MlDsa65, Self::MlDsa65) => false,
107 (Self::HybridEd25519MlDsa65, Self::SlhDsaSha2_128s) => false,
108 (Self::HybridEd25519MlDsa65, Self::HybridEd25519MlDsa65) => true,
109 }
110 }
111}
112
113#[derive(Clone, Copy, Eq, PartialEq)]
115pub struct SignatureEnvelope<'a> {
116 key_id: Digest,
117 algorithm: SignatureAlgorithm,
118 signature: &'a [u8],
119}
120
121impl<'a> SignatureEnvelope<'a> {
122 pub fn new(
124 key_id: Digest,
125 algorithm: SignatureAlgorithm,
126 signature: &'a [u8],
127 limits: WireLimits,
128 ) -> Result<Self, VerificationError> {
129 let envelope = Self {
130 key_id,
131 algorithm,
132 signature,
133 };
134 match envelope.validate(limits) {
135 Ok(()) => Ok(envelope),
136 Err(error) => Err(error),
137 }
138 }
139
140 pub fn validate(&self, limits: WireLimits) -> Result<(), VerificationError> {
142 if self.key_id.is_zero() {
143 return Err(VerificationError::EmptyKeyId);
144 }
145 if self.signature.is_empty() {
146 return Err(VerificationError::EmptySignature);
147 }
148 if self.signature.len() > limits.maximum_message_len() {
149 return Err(VerificationError::SignatureTooLarge);
150 }
151 if self.signature.len() != self.algorithm.expected_signature_len() {
152 return Err(VerificationError::InvalidSignature);
153 }
154 Ok(())
155 }
156
157 #[must_use]
159 pub const fn key_id(&self) -> Digest {
160 self.key_id
161 }
162
163 #[must_use]
165 pub const fn algorithm(&self) -> SignatureAlgorithm {
166 self.algorithm
167 }
168
169 #[must_use]
171 pub const fn signature(&self) -> &'a [u8] {
172 self.signature
173 }
174}
175
176impl<'a> fmt::Debug for SignatureEnvelope<'a> {
177 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
178 formatter
179 .debug_struct("SignatureEnvelope")
180 .field("key_id", &self.key_id)
181 .field("algorithm", &self.algorithm)
182 .field(
183 "signature",
184 &format_args!("[{} bytes]", self.signature.len()),
185 )
186 .finish()
187 }
188}
189
190#[derive(Clone, Copy, Debug, Eq, PartialEq)]
192pub struct SignedEnvelope<'a, T> {
193 payload: T,
194 signature: SignatureEnvelope<'a>,
195}
196
197impl<'a, T> SignedEnvelope<'a, T> {
198 #[must_use]
200 pub const fn new(payload: T, signature: SignatureEnvelope<'a>) -> Self {
201 Self { payload, signature }
202 }
203
204 pub fn verify_detached_bytes<V: Verifier>(
210 &self,
211 verifier: &V,
212 algorithm_policy: &AlgorithmPolicy<'_>,
213 canonical_payload: &[u8],
214 limits: WireLimits,
215 ) -> Result<(), VerificationError> {
216 if !algorithm_policy.admits(self.signature.algorithm) {
217 return Err(VerificationError::AlgorithmNotAdmitted);
218 }
219 self.signature.validate(limits)?;
220 if canonical_payload.len() > limits.maximum_message_len() {
221 return Err(VerificationError::PayloadTooLarge);
222 }
223 match self.signature.algorithm {
224 SignatureAlgorithm::HybridEd25519MlDsa65 => verifier
225 .verify_hybrid(&self.signature, canonical_payload)
226 .map(|_| ()),
227 SignatureAlgorithm::Ed25519
228 | SignatureAlgorithm::MlDsa65
229 | SignatureAlgorithm::SlhDsaSha2_128s => {
230 verifier.verify(&self.signature, canonical_payload)
231 }
232 }
233 }
234
235 #[must_use]
237 pub const fn payload(&self) -> &T {
238 &self.payload
239 }
240
241 #[must_use]
243 pub const fn signature(&self) -> SignatureEnvelope<'a> {
244 self.signature
245 }
246}
247
248#[derive(Clone, Copy, Debug, Eq, PartialEq)]
250pub struct HybridVerified(());
251
252pub trait HybridVerifier {
254 fn verify_ed25519(
256 &self,
257 ed25519_signature: &[u8],
258 canonical_payload: &[u8],
259 ) -> Result<(), VerificationError>;
260
261 fn verify_ml_dsa_65(
263 &self,
264 ml_dsa_65_signature: &[u8],
265 canonical_payload: &[u8],
266 ) -> Result<(), VerificationError>;
267
268 fn verify_hybrid(
270 &self,
271 envelope: &SignatureEnvelope<'_>,
272 canonical_payload: &[u8],
273 ) -> Result<HybridVerified, VerificationError> {
274 let (ed25519, ml_dsa_65) = envelope
275 .algorithm()
276 .split_hybrid(envelope.signature())
277 .ok_or(VerificationError::InvalidSignature)?;
278 self.verify_ed25519(ed25519, canonical_payload)?;
279 self.verify_ml_dsa_65(ml_dsa_65, canonical_payload)?;
280 Ok(HybridVerified(()))
281 }
282}
283
284pub trait Verifier: HybridVerifier {
286 fn verify(
288 &self,
289 envelope: &SignatureEnvelope<'_>,
290 canonical_payload: &[u8],
291 ) -> Result<(), VerificationError>;
292}