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
| 1 | use 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"))] |
| 5 | use crate::pqc::dilithium as dilithium2_fe2o3; |
| 6 | |
| 7 | use oxedyne_fe2o3_core::prelude::*; |
| 8 | use 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 | ))] |
| 22 | use oxedyne_fe2o3_iop_crypto::sign::verify_each; |
| 23 | use oxedyne_fe2o3_namex::{ |
| 24 | id::{ |
| 25 | LocalId, |
| 26 | InNamex, |
| 27 | NamexId, |
| 28 | }, |
| 29 | }; |
| 30 | |
| 31 | use std::{ |
| 32 | collections::BTreeMap, |
| 33 | convert::TryFrom, |
| 34 | fmt::{ |
| 35 | self, |
| 36 | Debug, |
| 37 | }, |
| 38 | str, |
| 39 | }; |
| 40 | |
| 41 | #[cfg(feature = "batch")] |
| 42 | use curve25519_dalek::{ |
| 43 | constants::ED25519_BASEPOINT_POINT, |
| 44 | traits::VartimeMultiscalarMul, |
| 45 | }; |
| 46 | use curve25519_dalek::{ |
| 47 | edwards::{ |
| 48 | CompressedEdwardsY, |
| 49 | EdwardsPoint, |
| 50 | }, |
| 51 | scalar::Scalar, |
| 52 | traits::IsIdentity, |
| 53 | }; |
| 54 | use ed25519_dalek::{ |
| 55 | SigningKey, |
| 56 | Signer as DalekSigner, |
| 57 | }; |
| 58 | use sha2::{ |
| 59 | Digest, |
| 60 | Sha512, |
| 61 | }; |
| 62 | |
| 63 | #[cfg(feature = "pq")] |
| 64 | use pqcrypto_dilithium::dilithium2; |
| 65 | #[cfg(feature = "pq")] |
| 66 | use 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"))] |
| 73 | use rand_core_old::OsRng as OsRng_old; |
| 74 | use rand_core::OsRng; |
| 75 | use 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"))] |
| 81 | use 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)] |
| 88 | pub 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 | |
| 108 | impl 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 | |
| 120 | impl 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 | |
| 171 | impl 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`. |
| 293 | pub 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. |
| 307 | struct 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. |
| 315 | fn 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. |
| 338 | fn 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. |
| 364 | fn 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? |
| 375 | fn 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. |
| 382 | const 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. |
| 402 | fn 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. |
| 436 | struct 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")] |
| 444 | const 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")] |
| 463 | fn 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"))] |
| 502 | fn 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. |
| 533 | fn 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 | |
| 566 | impl 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 | |
| 702 | impl 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 | |
| 735 | impl 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 | |
| 763 | impl 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)] |
| 842 | mod 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 | } |