Skip to main content

bcx_crypto/
envelope.rs

1use crate::VerificationError;
2use bcx_core::Digest;
3
4/// Signature algorithms named by BCX metadata.
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
6pub enum SignatureAlgorithm {
7    /// Ed25519 for compact classical signatures.
8    Ed25519,
9    /// ML-DSA-65 for post-quantum-ready deployments.
10    MlDsa65,
11    /// SLH-DSA-SHA2-128s for conservative stateless signatures.
12    SlhDsaSha2_128s,
13    /// Hybrid Ed25519 plus ML-DSA-65 signature envelope.
14    HybridEd25519MlDsa65,
15}
16
17impl SignatureAlgorithm {
18    /// Returns the exact signature length admitted for this algorithm.
19    #[must_use]
20    pub const fn expected_signature_len(self) -> usize {
21        match self {
22            Self::Ed25519 => 64,
23            Self::MlDsa65 => 3_293,
24            Self::SlhDsaSha2_128s => 7_856,
25            Self::HybridEd25519MlDsa65 => 3_357,
26        }
27    }
28}
29
30/// Closed algorithm admission policy for a verification context.
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
32pub struct AlgorithmPolicy<'a> {
33    admitted: &'a [SignatureAlgorithm],
34}
35
36impl<'a> AlgorithmPolicy<'a> {
37    /// Creates an algorithm admission policy from an explicit allow-list.
38    #[must_use]
39    pub const fn new(admitted: &'a [SignatureAlgorithm]) -> Self {
40        Self { admitted }
41    }
42
43    /// Returns true when the algorithm is admitted by this policy.
44    #[must_use]
45    pub const fn admits(&self, algorithm: SignatureAlgorithm) -> bool {
46        let mut index = 0;
47        while index < self.admitted.len() {
48            if self.admitted[index].eq_const(algorithm) {
49                return true;
50            }
51            index += 1;
52        }
53        false
54    }
55}
56
57impl SignatureAlgorithm {
58    const fn eq_const(self, other: Self) -> bool {
59        matches!(
60            (self, other),
61            (Self::Ed25519, Self::Ed25519)
62                | (Self::MlDsa65, Self::MlDsa65)
63                | (Self::SlhDsaSha2_128s, Self::SlhDsaSha2_128s)
64                | (Self::HybridEd25519MlDsa65, Self::HybridEd25519MlDsa65)
65        )
66    }
67}
68
69/// Signature metadata over a canonical BCX payload.
70#[derive(Clone, Copy, Debug, Eq, PartialEq)]
71pub struct SignatureEnvelope<'a> {
72    /// Commitment to the signing key or certificate chain.
73    pub key_id: Digest,
74    /// Signature algorithm identifier.
75    pub algorithm: SignatureAlgorithm,
76    /// Raw signature bytes.
77    pub signature: &'a [u8],
78}
79
80impl SignatureEnvelope<'_> {
81    /// Validates envelope shape before algorithm dispatch.
82    pub const fn validate(&self, maximum_signature_len: usize) -> Result<(), VerificationError> {
83        if self.key_id.is_zero() {
84            return Err(VerificationError::EmptyKeyId);
85        }
86        if self.signature.is_empty() {
87            return Err(VerificationError::EmptySignature);
88        }
89        if self.signature.len() > maximum_signature_len {
90            return Err(VerificationError::SignatureTooLarge);
91        }
92        if self.signature.len() != self.algorithm.expected_signature_len() {
93            return Err(VerificationError::InvalidSignature);
94        }
95        Ok(())
96    }
97}
98
99/// Payload paired with a signature envelope.
100#[derive(Clone, Copy, Debug, Eq, PartialEq)]
101pub struct SignedEnvelope<'a, T> {
102    /// Canonical payload value.
103    pub payload: T,
104    /// Signature envelope over the canonical payload bytes.
105    pub signature: SignatureEnvelope<'a>,
106}
107
108impl<'a, T> SignedEnvelope<'a, T> {
109    /// Verifies this envelope against caller-provided canonical bytes.
110    pub fn verify_bytes<V: Verifier>(
111        &self,
112        verifier: &V,
113        algorithm_policy: &AlgorithmPolicy<'_>,
114        canonical_payload: &[u8],
115        maximum_signature_len: usize,
116    ) -> Result<(), VerificationError> {
117        if !algorithm_policy.admits(self.signature.algorithm) {
118            return Err(VerificationError::AlgorithmNotAdmitted);
119        }
120        self.signature.validate(maximum_signature_len)?;
121        verifier.verify(&self.signature, canonical_payload)
122    }
123}
124
125/// Signature verification backend boundary.
126pub trait Verifier {
127    /// Verifies one signature envelope over canonical payload bytes.
128    fn verify(
129        &self,
130        envelope: &SignatureEnvelope<'_>,
131        canonical_payload: &[u8],
132    ) -> Result<(), VerificationError>;
133}