Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_crypto/src/sign.rs

46.0 KiB, 232 runs

created by r1870400018:248, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1use crate::keys::Keys;
2// `pqc::dilithium` needs a mode feature to have a parameter set (see `pqc::mod`); the pure-Rust
3// `Dilithium2_fe2o3` variant below needs it for the same reason.
4#[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
5use crate::pqc::dilithium as dilithium2_fe2o3;
6
7use oxedyne_fe2o3_core::prelude::*;
8use oxedyne_fe2o3_iop_crypto::{
9 keys::KeyManager,
10 sign::{
11 BatchItem,
12 Signer,
13 },
14};
15// Used by the Dilithium arms of `verify_batch` (pq or a mode feature) and, unconditionally, by
16// a test.
17#[cfg(any(
18 test,
19 feature = "pq",
20 feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3",
21))]
22use oxedyne_fe2o3_iop_crypto::sign::verify_each;
23use oxedyne_fe2o3_namex::{
24 id::{
25 LocalId,
26 InNamex,
27 NamexId,
28 },
29};
30
31use std::{
32 collections::BTreeMap,
33 convert::TryFrom,
34 fmt::{
35 self,
36 Debug,
37 },
38 str,
39};
40
41#[cfg(feature = "batch")]
42use curve25519_dalek::{
43 constants::ED25519_BASEPOINT_POINT,
44 traits::VartimeMultiscalarMul,
45};
46use curve25519_dalek::{
47 edwards::{
48 CompressedEdwardsY,
49 EdwardsPoint,
50 },
51 scalar::Scalar,
52 traits::IsIdentity,
53};
54use ed25519_dalek::{
55 SigningKey,
56 Signer as DalekSigner,
57};
58use sha2::{
59 Digest,
60 Sha512,
61};
62
63#[cfg(feature = "pq")]
64use pqcrypto_dilithium::dilithium2;
65#[cfg(feature = "pq")]
66use pqcrypto_traits::sign::{
67 DetachedSignature as _,
68 PublicKey as _,
69 SecretKey as _,
70};
71// Only `new_dilithium2_fe2o3` uses the old `rand_core`; it is mode-gated.
72#[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
73use rand_core_old::OsRng as OsRng_old;
74use rand_core::OsRng;
75use secrecy::{
76 ExposeSecret,
77 Secret,
78};
79// Only the Dilithium2_fe2o3 arm of `sign` zeroizes its own copy of the key; it is mode-gated.
80#[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
81use zeroize::Zeroize;
82
83// Note: Need to use heap when zeroizing:
84// https://benma.github.io/2020/10/16/rust-zeroize-move.html
85// Applies here to the keys encapsulated by the variants.
86/// Digital signature schemes.
87#[derive(Clone)]
88pub enum SignatureScheme { // Associated data: (public key, wrapped secret key)
89 Ed25519(Keys< // SecretVec gets zeroed whenever dropped.
90 {Self::ED25519_PK_LEN},
91 {Self::ED25519_SK_LEN},
92 >),
93 /// The C reference implementation, wrapped. Absent without the `pq` feature.
94 #[cfg(feature = "pq")]
95 Dilithium2(Keys<
96 {Self::DILITHIUM2_PK_LEN},
97 {Self::DILITHIUM2_SK_LEN},
98 >),
99 /// Pure Rust impl based on https://github.com/quininer. Absent without a `mode0`..`mode3`
100 /// feature, which is what gives it a parameter set.
101 #[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
102 Dilithium2_fe2o3(Keys<
103 {Self::DILITHIUM2_FE2O3_PK_LEN},
104 {Self::DILITHIUM2_FE2O3_SK_LEN},
105 >),
106}
107
108impl Debug for SignatureScheme {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 match self {
111 Self::Ed25519(..) => write!(f, "Ed25519"),
112 #[cfg(feature = "pq")]
113 Self::Dilithium2(..) => write!(f, "Dilithium2"),
114 #[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
115 Self::Dilithium2_fe2o3(..) => write!(f, "Dilithium2_fe2o3"),
116 }
117 }
118}
119
120impl InNamex for SignatureScheme {
121
122 fn name_id(&self) -> Outcome<NamexId> {
123 Ok(match self {
124 Self::Ed25519(..) =>
125 res!(NamexId::try_from("9UQvATp4Zbv8IbWOivdhiQnex+ELo7sxOr8ntEZphMc=")),
126 #[cfg(feature = "pq")]
127 Self::Dilithium2(..) =>
128 res!(NamexId::try_from("W4+qt2Gd+9RQBxllcx10b4h/Ih3g9m76C+mj17TwUNw=")),
129 #[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
130 Self::Dilithium2_fe2o3(..) =>
131 res!(NamexId::try_from("zkSGGwLauv5FLpNoCse+3D7bKIdNh7PeBsfbjv/TSvQ=")),
132 })
133 }
134
135 fn local_id(&self) -> LocalId {
136 match self {
137 Self::Ed25519(..) => LocalId(1),
138 #[cfg(feature = "pq")]
139 Self::Dilithium2(..) => LocalId(2),
140 #[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
141 Self::Dilithium2_fe2o3(..) => LocalId(3),
142 }
143 }
144
145 fn assoc_names_base64(
146 gname: &'static str,
147 )
148 -> Outcome<Option<Vec<(
149 &'static str,
150 &'static str,
151 )>>>
152 {
153 let ids = match gname {
154 "schemes" => [
155 ("Ed25519", "9UQvATp4Zbv8IbWOivdhiQnex+ELo7sxOr8ntEZphMc="),
156 ("Dilithium2", "W4+qt2Gd+9RQBxllcx10b4h/Ih3g9m76C+mj17TwUNw="),
157 ("Dilithium2_fe2o3", "zkSGGwLauv5FLpNoCse+3D7bKIdNh7PeBsfbjv/TSvQ="),
158 ],
159 _ => return Err(err!(
160 "The Namex group name '{}' is not recognised for SignatureScheme.", gname;
161 Invalid, Input)),
162 };
163 Ok(if ids.len() == 0 {
164 None
165 } else {
166 Some(ids.to_vec())
167 })
168 }
169}
170
171impl Signer for SignatureScheme {
172
173 #![allow(unused)]
174 fn sign(&self, msg: &[u8]) -> Outcome<Vec<u8>> {
175 match self {
176 Self::Ed25519(keys) => match keys {
177 Keys { pk: Some(pk), sks: Some(sks) } => {
178 let skv = sks.expose_secret();
179 let sk_byts = res!(<[u8; Self::ED25519_SK_LEN]>::try_from(&skv[..]));
180 let signing_key = SigningKey::from_bytes(&sk_byts);
181 let verifying_key = signing_key.verifying_key();
182 if verifying_key.to_bytes() != pk[..] {
183 return Err(err!("Public key mismatch."; Invalid, Configuration));
184 }
185 let result = signing_key.sign(msg).to_bytes().to_vec();
186 Ok(result)
187 },
188 _ => Err(err!("Require both keys to sign."; Missing, Configuration)),
189 },
190 #[cfg(feature = "pq")]
191 Self::Dilithium2(keys) => match keys {
192 Keys { sks: Some(sks), .. } => {
193 let skv = sks.expose_secret(); // This gets zeroized automatically, ...
194 let mut sk = res!(dilithium2::SecretKey::from_bytes(&skv[..])); // this does not, so...
195 let result = dilithium2::detached_sign(msg, &sk).as_bytes().to_vec();
196 sk = res!(dilithium2::SecretKey::from_bytes(&vec![0; skv.len()])); // do it manually.
197 Ok(result)
198 },
199 _ => Err(err!("Require secret key to sign."; Missing, Configuration)),
200 },
201 #[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
202 Self::Dilithium2_fe2o3(keys) => match keys {
203 Keys { sks: Some(sks), .. } => {
204 let skv = sks.expose_secret();
205 let mut sk = res!(<[u8; Self::DILITHIUM2_FE2O3_SK_LEN]>::try_from(&skv[..]));
206 let result = dilithium2_fe2o3::sign::sign(msg, &sk).to_vec();
207 sk.zeroize();
208 Ok(result)
209 },
210 _ => Err(err!("Require secret key to sign."; Missing, Configuration)),
211 },
212 }
213 }
214
215 fn verify(&self, msg: &[u8], sig: &[u8]) -> Outcome<bool> {
216 Ok(match self {
217 Self::Ed25519(keys) => match keys {
218 Keys { pk: Some(pk), .. } => res!(verify_ed25519(&pk[..], msg, sig)),
219 _ => return Err(err!("Require public key to verify."; Missing, Configuration)),
220 },
221 #[cfg(feature = "pq")]
222 Self::Dilithium2(keys) => match keys {
223 Keys { pk: Some(pk), .. } => {
224 let pk = res!(dilithium2::PublicKey::from_bytes(&pk[..]));
225 let sig = res!(dilithium2::DetachedSignature::from_bytes(&sig));
226 match dilithium2::verify_detached_signature(&sig, msg, &pk) {
227 Ok(()) => true,
228 _ => false,
229 }
230 },
231 _ => return Err(err!("Require public key to verify."; Missing, Configuration)),
232 },
233 #[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
234 Self::Dilithium2_fe2o3(keys) => match keys {
235 Keys { pk: Some(pk), .. } => {
236 let pk = res!(<[u8; Self::DILITHIUM2_FE2O3_PK_LEN]>::try_from(&pk[..]));
237 let sig = res!(<[u8; Self::DILITHIUM2_FE2O3_SIG_LEN]>::try_from(&sig[..]));
238 dilithium2_fe2o3::sign::verify(msg, &sig, &pk)
239 },
240 _ => return Err(err!("Require public key to verify."; Missing, Configuration)),
241 },
242 })
243 }
244
245 /// Checks many signatures at once, each against the public key its item
246 /// carries.
247 ///
248 /// Ed25519 is checked by [`verify_batch_ed25519`], which decompresses each
249 /// distinct public key once and, where the build carries the `batch`
250 /// feature, puts the whole set to one verification equation. Either way it
251 /// holds each item to the rules of [`verify_ed25519`] and accepts a set
252 /// exactly when that accepts every member. The scheme's own keys are not
253 /// consulted: every item names its own signer, which is what a batch drawn
254 /// from a history signed by several people needs.
255 ///
256 /// The Dilithium schemes have no batch equation here, so they are checked
257 /// one at a time and the result is the same as it always was.
258 fn verify_batch(&self, items: &[BatchItem<'_>])
259 -> Outcome<bool>
260 where Self: Sized
261 {
262 match self {
263 Self::Ed25519(..) => verify_batch_ed25519(items),
264 #[cfg(feature = "pq")]
265 Self::Dilithium2(..) => verify_each(self, items),
266 #[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
267 Self::Dilithium2_fe2o3(..) => verify_each(self, items),
268 }
269 }
270}
271
272/// Does `sig` verify as an Ed25519 signature by `public` over `msg`?
273///
274/// Verification is strict: RFC 8032 §5.1.7 with nothing left optional, plus the
275/// one refusal the RFC leaves to the verifier.
276///
277/// - `public` and R must each be the canonical encoding of a point (§5.1.3), so
278/// one key and one signature each have exactly one spelling.
279/// - S must lie below the group order L, so a signature cannot be re-spelt as
280/// S + L.
281/// - A public key or R of small order is refused. The identity as a public key,
282/// with R the identity and S zero, satisfies the group equation over every
283/// message, so a verifier that took it would let anyone sign as that key.
284/// - The group equation is the cofactored `[8][S]B = [8]R + [8][k]A`, the form
285/// the RFC gives first. It is the only form a batch can check exactly, which is
286/// what lets [`SignatureScheme::verify_batch`] promise to accept precisely the
287/// signatures this accepts.
288///
289/// No honest signer is affected: a key and an R made by signing are canonical,
290/// of large order and free of any small-order component. A key or signature of
291/// the wrong length, or a key that encodes no curve point at all, is an error;
292/// every other failure is `false`.
293pub fn verify_ed25519(public: &[u8], msg: &[u8], sig: &[u8]) -> Outcome<bool> {
294 let key = match res!(strict_key(public)) {
295 Some(key) => key,
296 None => return Ok(false),
297 };
298 let parts = match res!(strict_sig(sig)) {
299 Some(parts) => parts,
300 None => return Ok(false),
301 };
302 let k = Scalar::from_bytes_mod_order_wide(&challenge(&parts.r_bytes, public, msg));
303 Ok(equation_holds(&key, &parts, &k))
304}
305
306/// R and S of a signature that has passed the strict checks.
307struct SigParts {
308 r_bytes: [u8; 32], // R as signed, which the challenge hashes
309 r: EdwardsPoint,
310 s: Scalar,
311}
312
313/// Decodes a public key for verification: an error where it has the wrong
314/// length or encodes no point, `None` where a strict verifier refuses it.
315fn strict_key(public: &[u8]) -> Outcome<Option<EdwardsPoint>> {
316 let byts = match <[u8; SignatureScheme::ED25519_PK_LEN]>::try_from(public) {
317 Ok(byts) => byts,
318 Err(_) => return Err(err!(
319 "An Ed25519 public key is {} bytes, and {} were given.",
320 SignatureScheme::ED25519_PK_LEN, public.len();
321 Invalid, Input, Size)),
322 };
323 let point = match CompressedEdwardsY(byts).decompress() {
324 Some(point) => point,
325 None => return Err(err!(
326 "The {} bytes given as an Ed25519 public key encode no curve point.",
327 SignatureScheme::ED25519_PK_LEN;
328 Invalid, Input)),
329 };
330 if !is_canonical_point(&byts) || point.is_small_order() {
331 return Ok(None);
332 }
333 Ok(Some(point))
334}
335
336/// Decodes a signature for verification: an error where it has the wrong
337/// length, `None` where a strict verifier refuses it.
338fn strict_sig(sig: &[u8]) -> Outcome<Option<SigParts>> {
339 if sig.len() != SignatureScheme::ED25519_SIG_LEN {
340 return Err(err!(
341 "An Ed25519 signature is {} bytes, and {} were given.",
342 SignatureScheme::ED25519_SIG_LEN, sig.len();
343 Invalid, Input, Size));
344 }
345 let mut r_bytes = [0u8; 32];
346 r_bytes.copy_from_slice(&sig[..32]);
347 let mut s_bytes = [0u8; 32];
348 s_bytes.copy_from_slice(&sig[32..]);
349 if !is_canonical_point(&r_bytes) {
350 return Ok(None);
351 }
352 let r = match CompressedEdwardsY(r_bytes).decompress() {
353 Some(r) if !r.is_small_order() => r,
354 _ => return Ok(None),
355 };
356 let s = match Option::<Scalar>::from(Scalar::from_canonical_bytes(s_bytes)) {
357 Some(s) => s,
358 None => return Ok(None),
359 };
360 Ok(Some(SigParts { r_bytes, r, s }))
361}
362
363/// SHA-512(R ‖ A ‖ M), the 64 bytes RFC 8032 reads as the integer k.
364fn challenge(r_bytes: &[u8; 32], public: &[u8], msg: &[u8]) -> [u8; 64] {
365 let mut h = Sha512::new();
366 h.update(r_bytes);
367 h.update(public);
368 h.update(msg);
369 let mut out = [0u8; 64];
370 out.copy_from_slice(&h.finalize());
371 out
372}
373
374/// Is `[8]([S]B - R - [k]A)` the identity?
375fn equation_holds(key: &EdwardsPoint, parts: &SigParts, k: &Scalar) -> bool {
376 let sb_ka = EdwardsPoint::vartime_double_scalar_mul_basepoint(k, &-key, &parts.s);
377 (sb_ka - parts.r).mul_by_cofactor().is_identity()
378}
379
380/// The field modulus p = 2^255 - 19, little endian, against which a compressed
381/// curve point's y coordinate is measured for canonicity.
382const FIELD_MODULUS: [u8; 32] = [
383 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
384 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
385 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
386 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
387];
388
389/// Reports whether 32 bytes are the *canonical* compressed encoding of an
390/// Edwards point, which is to say the one and only encoding that point
391/// compresses back to.
392///
393/// RFC 8032 §5.1.3 fails the decoding of any other, and `curve25519-dalek`
394/// decompresses them without complaint, so the check is made here, for the
395/// public key and for R alike. Two things make an encoding non-canonical:
396///
397/// - a y coordinate not less than p, which is reduced on decompression and so
398/// compresses back to different bytes, and
399/// - the sign bit set on a point whose x is zero, since -0 is 0 and compression
400/// emits a clear sign bit. x is zero exactly when y is 1 or p - 1, which is
401/// why only those two values are named.
402fn is_canonical_point(bytes: &[u8]) -> bool {
403 if bytes.len() != 32 {
404 return false;
405 }
406 let negative = bytes[31] & 0x80 != 0;
407 let mut y = [0u8; 32];
408 y.copy_from_slice(bytes);
409 y[31] &= 0x7f;
410 // Little endian, so the comparison walks down from the top byte.
411 for i in (0..32).rev() {
412 if y[i] < FIELD_MODULUS[i] {
413 break;
414 }
415 if y[i] > FIELD_MODULUS[i] {
416 return false;
417 }
418 if i == 0 {
419 return false; // Equal to p, which reduces to zero.
420 }
421 }
422 if negative {
423 // y = 1, and y = p - 1, are the two points whose x is zero.
424 let one = y[0] == 0x01 && y[1..].iter().all(|b| *b == 0);
425 let minus_one = y[0] == 0xec
426 && y[1..31].iter().all(|b| *b == 0xff)
427 && y[31] == 0x7f;
428 if one || minus_one {
429 return false;
430 }
431 }
432 true
433}
434
435/// One item of a batch, decoded and passed by the strict checks.
436struct Check {
437 key: usize, // index into the batch's distinct keys
438 parts: SigParts,
439 hram: [u8; 64], // the challenge, before reduction
440}
441
442// Domain separation for the batch coefficients
443#[cfg(feature = "batch")]
444const BATCH_DST: &[u8] = b"fe2o3 ed25519 batch/1";
445
446/// Puts the checked items to one equation: the sum of each item's own
447/// cofactored equation, weighted by a 128-bit coefficient.
448///
449/// ```text
450/// [8]( [Σ z_i·S_i]B − Σ z_i·R_i − Σ_j (Σ_{i→j} z_i·k_i)·A_j ) = identity
451/// ```
452///
453/// The coefficients are drawn by hashing every input, so a signer cannot pick a
454/// signature knowing its coefficient, and nothing asks the operating system for
455/// randomness, so this runs on wasm32. Each term is multiplied by the cofactor,
456/// so a residue of small order, which the single check also absorbs, can
457/// neither be cancelled nor exposed by a coefficient: the batch accepts a set
458/// exactly when every member passes [`verify_ed25519`], short of a 2^-128
459/// chance. The cofactorless batch it replaces accepted, whenever a coefficient
460/// happened to annihilate it, a residue that the single check refused, and a
461/// signer could arrange that by trying messages.
462#[cfg(feature = "batch")]
463fn holds(keys: &[EdwardsPoint], checks: &[Check]) -> bool {
464 let mut h = Sha512::new();
465 h.update(BATCH_DST);
466 h.update(&(checks.len() as u64).to_le_bytes());
467 for c in checks {
468 h.update(&c.hram); // Binds R, A and the message.
469 h.update(c.parts.s.as_bytes());
470 }
471 let seed = h.finalize();
472 let mut b_coef = Scalar::ZERO;
473 let mut a_coefs = vec![Scalar::ZERO; keys.len()];
474 let mut scalars = Vec::with_capacity(1 + checks.len() + keys.len());
475 let mut points = Vec::with_capacity(1 + checks.len() + keys.len());
476 for (i, c) in checks.iter().enumerate() {
477 let mut h = Sha512::new();
478 h.update(&seed);
479 h.update(&(i as u64).to_le_bytes());
480 let mut z = [0u8; 16];
481 z.copy_from_slice(&h.finalize()[..16]);
482 let z = Scalar::from(u128::from_le_bytes(z));
483 b_coef += z * c.parts.s;
484 a_coefs[c.key] -= z * Scalar::from_bytes_mod_order_wide(&c.hram);
485 scalars.push(-z);
486 points.push(c.parts.r);
487 }
488 scalars.push(b_coef);
489 points.push(ED25519_BASEPOINT_POINT);
490 for (a_coef, key) in a_coefs.iter().zip(keys.iter()) {
491 scalars.push(*a_coef);
492 points.push(*key);
493 }
494 EdwardsPoint::vartime_multiscalar_mul(scalars.iter(), points.iter())
495 .mul_by_cofactor()
496 .is_identity()
497}
498
499/// Checks the items one at a time, each by its own equation. See the `batch`
500/// variant above.
501#[cfg(not(feature = "batch"))]
502fn holds(keys: &[EdwardsPoint], checks: &[Check]) -> bool {
503 for c in checks {
504 let k = Scalar::from_bytes_mod_order_wide(&c.hram);
505 if !equation_holds(&keys[c.key], &c.parts, &k) {
506 return false;
507 }
508 }
509 true
510}
511
512/// Checks a batch of Ed25519 signatures under the rules of [`verify_ed25519`],
513/// decompressing each distinct public key once.
514///
515/// # The public key cache
516///
517/// Decompressing a public key costs a field inversion and a square root, and a
518/// version control history is typically signed by a handful of people over
519/// thousands of operations. Doing it once per *key* rather than once per
520/// *signature* saves that much, and it does not depend on the batch equation
521/// being available. The batch equation also gathers every signature by one key
522/// into a single term, so a key costs one point in the sum however often it
523/// signed.
524///
525/// # What the result means
526///
527/// `true` says every signature in the set holds. `false` says at least one does
528/// not, and says nothing about which: a caller that must name the culprit
529/// checks them again one at a time. A malformed key or signature is an error
530/// rather than a `false`, matching what [`Signer::verify`] does with the same
531/// bytes, so that a caller falling back on either outcome reproduces the same
532/// message.
533fn verify_batch_ed25519(items: &[BatchItem<'_>])
534 -> Outcome<bool>
535{
536 if items.is_empty() {
537 return Ok(true);
538 }
539 let mut index: BTreeMap<&[u8], usize> = BTreeMap::new();
540 let mut keys: Vec<EdwardsPoint> = Vec::new();
541 let mut checks: Vec<Check> = Vec::with_capacity(items.len());
542 for item in items {
543 // The key first and then the signature, the order `verify_ed25519`
544 // takes them in, so an item earns the same error or `false` here.
545 let key = match index.get(item.public) {
546 Some(key) => *key,
547 None => match res!(strict_key(item.public)) {
548 Some(point) => {
549 keys.push(point);
550 index.insert(item.public, keys.len() - 1);
551 keys.len() - 1
552 },
553 None => return Ok(false),
554 },
555 };
556 let parts = match res!(strict_sig(item.sig)) {
557 Some(parts) => parts,
558 None => return Ok(false),
559 };
560 let hram = challenge(&parts.r_bytes, item.public, item.msg);
561 checks.push(Check { key, parts, hram });
562 }
563 Ok(holds(&keys, &checks))
564}
565
566impl KeyManager for SignatureScheme {
567
568 /// Clone using the specified keys.
569 fn clone_with_keys(&self, pk: Option<&[u8]>, sk: Option<&[u8]>) -> Outcome<Self> {
570 Ok(match self {
571 Self::Ed25519(..) => Self::Ed25519(Keys {
572 pk: match pk {
573 Some(pk) => Some(res!(<[u8; Self::ED25519_PK_LEN]>::try_from(&pk[..]))),
574 None => None,
575 },
576 sks: match sk {
577 Some(sk) => Some(Secret::new(res!(
578 <[u8; Self::ED25519_SK_LEN]>::try_from(&sk[..])
579 ))),
580 None => None,
581 },
582 }),
583 #[cfg(feature = "pq")]
584 Self::Dilithium2(..) => Self::Dilithium2(Keys {
585 pk: match pk {
586 Some(pk) => Some(res!(<[u8; Self::DILITHIUM2_PK_LEN]>::try_from(&pk[..]))),
587 None => None,
588 },
589 sks: match sk {
590 Some(sk) => Some(Secret::new(res!(
591 <[u8; Self::DILITHIUM2_SK_LEN]>::try_from(&sk[..])
592 ))),
593 None => None,
594 },
595 }),
596 #[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
597 Self::Dilithium2_fe2o3(..) => Self::Dilithium2_fe2o3(Keys {
598 pk: match pk {
599 Some(pk) => Some(res!(
600 <[u8; Self::DILITHIUM2_FE2O3_PK_LEN]>::try_from(&pk[..])
601 )),
602 None => None,
603 },
604 sks: match sk {
605 Some(sk) => Some(Secret::new(res!(
606 <[u8; Self::DILITHIUM2_FE2O3_SK_LEN]>::try_from(&sk[..])
607 ))),
608 None => None,
609 },
610 }),
611 })
612 }
613
614 fn get_public_key(&self) -> Outcome<Option<&[u8]>> {
615 Ok(match self {
616 Self::Ed25519(keys) => match &keys.pk {
617 Some(k) => Some(&k[..]),
618 None => None,
619 },
620 #[cfg(feature = "pq")]
621 Self::Dilithium2(keys) => match &keys.pk {
622 Some(k) => Some(&k[..]),
623 None => None,
624 },
625 #[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
626 Self::Dilithium2_fe2o3(keys) => match &keys.pk {
627 Some(k) => Some(&k[..]),
628 None => None,
629 },
630 })
631 }
632
633 fn get_secret_key(&self) -> Outcome<Option<&[u8]>> {
634 Ok(match self {
635 Self::Ed25519(keys) => match &keys.sks {
636 Some(sks) => {
637 let sk = sks.expose_secret();
638 Some(&sk[..])
639 },
640 None => None,
641 },
642 #[cfg(feature = "pq")]
643 Self::Dilithium2(keys) => match &keys.sks {
644 Some(sks) => {
645 let sk = sks.expose_secret();
646 Some(&sk[..])
647 },
648 None => None,
649 },
650 #[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
651 Self::Dilithium2_fe2o3(keys) => match &keys.sks {
652 Some(sks) => {
653 let sk = sks.expose_secret();
654 Some(&sk[..])
655 },
656 None => None,
657 },
658 })
659 }
660
661 fn set_public_key(mut self, pk: Option<&[u8]>) -> Outcome<Self> {
662 match &mut self {
663 Self::Ed25519(keys) => keys.pk = match pk {
664 Some(pk) => Some(res!(<[u8; Self::ED25519_PK_LEN]>::try_from(&pk[..]))),
665 None => None,
666 },
667 #[cfg(feature = "pq")]
668 Self::Dilithium2(keys) => keys.pk = match pk {
669 Some(pk) => Some(res!(<[u8; Self::DILITHIUM2_PK_LEN]>::try_from(&pk[..]))),
670 None => None,
671 },
672 #[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
673 Self::Dilithium2_fe2o3(keys) => keys.pk = match pk {
674 Some(pk) => Some(res!(<[u8; Self::DILITHIUM2_FE2O3_PK_LEN]>::try_from(&pk[..]))),
675 None => None,
676 },
677 }
678 Ok(self)
679 }
680
681 fn set_secret_key(mut self, sk: Option<&[u8]>) -> Outcome<Self> {
682 match &mut self {
683 Self::Ed25519(keys) => keys.sks = match sk {
684 Some(sk) => Some(Secret::new(res!(<[u8; Self::ED25519_SK_LEN]>::try_from(&sk[..])))),
685 None => None,
686 },
687 #[cfg(feature = "pq")]
688 Self::Dilithium2(keys) => keys.sks = match sk {
689 Some(sk) => Some(Secret::new(res!(<[u8; Self::DILITHIUM2_SK_LEN]>::try_from(&sk[..])))),
690 None => None,
691 },
692 #[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
693 Self::Dilithium2_fe2o3(keys) => keys.sks = match sk {
694 Some(sk) => Some(Secret::new(res!(<[u8; Self::DILITHIUM2_FE2O3_SK_LEN]>::try_from(&sk[..])))),
695 None => None,
696 },
697 }
698 Ok(self)
699 }
700}
701
702impl str::FromStr for SignatureScheme {
703 type Err = Error<ErrTag>;
704
705 fn from_str(name: &str) -> std::result::Result<Self, Self::Err> {
706 Ok(match name {
707 "Ed25519" => Self::new_ed25519(),
708 #[cfg(feature = "pq")]
709 "Dilithium2" => res!(Self::new_dilithium2()),
710 // The name is a real one, and this build simply does not carry it. Saying so is not the
711 // same as saying it does not exist, and a caller deserves to be told which it is.
712 #[cfg(not(feature = "pq"))]
713 "Dilithium2" => return Err(err!(
714 "The signature scheme 'Dilithium2' is the C reference implementation, which this \
715 build does not carry: it was built without the 'pq' feature, which needs a C \
716 toolchain. The pure-Rust 'Dilithium2_fe2o3' is here and does the same job.";
717 Invalid, Input, NoImpl)),
718 #[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
719 "Dilithium2_fe2o3" => Self::new_dilithium2_fe2o3(),
720 // The name is a real one, and this build simply does not carry it: it was built
721 // without a `mode0`..`mode3` feature, which is what gives Dilithium2_fe2o3 a
722 // parameter set.
723 #[cfg(not(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3")))]
724 "Dilithium2_fe2o3" => return Err(err!(
725 "The signature scheme 'Dilithium2_fe2o3' needs one of the 'mode0'..'mode3' \
726 features, which this build was not given.";
727 Invalid, Input, NoImpl)),
728 _ => return Err(err!(
729 "The signature scheme '{}' is not recognised.", name;
730 Invalid, Input)),
731 })
732 }
733}
734
735impl TryFrom<&LocalId> for SignatureScheme {
736 type Error = Error<ErrTag>;
737
738 fn try_from(n: &LocalId) -> std::result::Result<Self, Self::Error> {
739 Ok(match *n {
740 LocalId(1) => Self::new_ed25519(),
741 #[cfg(feature = "pq")]
742 LocalId(2) => res!(Self::new_dilithium2()),
743 #[cfg(not(feature = "pq"))]
744 LocalId(2) => return Err(err!(
745 "The signature scheme with local id 2 is Dilithium2, the C reference \
746 implementation, which this build does not carry: it was built without the 'pq' \
747 feature. The pure-Rust Dilithium2_fe2o3, local id 3, is here.";
748 Invalid, Input, NoImpl)),
749 #[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
750 LocalId(3) => Self::new_dilithium2_fe2o3(),
751 #[cfg(not(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3")))]
752 LocalId(3) => return Err(err!(
753 "The signature scheme with local id 3 is Dilithium2_fe2o3, which needs one of the \
754 'mode0'..'mode3' features, which this build was not given.";
755 Invalid, Input, NoImpl)),
756 _ => return Err(err!(
757 "The signature scheme with local id {} is not recognised.", n;
758 Invalid, Input)),
759 })
760 }
761}
762
763impl SignatureScheme {
764
765 //pub const USR_VERSION: SemVer = SemVer::new(0,0,1);
766 pub const SCHEMES: [&'static str; 3] = [
767 "<EdDSA|Ed25519>",
768 "<Dilithium|Dilithium2>",
769 "<Dilithium|Dilithium2_fe2o3>",
770 ];
771
772 pub const ED25519_PK_LEN: usize = ed25519_dalek::PUBLIC_KEY_LENGTH;
773 pub const ED25519_SK_LEN: usize = ed25519_dalek::SECRET_KEY_LENGTH;
774 pub const ED25519_SIG_LEN: usize = ed25519_dalek::SIGNATURE_LENGTH;
775 // These are the C implementation's own sizes, so they can only be asked of it when it is here.
776 #[cfg(feature = "pq")]
777 pub const DILITHIUM2_PK_LEN: usize = dilithium2::public_key_bytes();
778 #[cfg(feature = "pq")]
779 pub const DILITHIUM2_SK_LEN: usize = dilithium2::secret_key_bytes();
780 #[cfg(feature = "pq")]
781 pub const DILITHIUM2_SIG_LEN: usize = dilithium2::signature_bytes();
782 // These sizes come from the mode feature's parameter set, so they only exist with one.
783 #[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
784 pub const DILITHIUM2_FE2O3_PK_LEN: usize = dilithium2_fe2o3::params::PUBLICKEYBYTES;
785 #[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
786 pub const DILITHIUM2_FE2O3_SK_LEN: usize = dilithium2_fe2o3::params::SECRETKEYBYTES;
787 #[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
788 pub const DILITHIUM2_FE2O3_SIG_LEN: usize = dilithium2_fe2o3::params::SIG_SIZE_PACKED;
789
790 pub fn new_ed25519() -> Self {
791 let signing_key = SigningKey::generate(&mut OsRng);
792 let keys = Keys {
793 pk: Some(signing_key.verifying_key().to_bytes()),
794 sks: Some(Secret::new(signing_key.to_bytes())),
795 };
796 Self::Ed25519(keys)
797 }
798
799 pub fn empty_ed25519() -> Self {
800 Self::Ed25519(Keys::default())
801 }
802
803 #[cfg(feature = "pq")]
804 pub fn new_dilithium2() -> Outcome<Self> {
805 let (pk, sk) = dilithium2::keypair();
806 const PK_LEN: usize = dilithium2::public_key_bytes();
807 const SK_LEN: usize = dilithium2::secret_key_bytes();
808 let keys = Keys {
809 pk: Some(res!(<[u8; PK_LEN]>::try_from(&(pk.as_bytes())[..]))),
810 sks: Some(Secret::new(res!(<[u8; SK_LEN]>::try_from(&(sk.as_bytes())[..])))),
811 };
812 Ok(Self::Dilithium2(keys))
813 }
814
815 #[cfg(feature = "pq")]
816 pub fn empty_dilithium2() -> Self {
817 Self::Dilithium2(Keys::default())
818 }
819
820 #[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
821 pub fn new_dilithium2_fe2o3() -> Self {
822 let (mut pk, mut sk) = (
823 [0; Self::DILITHIUM2_FE2O3_PK_LEN],
824 [0; Self::DILITHIUM2_FE2O3_SK_LEN],
825 );
826 dilithium2_fe2o3::sign::keypair(&mut OsRng_old, &mut pk, &mut sk);
827 let keys = Keys {
828 pk: Some(pk),
829 sks: Some(Secret::new(sk)),
830 };
831 Self::Dilithium2_fe2o3(keys)
832 }
833
834 #[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
835 pub fn empty_dilithium2_fe2o3() -> Self {
836 Self::Dilithium2_fe2o3(Keys::default())
837 }
838}
839
840
841#[cfg(test)]
842mod tests {
843 use super::*;
844
845 /// A key pair together with the public key bytes, which is what a batch item
846 /// wants and what the scheme hands back only as an `Option`.
847 struct Pair {
848 /// The scheme, holding both keys.
849 scheme: SignatureScheme,
850 /// The public key bytes.
851 public: Vec<u8>,
852 }
853
854 /// Mints an Ed25519 pair for a test.
855 fn pair() -> Outcome<Pair> {
856 let scheme = SignatureScheme::new_ed25519();
857 let public = match res!(scheme.get_public_key()) {
858 Some(pk) => pk.to_vec(),
859 None => return Err(err!("A minted Ed25519 pair has no public key."; Bug, Missing)),
860 };
861 Ok(Pair { scheme, public })
862 }
863
864 /// Checks one signature the way a caller checks one, so that a test can ask
865 /// whether the batch and the single agree.
866 fn singly(public: &[u8], msg: &[u8], sig: &[u8]) -> Outcome<bool> {
867 let bound = res!(SignatureScheme::empty_ed25519().clone_with_keys(Some(public), None));
868 bound.verify(msg, sig)
869 }
870
871 /// A batch of sound signatures by several signers holds, and an empty batch
872 /// holds vacuously.
873 #[test]
874 fn a_batch_of_sound_ed25519_signatures_holds() -> Outcome<()> {
875 let a = res!(pair());
876 let b = res!(pair());
877 let msgs: Vec<Vec<u8>> = (0..8u8).map(|i| vec![i; 64 + i as usize]).collect();
878 let mut sigs = Vec::new();
879 for (i, msg) in msgs.iter().enumerate() {
880 let who = if i % 2 == 0 { &a } else { &b };
881 sigs.push(res!(who.scheme.sign(msg)));
882 }
883 let items: Vec<BatchItem<'_>> = (0..msgs.len())
884 .map(|i| BatchItem {
885 public: if i % 2 == 0 { &a.public } else { &b.public },
886 msg: &msgs[i],
887 sig: &sigs[i],
888 })
889 .collect();
890 let algorithm = SignatureScheme::empty_ed25519();
891 assert!(res!(algorithm.verify_batch(&items)));
892 assert!(res!(algorithm.verify_batch(&[])), "an empty batch holds vacuously");
893 Ok(())
894 }
895
896 /// One tampered signature anywhere in the batch fails it, and checking the
897 /// same items one at a time then finds exactly that one.
898 ///
899 /// This is what lets a caller batch at all. The batch is permitted to be
900 /// silent about which member failed only because the fallback is guaranteed
901 /// to find it; a batch that failed while every member passed singly would
902 /// leave a caller with a refusal it cannot explain.
903 #[test]
904 fn a_tampered_signature_fails_the_batch_and_is_found_singly() -> Outcome<()> {
905 let a = res!(pair());
906 let msgs: Vec<Vec<u8>> = (0..6u8).map(|i| vec![i; 40]).collect();
907 let algorithm = SignatureScheme::empty_ed25519();
908 for spoiled in 0..msgs.len() {
909 let mut sigs = Vec::new();
910 for (i, msg) in msgs.iter().enumerate() {
911 let mut sig = res!(a.scheme.sign(msg));
912 if i == spoiled {
913 sig[40] ^= 0x01; // Within s, so the encoding stays well formed.
914 }
915 sigs.push(sig);
916 }
917 let items: Vec<BatchItem<'_>> = (0..msgs.len())
918 .map(|i| BatchItem { public: &a.public, msg: &msgs[i], sig: &sigs[i] })
919 .collect();
920 assert!(!res!(algorithm.verify_batch(&items)),
921 "the batch holding a tampered signature at {} was accepted", spoiled);
922 let mut bad = Vec::new();
923 for (i, item) in items.iter().enumerate() {
924 if !res!(singly(item.public, item.msg, item.sig)) {
925 bad.push(i);
926 }
927 }
928 assert_eq!(bad, vec![spoiled], "the fallback named the wrong signature");
929 }
930 Ok(())
931 }
932
933 /// The public key cache does not let a second signature by a signer already
934 /// in the batch go unchecked.
935 ///
936 /// A cache keyed on the public key is exactly the shape of mistake where the
937 /// *key* is remembered as having been checked rather than the *signature*,
938 /// and every signature after the first by that signer is then waved through.
939 /// Every item here is signed by one key and every one of them is tampered
940 /// with in turn.
941 #[test]
942 fn the_key_cache_does_not_wave_a_repeat_signer_through() -> Outcome<()> {
943 let a = res!(pair());
944 let algorithm = SignatureScheme::empty_ed25519();
945 let msgs: Vec<Vec<u8>> = (0..5u8).map(|i| vec![i; 32]).collect();
946 for spoiled in 0..msgs.len() {
947 let mut sigs = Vec::new();
948 for (i, msg) in msgs.iter().enumerate() {
949 let mut sig = res!(a.scheme.sign(msg));
950 if i == spoiled {
951 sig[40] ^= 0x01;
952 }
953 sigs.push(sig);
954 }
955 let items: Vec<BatchItem<'_>> = (0..msgs.len())
956 .map(|i| BatchItem { public: &a.public, msg: &msgs[i], sig: &sigs[i] })
957 .collect();
958 assert!(!res!(algorithm.verify_batch(&items)),
959 "signature {} by an already cached key was not checked", spoiled);
960 }
961 Ok(())
962 }
963
964 /// Swapping two signatures between messages fails the batch, which a batch
965 /// that only counted signatures would not catch.
966 #[test]
967 fn signatures_swapped_between_messages_fail_the_batch() -> Outcome<()> {
968 let a = res!(pair());
969 let one = b"the first message".to_vec();
970 let two = b"the second message".to_vec();
971 let sig_one = res!(a.scheme.sign(&one));
972 let sig_two = res!(a.scheme.sign(&two));
973 let items = vec![
974 BatchItem { public: &a.public, msg: &one, sig: &sig_two },
975 BatchItem { public: &a.public, msg: &two, sig: &sig_one },
976 ];
977 assert!(!res!(SignatureScheme::empty_ed25519().verify_batch(&items)));
978 Ok(())
979 }
980
981 /// The batch accepts a signature exactly when checking it alone does.
982 ///
983 /// The two are different equations, and the reason a batch may stand in for
984 /// the single check is that they accept the same set. Every one-bit change
985 /// tried here has to be refused by both or accepted by both.
986 #[test]
987 fn the_batch_and_the_single_check_agree() -> Outcome<()> {
988 let a = res!(pair());
989 let b = res!(pair());
990 let msg = b"a message worth signing".to_vec();
991 let sound = res!(a.scheme.sign(&msg));
992 let algorithm = SignatureScheme::empty_ed25519();
993
994 // Each case is (what it is, public key, message, signature).
995 let mut spoiled_msg = msg.clone();
996 spoiled_msg[0] ^= 0x01;
997 let mut spoiled_sig_r = sound.clone();
998 spoiled_sig_r[0] ^= 0x01;
999 let mut spoiled_sig_s = sound.clone();
1000 spoiled_sig_s[40] ^= 0x01;
1001 let non_canonical_r = {
1002 // A y coordinate of p itself, which decompression reduces and which
1003 // therefore compresses back to different bytes.
1004 let mut sig = sound.clone();
1005 sig[..32].copy_from_slice(&FIELD_MODULUS);
1006 sig
1007 };
1008 // The identity as a key, R the identity and S zero: the cofactorless
1009 // equation holds for every message.
1010 let mut identity = [0u8; 32];
1011 identity[0] = 0x01;
1012 let mut forged = [0u8; 64];
1013 forged[0] = 0x01;
1014 // A point of order four, y = 0, as a key.
1015 let order_four = [0u8; 32];
1016 let cases: Vec<(&str, &[u8], &[u8], &[u8], bool)> = vec![
1017 ("sound", &a.public, &msg, &sound, true),
1018 ("another's key", &b.public, &msg, &sound, false),
1019 ("altered message", &a.public, &spoiled_msg, &sound, false),
1020 ("altered R", &a.public, &msg, &spoiled_sig_r, false),
1021 ("altered s", &a.public, &msg, &spoiled_sig_s, false),
1022 ("non canonical R", &a.public, &msg, &non_canonical_r, false),
1023 ("identity key forgery",&identity, &msg, &forged, false),
1024 ("order four key", &order_four, &msg, &forged, false),
1025 ];
1026 for (what, public, message, sig, verdict) in cases {
1027 let items = vec![BatchItem { public, msg: message, sig }];
1028 let batched = res!(algorithm.verify_batch(&items));
1029 let single = res!(singly(public, message, sig));
1030 assert_eq!(batched, single,
1031 "the batch and the single check disagree about the {} case", what);
1032 assert_eq!(single, verdict, "the {} case earned the wrong verdict", what);
1033 assert_eq!(res!(verify_ed25519(public, message, sig)), single,
1034 "the free function and the scheme disagree about the {} case", what);
1035 }
1036 Ok(())
1037 }
1038
1039 /// The canonicity test admits the encodings compression produces and refuses
1040 /// the ones it does not.
1041 #[test]
1042 fn canonical_points_are_told_from_the_rest() -> Outcome<()> {
1043 let mut zero = [0u8; 32];
1044 assert!(is_canonical_point(&zero), "y = 0 is canonical");
1045 zero[31] |= 0x80;
1046 assert!(is_canonical_point(&zero), "y = 0 with the sign bit set is a real encoding");
1047
1048 assert!(!is_canonical_point(&FIELD_MODULUS), "y = p reduces to zero");
1049 let mut over = FIELD_MODULUS;
1050 over[0] = 0xee;
1051 assert!(!is_canonical_point(&over), "y = p + 1 reduces to one");
1052 let mut under = FIELD_MODULUS;
1053 under[0] = 0xec;
1054 assert!(is_canonical_point(&under), "y = p - 1 is canonical");
1055 under[31] |= 0x80;
1056 assert!(!is_canonical_point(&under), "y = p - 1 has x = 0, so it has no negative");
1057
1058 let mut one = [0u8; 32];
1059 one[0] = 0x01;
1060 assert!(is_canonical_point(&one), "y = 1 is the identity");
1061 one[31] |= 0x80;
1062 assert!(!is_canonical_point(&one), "y = 1 has x = 0, so it has no negative");
1063
1064 let mut two = [0u8; 32];
1065 two[0] = 0x02;
1066 two[31] |= 0x80;
1067 assert!(is_canonical_point(&two), "y = 2 has a negative like any other");
1068
1069 assert!(!is_canonical_point(&[0u8; 31]), "a short encoding is not one");
1070 assert!(!is_canonical_point(&[0u8; 33]), "a long encoding is not one");
1071 Ok(())
1072 }
1073
1074 /// A build without the batch equation gives the same answers as one with it.
1075 ///
1076 /// This is the wasm32 path stated as a property. A browser build that leaves
1077 /// `batch` off -- or any build that does -- falls back to `verify_each`, and
1078 /// what must not differ between the two is which signatures are accepted.
1079 /// The test compares the two here, in one build, so that a divergence shows
1080 /// up on a developer's machine rather than only in a browser.
1081 #[test]
1082 fn the_batchless_path_agrees_with_the_batch() -> Outcome<()> {
1083 let a = res!(pair());
1084 let b = res!(pair());
1085 let msgs: Vec<Vec<u8>> = (0..4u8).map(|i| vec![i; 48]).collect();
1086 let algorithm = SignatureScheme::empty_ed25519();
1087 // Every subset of the four having its signature spoiled, sixteen in all,
1088 // so the two paths are compared on sound sets and unsound ones alike.
1089 for spoiled in 0..16u8 {
1090 let mut sigs = Vec::new();
1091 for (i, msg) in msgs.iter().enumerate() {
1092 let who = if i % 2 == 0 { &a } else { &b };
1093 let mut sig = res!(who.scheme.sign(msg));
1094 if spoiled & (1 << i) != 0 {
1095 sig[40] ^= 0x01;
1096 }
1097 sigs.push(sig);
1098 }
1099 let items: Vec<BatchItem<'_>> = (0..msgs.len())
1100 .map(|i| BatchItem {
1101 public: if i % 2 == 0 { &a.public } else { &b.public },
1102 msg: &msgs[i],
1103 sig: &sigs[i],
1104 })
1105 .collect();
1106 let batched = res!(algorithm.verify_batch(&items));
1107 let each = res!(verify_each(&algorithm, &items));
1108 assert_eq!(batched, each,
1109 "the two paths disagree about the set spoiled at {:04b}", spoiled);
1110 assert_eq!(batched, spoiled == 0,
1111 "the set spoiled at {:04b} was not judged on its merits", spoiled);
1112 }
1113 Ok(())
1114 }
1115
1116 /// A scheme with no batch equation still answers the batch, one signature at
1117 /// a time, so a caller need not ask which scheme it holds.
1118 #[cfg(any(feature = "mode0", feature = "mode1", feature = "mode2", feature = "mode3"))]
1119 #[test]
1120 fn a_scheme_without_a_batch_equation_still_answers() -> Outcome<()> {
1121 let scheme = SignatureScheme::new_dilithium2_fe2o3();
1122 let public = match res!(scheme.get_public_key()) {
1123 Some(pk) => pk.to_vec(),
1124 None => return Err(err!("A minted pair has no public key."; Bug, Missing)),
1125 };
1126 let msg = b"a message worth signing".to_vec();
1127 let sound = res!(scheme.sign(&msg));
1128 let mut spoiled = sound.clone();
1129 spoiled[0] ^= 0x01;
1130 assert!(res!(scheme.verify_batch(&[
1131 BatchItem { public: &public, msg: &msg, sig: &sound },
1132 ])));
1133 assert!(!res!(scheme.verify_batch(&[
1134 BatchItem { public: &public, msg: &msg, sig: &sound },
1135 BatchItem { public: &public, msg: &msg, sig: &spoiled },
1136 ])));
1137 Ok(())
1138 }
1139}