ndn_security/
verifier.rs

1use std::future::Future;
2use std::pin::Pin;
3
4use crate::TrustError;
5
6type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
7
8/// Outcome of a signature verification attempt.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum VerifyOutcome {
11    Valid,
12    /// Signature is cryptographically invalid.
13    Invalid,
14}
15
16/// Verifies a signature against a public key.
17pub trait Verifier: Send + Sync + 'static {
18    fn verify<'a>(
19        &'a self,
20        region: &'a [u8],
21        sig_value: &'a [u8],
22        public_key: &'a [u8],
23    ) -> BoxFuture<'a, Result<VerifyOutcome, TrustError>>;
24}
25
26/// Ed25519 verifier.
27pub struct Ed25519Verifier;
28
29impl Ed25519Verifier {
30    /// Synchronous Ed25519 verification — avoids boxing a Future for CPU-only work.
31    pub fn verify_sync(&self, region: &[u8], sig_value: &[u8], public_key: &[u8]) -> VerifyOutcome {
32        use ed25519_dalek::{Signature, Verifier as _, VerifyingKey};
33
34        let Ok(vk) = VerifyingKey::from_bytes(public_key.try_into().unwrap_or(&[0u8; 32])) else {
35            return VerifyOutcome::Invalid;
36        };
37
38        let Ok(sig_bytes): Result<&[u8; 64], _> = sig_value.try_into() else {
39            return VerifyOutcome::Invalid;
40        };
41        let sig = Signature::from_bytes(sig_bytes);
42
43        match vk.verify(region, &sig) {
44            Ok(()) => VerifyOutcome::Valid,
45            Err(_) => VerifyOutcome::Invalid,
46        }
47    }
48}
49
50impl Verifier for Ed25519Verifier {
51    fn verify<'a>(
52        &'a self,
53        region: &'a [u8],
54        sig_value: &'a [u8],
55        public_key: &'a [u8],
56    ) -> BoxFuture<'a, Result<VerifyOutcome, TrustError>> {
57        Box::pin(async move {
58            use ed25519_dalek::{Signature, Verifier as _, VerifyingKey};
59
60            let vk = VerifyingKey::from_bytes(
61                public_key.try_into().map_err(|_| TrustError::InvalidKey)?,
62            )
63            .map_err(|_| TrustError::InvalidKey)?;
64
65            let sig_bytes: &[u8; 64] = sig_value
66                .try_into()
67                .map_err(|_| TrustError::InvalidSignature)?;
68            let sig = Signature::from_bytes(sig_bytes);
69
70            match vk.verify(region, &sig) {
71                Ok(()) => Ok(VerifyOutcome::Valid),
72                Err(_) => Ok(VerifyOutcome::Invalid),
73            }
74        })
75    }
76}
77
78// ─── Batch Ed25519 verification ────────────────────────────────────────────
79//
80// For a forwarder or consumer ingesting many independent Ed25519
81// signatures at once (sync snapshot, batched Interest set, a fetch
82// of N segment Data packets), verifying them **together** via
83// `ed25519_dalek::verify_batch` is ~2–3× faster than N separate
84// `verify()` calls. The technique combines the N verification
85// equations into one big check using random coefficients — if every
86// signature is valid the batch passes in one shot; if any is invalid
87// the batch fails and you don't know which one (fallback is to
88// re-verify individually).
89
90/// Batch-verify a homogeneous slice of Ed25519 signatures. All three
91/// inputs must have the same length. Returns `Ok(())` if every
92/// signature is valid under its paired `(message, public_key)`; any
93/// single invalid signature causes the whole batch to fail with
94/// [`VerifyOutcome::Invalid`] (you then have to fall back to
95/// per-signature verify to find the culprit).
96///
97/// For a homogeneous-message workload (all messages identical — e.g.
98/// SVS sync state blobs in a group), callers can pass the same
99/// `&[u8]` multiple times in `messages` and still get the batch
100/// speedup because `ed25519_dalek::verify_batch` is message-by-
101/// reference.
102pub fn ed25519_verify_batch(
103    messages: &[&[u8]],
104    signatures: &[&[u8; 64]],
105    public_keys: &[&[u8; 32]],
106) -> Result<VerifyOutcome, TrustError> {
107    use ed25519_dalek::{Signature, VerifyingKey, verify_batch};
108
109    let n = messages.len();
110    if signatures.len() != n || public_keys.len() != n {
111        return Err(TrustError::InvalidSignature);
112    }
113    if n == 0 {
114        return Ok(VerifyOutcome::Valid); // vacuous truth
115    }
116
117    // Decode signatures into `ed25519_dalek::Signature` objects.
118    // `Signature::from_bytes` is infallible for `[u8; 64]` inputs.
119    let sigs: Vec<Signature> = signatures
120        .iter()
121        .map(|s| Signature::from_bytes(s))
122        .collect();
123
124    // Decode verifying keys. Any malformed public key makes the
125    // whole batch undecodable — return InvalidKey so the caller
126    // knows this is a structural error, not a signature mismatch.
127    let mut keys: Vec<VerifyingKey> = Vec::with_capacity(n);
128    for pk in public_keys {
129        match VerifyingKey::from_bytes(pk) {
130            Ok(vk) => keys.push(vk),
131            Err(_) => return Err(TrustError::InvalidKey),
132        }
133    }
134
135    // Run the batch verify. A failed batch returns Invalid, not
136    // Err — the signatures are well-formed, one (or more) is just
137    // wrong, which is the same semantic as a single `verify()`
138    // returning `VerifyOutcome::Invalid`.
139    match verify_batch(messages, &sigs, &keys) {
140        Ok(()) => Ok(VerifyOutcome::Valid),
141        Err(_) => Ok(VerifyOutcome::Invalid),
142    }
143}
144
145/// BLAKE3 digest verifier — checks that `sig_value` is the BLAKE3 hash of `region`.
146///
147/// `public_key` is unused (BLAKE3 digest signing has no key). Pass an empty slice.
148///
149/// Hashes large inputs (≥ [`BLAKE3_RAYON_THRESHOLD`]) via `update_rayon`,
150/// which scales with available cores. Per-packet verification never
151/// reaches the threshold; bulk content verification (large segmented
152/// Data with a tree-signed root) does.
153pub struct Blake3DigestVerifier;
154
155impl Verifier for Blake3DigestVerifier {
156    fn verify<'a>(
157        &'a self,
158        region: &'a [u8],
159        sig_value: &'a [u8],
160        _public_key: &'a [u8],
161    ) -> BoxFuture<'a, Result<VerifyOutcome, TrustError>> {
162        Box::pin(async move {
163            let Ok(expected): Result<&[u8; 32], _> = sig_value.try_into() else {
164                return Ok(VerifyOutcome::Invalid);
165            };
166            let hash = crate::signer::blake3_hash_auto(region);
167            if hash.as_bytes() == expected {
168                Ok(VerifyOutcome::Valid)
169            } else {
170                Ok(VerifyOutcome::Invalid)
171            }
172        })
173    }
174}
175
176/// BLAKE3 keyed verifier — checks that `sig_value` is the BLAKE3 keyed hash of `region`.
177///
178/// `public_key` must be exactly 32 bytes (the BLAKE3 key). Same large-
179/// input dispatch as [`Blake3DigestVerifier`].
180pub struct Blake3KeyedVerifier;
181
182impl Verifier for Blake3KeyedVerifier {
183    fn verify<'a>(
184        &'a self,
185        region: &'a [u8],
186        sig_value: &'a [u8],
187        public_key: &'a [u8],
188    ) -> BoxFuture<'a, Result<VerifyOutcome, TrustError>> {
189        Box::pin(async move {
190            let key: &[u8; 32] = public_key.try_into().map_err(|_| TrustError::InvalidKey)?;
191            let Ok(expected): Result<&[u8; 32], _> = sig_value.try_into() else {
192                return Ok(VerifyOutcome::Invalid);
193            };
194            let hash = crate::signer::blake3_keyed_hash_auto(key, region);
195            if hash.as_bytes() == expected {
196                Ok(VerifyOutcome::Valid)
197            } else {
198                Ok(VerifyOutcome::Invalid)
199            }
200        })
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207    use ed25519_dalek::{Signer as _, SigningKey};
208
209    fn keypair(seed: &[u8; 32]) -> (SigningKey, [u8; 32]) {
210        let sk = SigningKey::from_bytes(seed);
211        let pk = sk.verifying_key().to_bytes();
212        (sk, pk)
213    }
214
215    #[tokio::test]
216    async fn valid_signature_returns_valid() {
217        let (sk, pk) = keypair(&[1u8; 32]);
218        let region = b"signed region";
219        let sig = sk.sign(region).to_bytes();
220        let outcome = Ed25519Verifier.verify(region, &sig, &pk).await.unwrap();
221        assert_eq!(outcome, VerifyOutcome::Valid);
222    }
223
224    #[tokio::test]
225    async fn wrong_signature_returns_invalid() {
226        let (_sk, pk) = keypair(&[1u8; 32]);
227        let region = b"signed region";
228        let bad_sig = [0u8; 64];
229        let outcome = Ed25519Verifier.verify(region, &bad_sig, &pk).await.unwrap();
230        assert_eq!(outcome, VerifyOutcome::Invalid);
231    }
232
233    #[tokio::test]
234    async fn wrong_key_returns_invalid() {
235        let (sk, _) = keypair(&[1u8; 32]);
236        let (_, pk2) = keypair(&[2u8; 32]); // different key
237        let region = b"signed region";
238        let sig = sk.sign(region).to_bytes();
239        let outcome = Ed25519Verifier.verify(region, &sig, &pk2).await.unwrap();
240        assert_eq!(outcome, VerifyOutcome::Invalid);
241    }
242
243    #[tokio::test]
244    async fn short_public_key_returns_err() {
245        let sig = [0u8; 64];
246        let result = Ed25519Verifier.verify(b"region", &sig, &[0u8; 16]).await;
247        assert!(matches!(result, Err(TrustError::InvalidKey)));
248    }
249
250    #[tokio::test]
251    async fn short_signature_returns_err() {
252        let (_, pk) = keypair(&[1u8; 32]);
253        let result = Ed25519Verifier.verify(b"region", &[0u8; 32], &pk).await;
254        assert!(matches!(result, Err(TrustError::InvalidSignature)));
255    }
256
257    // ── batch verification ──────────────────────────────────────────────
258
259    /// A batch of all-valid signatures over per-key messages verifies.
260    #[test]
261    fn batch_all_valid_returns_valid() {
262        // Generate 10 independent keypairs, sign a distinct message with
263        // each, and batch-verify.
264        let ns: Vec<(SigningKey, [u8; 32], Vec<u8>, [u8; 64])> = (0u8..10)
265            .map(|i| {
266                let (sk, pk) = keypair(&[i; 32]);
267                let msg = format!("message {i}").into_bytes();
268                let sig = sk.sign(&msg).to_bytes();
269                (sk, pk, msg, sig)
270            })
271            .collect();
272        let messages: Vec<&[u8]> = ns.iter().map(|(_, _, m, _)| m.as_slice()).collect();
273        let signatures: Vec<&[u8; 64]> = ns.iter().map(|(_, _, _, s)| s).collect();
274        let public_keys: Vec<&[u8; 32]> = ns.iter().map(|(_, pk, _, _)| pk).collect();
275        let out = ed25519_verify_batch(&messages, &signatures, &public_keys).unwrap();
276        assert_eq!(out, VerifyOutcome::Valid);
277    }
278
279    /// A batch with one bad signature fails the whole batch (per
280    /// `verify_batch` semantics — the caller then falls back to
281    /// per-signature verify to locate the culprit).
282    #[test]
283    fn batch_one_bad_sig_returns_invalid() {
284        let ns: Vec<(SigningKey, [u8; 32], Vec<u8>, [u8; 64])> = (0u8..10)
285            .map(|i| {
286                let (sk, pk) = keypair(&[i; 32]);
287                let msg = format!("message {i}").into_bytes();
288                let sig = sk.sign(&msg).to_bytes();
289                (sk, pk, msg, sig)
290            })
291            .collect();
292        let messages: Vec<&[u8]> = ns.iter().map(|(_, _, m, _)| m.as_slice()).collect();
293        let mut signatures: Vec<[u8; 64]> = ns.iter().map(|(_, _, _, s)| *s).collect();
294        // Corrupt one byte of one signature.
295        signatures[4][0] ^= 0x80;
296        let sig_refs: Vec<&[u8; 64]> = signatures.iter().collect();
297        let public_keys: Vec<&[u8; 32]> = ns.iter().map(|(_, pk, _, _)| pk).collect();
298        let out = ed25519_verify_batch(&messages, &sig_refs, &public_keys).unwrap();
299        assert_eq!(out, VerifyOutcome::Invalid);
300    }
301
302    #[test]
303    fn batch_length_mismatch_returns_err() {
304        let (sk, pk) = keypair(&[1u8; 32]);
305        let msg: &[u8] = b"a message";
306        let sig = sk.sign(msg).to_bytes();
307        let messages: &[&[u8]] = &[msg, msg];
308        let sigs = [&sig];
309        let keys = [&pk, &pk];
310        let out = ed25519_verify_batch(messages, &sigs, &keys);
311        assert!(matches!(out, Err(TrustError::InvalidSignature)));
312    }
313
314    #[test]
315    fn batch_empty_is_vacuously_valid() {
316        let out = ed25519_verify_batch(&[], &[], &[]).unwrap();
317        assert_eq!(out, VerifyOutcome::Valid);
318    }
319
320    // Note: no test for "malformed public key returns InvalidKey"
321    // because `ed25519_dalek::VerifyingKey::from_bytes` is more
322    // lenient than I expected — all-0x00 and all-0xFF both decode as
323    // (unusable) curve points, so the InvalidKey mapping in
324    // `ed25519_verify_batch` only fires for genuinely malformed
325    // 32-byte sequences that ed25519-dalek considers non-points,
326    // which is hard to construct without internal knowledge of the
327    // curve encoding. The functional behaviour is correct — a bogus
328    // key produces a `VerifyOutcome::Invalid` rather than an Err —
329    // which is fine for the forwarder-ingest use case (you'd
330    // fall back to per-signature verify either way).
331}