Skip to main content

bcx_crypto/
lib.rs

1#![no_std]
2#![doc = "Crypto-agile envelope metadata for BCX."]
3
4mod envelope;
5mod error;
6
7pub use envelope::{
8    AlgorithmPolicy, HybridVerified, HybridVerifier, SignatureAlgorithm, SignatureEnvelope,
9    SignedEnvelope, Verifier,
10};
11pub use error::VerificationError;
12
13#[cfg(test)]
14mod tests {
15    use super::*;
16    use bcx_core::Digest;
17    use bcx_wire::WireLimits;
18
19    #[test]
20    fn empty_signature_is_rejected() {
21        assert_eq!(
22            SignatureEnvelope::new(
23                Digest::new([1; Digest::LEN]),
24                SignatureAlgorithm::Ed25519,
25                &[],
26                WireLimits::DEVELOPMENT,
27            ),
28            Err(VerificationError::EmptySignature)
29        );
30    }
31
32    #[test]
33    fn wrong_signature_length_is_rejected() {
34        assert_eq!(
35            SignatureEnvelope::new(
36                Digest::new([1; Digest::LEN]),
37                SignatureAlgorithm::Ed25519,
38                &[7; 63],
39                WireLimits::DEVELOPMENT,
40            ),
41            Err(VerificationError::InvalidSignature)
42        );
43    }
44
45    #[test]
46    fn hybrid_signature_split_uses_documented_layout() {
47        let signature = [5; SignatureAlgorithm::HYBRID_ED25519_ML_DSA_65_SIGNATURE_LEN];
48        let split = SignatureAlgorithm::HybridEd25519MlDsa65.split_hybrid(&signature);
49
50        assert_eq!(
51            split.map(|(ed25519, ml_dsa)| (ed25519.len(), ml_dsa.len())),
52            Some((
53                SignatureAlgorithm::ED25519_SIGNATURE_LEN,
54                SignatureAlgorithm::ML_DSA_65_SIGNATURE_LEN,
55            ))
56        );
57        assert_eq!(SignatureAlgorithm::Ed25519.split_hybrid(&signature), None);
58    }
59
60    #[test]
61    fn policy_rejects_unadmitted_algorithm_before_verifier() -> Result<(), VerificationError> {
62        struct RejectingVerifier;
63
64        impl HybridVerifier for RejectingVerifier {
65            fn verify_ed25519(
66                &self,
67                _ed25519_signature: &[u8],
68                _canonical_payload: &[u8],
69            ) -> Result<(), VerificationError> {
70                Err(VerificationError::InvalidSignature)
71            }
72
73            fn verify_ml_dsa_65(
74                &self,
75                _ml_dsa_65_signature: &[u8],
76                _canonical_payload: &[u8],
77            ) -> Result<(), VerificationError> {
78                Err(VerificationError::InvalidSignature)
79            }
80        }
81
82        impl Verifier for RejectingVerifier {
83            fn verify(
84                &self,
85                _envelope: &SignatureEnvelope<'_>,
86                _canonical_payload: &[u8],
87            ) -> Result<(), VerificationError> {
88                Err(VerificationError::InvalidSignature)
89            }
90        }
91
92        let signature = [1; 64];
93        let envelope = SignedEnvelope::new(
94            (),
95            SignatureEnvelope::new(
96                Digest::new([1; Digest::LEN]),
97                SignatureAlgorithm::Ed25519,
98                &signature,
99                WireLimits::DEVELOPMENT,
100            )?,
101        );
102        let policy = AlgorithmPolicy::new(&[SignatureAlgorithm::MlDsa65]);
103
104        assert_eq!(
105            envelope.verify_detached_bytes(
106                &RejectingVerifier,
107                &policy,
108                b"payload",
109                WireLimits::DEVELOPMENT,
110            ),
111            Err(VerificationError::AlgorithmNotAdmitted)
112        );
113        Ok(())
114    }
115
116    #[test]
117    fn detached_payload_is_bounded_by_wire_limits() -> Result<(), VerificationError> {
118        struct AcceptingVerifier;
119
120        impl HybridVerifier for AcceptingVerifier {
121            fn verify_ed25519(
122                &self,
123                _ed25519_signature: &[u8],
124                _canonical_payload: &[u8],
125            ) -> Result<(), VerificationError> {
126                Ok(())
127            }
128
129            fn verify_ml_dsa_65(
130                &self,
131                _ml_dsa_65_signature: &[u8],
132                _canonical_payload: &[u8],
133            ) -> Result<(), VerificationError> {
134                Ok(())
135            }
136        }
137
138        impl Verifier for AcceptingVerifier {
139            fn verify(
140                &self,
141                _envelope: &SignatureEnvelope<'_>,
142                _canonical_payload: &[u8],
143            ) -> Result<(), VerificationError> {
144                Ok(())
145            }
146        }
147
148        let signature = [1; 64];
149        let envelope = SignedEnvelope::new(
150            (),
151            SignatureEnvelope::new(
152                Digest::new([1; Digest::LEN]),
153                SignatureAlgorithm::Ed25519,
154                &signature,
155                WireLimits::DEVELOPMENT,
156            )?,
157        );
158        let payload = [0; 65];
159        let limits =
160            WireLimits::new(64, 1, 1, 1).map_err(|_| VerificationError::InvalidSignature)?;
161        let policy = AlgorithmPolicy::new(&[SignatureAlgorithm::Ed25519]);
162
163        assert_eq!(
164            envelope.verify_detached_bytes(&AcceptingVerifier, &policy, &payload, limits),
165            Err(VerificationError::PayloadTooLarge)
166        );
167        Ok(())
168    }
169
170    #[test]
171    fn signature_envelope_debug_redacts_signature_bytes() -> Result<(), VerificationError> {
172        extern crate std;
173        use std::{format, string::String};
174
175        let signature = [7; SignatureAlgorithm::ED25519_SIGNATURE_LEN];
176        let envelope = SignatureEnvelope::new(
177            Digest::new([1; Digest::LEN]),
178            SignatureAlgorithm::Ed25519,
179            &signature,
180            WireLimits::DEVELOPMENT,
181        )?;
182
183        assert_eq!(
184            format!("{envelope:?}"),
185            String::from(
186                "SignatureEnvelope { key_id: Digest(..), algorithm: Ed25519, signature: [64 bytes] }"
187            )
188        );
189        Ok(())
190    }
191
192    #[test]
193    fn hybrid_verification_requires_both_components() -> Result<(), VerificationError> {
194        struct PartialHybridVerifier;
195
196        impl HybridVerifier for PartialHybridVerifier {
197            fn verify_ed25519(
198                &self,
199                ed25519_signature: &[u8],
200                _canonical_payload: &[u8],
201            ) -> Result<(), VerificationError> {
202                if ed25519_signature.len() == SignatureAlgorithm::ED25519_SIGNATURE_LEN {
203                    Ok(())
204                } else {
205                    Err(VerificationError::InvalidSignature)
206                }
207            }
208
209            fn verify_ml_dsa_65(
210                &self,
211                _ml_dsa_65_signature: &[u8],
212                _canonical_payload: &[u8],
213            ) -> Result<(), VerificationError> {
214                Err(VerificationError::InvalidSignature)
215            }
216        }
217
218        impl Verifier for PartialHybridVerifier {
219            fn verify(
220                &self,
221                _envelope: &SignatureEnvelope<'_>,
222                _canonical_payload: &[u8],
223            ) -> Result<(), VerificationError> {
224                Ok(())
225            }
226        }
227
228        let signature = [9; SignatureAlgorithm::HYBRID_ED25519_ML_DSA_65_SIGNATURE_LEN];
229        let envelope = SignedEnvelope::new(
230            (),
231            SignatureEnvelope::new(
232                Digest::new([1; Digest::LEN]),
233                SignatureAlgorithm::HybridEd25519MlDsa65,
234                &signature,
235                WireLimits::DEVELOPMENT,
236            )?,
237        );
238        let policy = AlgorithmPolicy::new(&[SignatureAlgorithm::HybridEd25519MlDsa65]);
239
240        assert_eq!(
241            envelope.verify_detached_bytes(
242                &PartialHybridVerifier,
243                &policy,
244                b"payload",
245                WireLimits::DEVELOPMENT,
246            ),
247            Err(VerificationError::InvalidSignature)
248        );
249        Ok(())
250    }
251}