krypteia-arcana 0.1.0

Pure-Rust classical cryptographic primitives: RSA (PKCS#1 v1.5, OAEP), ECC (NIST P-256/384/521, secp256k1), ECDSA, EdDSA (Ed25519), X25519, AES (128/192/256, GCM/CBC), DES/3DES, SHA-1/2/3, HMAC. Side-channel-aware (Montgomery ladder, branchless point_add_ct). Targets embedded (no_std), STM32 M0/M4/M33, ESP32-C3 RISC-V. Zero runtime dependencies.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
//! PKCS#1 v1.5 padding for encryption and signatures (RFC 8017).
//!
//! Encryption padding: `0x00 || 0x02 || PS || 0x00 || M`
//! Signature padding: `0x00 || 0x01 || PS || 0x00 || DigestInfo || H`
//!
//! # ⚠ Use OAEP / PSS for new deployments
//!
//! PKCS#1 v1.5 is the **legacy** padding scheme. New code should
//! prefer:
//! - [`super::oaep`] for encryption (RFC 8017 §7.1).
//! - [`super::pss`] for signatures (RFC 8017 §8.1).
//!
//! v1.5 is preserved here because it is still the only padding
//! supported by some embedded TLS stacks and HSMs. Where the
//! choice exists, do not select it.
//!
//! # Side-channel posture — Bleichenbacher
//!
//! The PKCS#1 v1.5 **encryption** scheme is the historical
//! Bleichenbacher target (CRYPTO 1998): a padding oracle on the
//! `02` prefix recovers the plaintext in ~2²⁰ – 2²² queries. RFC
//! 8017 §7.2.2 defines a constant-time decrypt that **always**
//! returns the same number of bytes whether or not padding parses
//! correctly, with the data being an internally-derived
//! deterministic dummy in the failure path. Roadmap item `T2-J`
//! tracks the audit + tightening of `pkcs1::decrypt` against this
//! recipe.
//!
//! The PKCS#1 v1.5 **signature** scheme has no analogous oracle,
//! but inherits the underlying [`super::rsa::rsa_decrypt_raw`]
//! Bellcore exposure (roadmap item `T1-C`).

use super::bigint::BigInt;
use super::rsa::{RsaPublicKey, RsaSecretKey, rsa_decrypt_raw, rsa_encrypt_raw};
use crate::Hasher;
use crate::hash::ripemd160::Ripemd160;
use crate::hash::sha1::Sha1;
use crate::hash::sha3::{Sha3_256, Sha3_384, Sha3_512};
use crate::hash::sha256::Sha256;
use crate::hash::sha384::Sha384;
use crate::hash::sha512::Sha512;

/// Hash algorithm identifiers for PKCS#1 v1.5 signatures.
///
/// Each variant carries the DER-encoded `DigestInfo` prefix that
/// gets prepended to the hash output `H` to form `T = prefix || H`.
/// Per RFC 8017 §9.2 Note 1 the prefix encodes
///
/// ```text
/// DigestInfo ::= SEQUENCE {
///     digestAlgorithm  SEQUENCE { OID, NULL },
///     digest           OCTET STRING
/// }
/// ```
///
/// SHA-3 prefixes use the OIDs assigned by NIST CSOR
/// (`2.16.840.1.101.3.4.2.{8,9,10}`) which were standardised after
/// RFC 8017 was published, but follow the same template as the
/// SHA-2 prefixes.
///
/// **Note on RIPEMD-160**: included for backwards compatibility
/// with legacy systems (Bitcoin signatures, some X.509 CAs from
/// the 2000s). Not recommended for new designs.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum HashAlg {
    /// SHA-1 (FIPS 180-4). 20-byte output. **Legacy / collision-broken**;
    /// included only for compatibility with old certificates and HMAC-SHA-1.
    Sha1,
    /// SHA-256 (FIPS 180-4). 32-byte output.
    Sha256,
    /// SHA-384 (FIPS 180-4). 48-byte output.
    Sha384,
    /// SHA-512 (FIPS 180-4). 64-byte output.
    Sha512,
    /// SHA3-256 (FIPS 202). 32-byte output.
    Sha3_256,
    /// SHA3-384 (FIPS 202). 48-byte output.
    Sha3_384,
    /// SHA3-512 (FIPS 202). 64-byte output.
    Sha3_512,
    /// RIPEMD-160 (ISO/IEC 10118-3). 20-byte output. **Legacy**;
    /// included for Bitcoin and 2000s European X.509 CAs.
    Ripemd160,
}

impl HashAlg {
    /// DER-encoded DigestInfo prefix for this hash algorithm
    /// (RFC 8017 §9.2 Note 1, plus the SHA-3 family OIDs from
    /// NIST CSOR `2.16.840.1.101.3.4.2.{8,9,10}`, and the
    /// TeleTrusT OID `1.3.36.3.2.1` for RIPEMD-160).
    fn digest_info_prefix(&self) -> &'static [u8] {
        match self {
            // SHA-1: OID 1.3.14.3.2.26 (5-byte OID)
            // -> prefix 30 21 30 09 06 05 2b 0e 03 02 1a 05 00 04 14
            HashAlg::Sha1 => &[
                0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14,
            ],
            // SHA-256: OID 2.16.840.1.101.3.4.2.1
            HashAlg::Sha256 => &[
                0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00,
                0x04, 0x20,
            ],
            // SHA-384: OID 2.16.840.1.101.3.4.2.2
            HashAlg::Sha384 => &[
                0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00,
                0x04, 0x30,
            ],
            // SHA-512: OID 2.16.840.1.101.3.4.2.3
            HashAlg::Sha512 => &[
                0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00,
                0x04, 0x40,
            ],
            // SHA3-256: OID 2.16.840.1.101.3.4.2.8
            HashAlg::Sha3_256 => &[
                0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x08, 0x05, 0x00,
                0x04, 0x20,
            ],
            // SHA3-384: OID 2.16.840.1.101.3.4.2.9
            HashAlg::Sha3_384 => &[
                0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x09, 0x05, 0x00,
                0x04, 0x30,
            ],
            // SHA3-512: OID 2.16.840.1.101.3.4.2.10
            HashAlg::Sha3_512 => &[
                0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0a, 0x05, 0x00,
                0x04, 0x40,
            ],
            // RIPEMD-160: OID 1.3.36.3.2.1 (5-byte OID, same shape as SHA-1)
            // -> prefix 30 21 30 09 06 05 2b 24 03 02 01 05 00 04 14
            HashAlg::Ripemd160 => &[
                0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03, 0x02, 0x01, 0x05, 0x00, 0x04, 0x14,
            ],
        }
    }

    /// Output length of the hash in bytes.
    fn hash_len(&self) -> usize {
        match self {
            HashAlg::Sha1 | HashAlg::Ripemd160 => 20,
            HashAlg::Sha256 | HashAlg::Sha3_256 => 32,
            HashAlg::Sha384 | HashAlg::Sha3_384 => 48,
            HashAlg::Sha512 | HashAlg::Sha3_512 => 64,
        }
    }
}

/// PKCS#1 v1.5 encryption.
///
/// Pads `msg` and encrypts with `pk`. `rng` fills buffers with random bytes.
/// Returns the ciphertext as a byte vector of length equal to the modulus.
pub fn pkcs1v15_encrypt(pk: &RsaPublicKey, msg: &[u8], rng: &mut dyn FnMut(&mut [u8])) -> Vec<u8> {
    let k = pk.modulus_byte_len();
    assert!(
        msg.len() <= k - 11,
        "PKCS1v15 encrypt: message too long (max {} bytes, got {})",
        k - 11,
        msg.len()
    );

    // EM = 0x00 || 0x02 || PS || 0x00 || M
    let ps_len = k - msg.len() - 3;
    let mut em = vec![0u8; k];
    em[0] = 0x00;
    em[1] = 0x02;

    // PS: random non-zero bytes.
    rng(&mut em[2..2 + ps_len]);
    for b in em[2..2 + ps_len].iter_mut() {
        while *b == 0 {
            let mut tmp = [0u8; 1];
            rng(&mut tmp);
            *b = tmp[0];
        }
    }

    em[2 + ps_len] = 0x00;
    em[3 + ps_len..].copy_from_slice(msg);

    let m = BigInt::from_be_bytes(&em);
    let c = rsa_encrypt_raw(pk, &m);
    c.to_be_bytes(k)
}

/// PKCS#1 v1.5 decryption.
///
/// Decrypts `ct` with `sk` and removes padding.
/// Returns `None` if padding is invalid.
pub fn pkcs1v15_decrypt(sk: &RsaSecretKey, ct: &[u8]) -> Option<Vec<u8>> {
    let k = sk.modulus_byte_len();
    if ct.len() != k || k < 11 {
        return None;
    }

    let c = BigInt::from_be_bytes(ct);
    let m = rsa_decrypt_raw(sk, &c);
    let em = m.to_be_bytes(k);

    // Check format: 0x00 || 0x02 || PS || 0x00 || M
    if em[0] != 0x00 || em[1] != 0x02 {
        return None;
    }

    // Find the 0x00 separator after PS (PS must be at least 8 bytes).
    let mut sep = None;
    for i in 2..em.len() {
        if em[i] == 0x00 {
            if i < 10 {
                // PS too short
                return None;
            }
            sep = Some(i);
            break;
        }
    }

    let sep = sep?;
    Some(em[sep + 1..].to_vec())
}

/// PKCS#1 v1.5 signature generation.
///
/// Signs the pre-computed `hash` (of `hash_alg` type) with `sk`.
pub fn pkcs1v15_sign(sk: &RsaSecretKey, hash: &[u8], hash_alg: HashAlg) -> Vec<u8> {
    let k = sk.modulus_byte_len();
    let prefix = hash_alg.digest_info_prefix();
    let t_len = prefix.len() + hash_alg.hash_len();

    assert!(hash.len() == hash_alg.hash_len(), "Hash length mismatch");
    assert!(k >= t_len + 11, "Modulus too short for PKCS1v15 signature");

    // EM = 0x00 || 0x01 || PS || 0x00 || T
    let ps_len = k - t_len - 3;
    let mut em = vec![0u8; k];
    em[0] = 0x00;
    em[1] = 0x01;
    for i in 0..ps_len {
        em[2 + i] = 0xFF;
    }
    em[2 + ps_len] = 0x00;
    em[3 + ps_len..3 + ps_len + prefix.len()].copy_from_slice(prefix);
    em[3 + ps_len + prefix.len()..].copy_from_slice(hash);

    let m = BigInt::from_be_bytes(&em);
    let s = rsa_decrypt_raw(sk, &m); // signing = decryption with private key
    s.to_be_bytes(k)
}

/// PKCS#1 v1.5 signature verification.
///
/// Verifies that `sig` is a valid signature of `hash` under `pk`.
pub fn pkcs1v15_verify(pk: &RsaPublicKey, hash: &[u8], hash_alg: HashAlg, sig: &[u8]) -> bool {
    let k = pk.modulus_byte_len();
    if sig.len() != k {
        return false;
    }
    if hash.len() != hash_alg.hash_len() {
        return false;
    }

    let s = BigInt::from_be_bytes(sig);
    let m = rsa_encrypt_raw(pk, &s); // verification = encryption with public key
    let em = m.to_be_bytes(k);

    // Reconstruct expected EM.
    let prefix = hash_alg.digest_info_prefix();
    let t_len = prefix.len() + hash_alg.hash_len();
    if k < t_len + 11 {
        return false;
    }

    let ps_len = k - t_len - 3;
    let mut expected = vec![0u8; k];
    expected[0] = 0x00;
    expected[1] = 0x01;
    for i in 0..ps_len {
        expected[2 + i] = 0xFF;
    }
    expected[2 + ps_len] = 0x00;
    expected[3 + ps_len..3 + ps_len + prefix.len()].copy_from_slice(prefix);
    expected[3 + ps_len + prefix.len()..].copy_from_slice(hash);

    // Constant-time comparison.
    let mut diff = 0u8;
    for (a, b) in em.iter().zip(expected.iter()) {
        diff |= a ^ b;
    }
    diff == 0 && em.len() == expected.len()
}

// ============================================================================
// Convenience helpers: hash a message with the chosen hash, then sign / verify.
// ============================================================================
//
// One pair per supported (hash, HashAlg variant). The macro factors the
// trivial 2-line body so adding a new hash in the future is a single line.
// SHA-256 keeps its standalone definition (and not the macro) to preserve
// the byte-for-byte stability of the public symbol that pre-existed this
// commit -- this avoids any chance of accidentally changing its inlining
// behaviour.

/// Convenience: hash a message with SHA-256, then sign.
pub fn pkcs1v15_sign_sha256(sk: &RsaSecretKey, message: &[u8]) -> Vec<u8> {
    let hash = Sha256::hash(message);
    pkcs1v15_sign(sk, &hash, HashAlg::Sha256)
}

/// Convenience: hash a message with SHA-256, then verify.
pub fn pkcs1v15_verify_sha256(pk: &RsaPublicKey, message: &[u8], sig: &[u8]) -> bool {
    let hash = Sha256::hash(message);
    pkcs1v15_verify(pk, &hash, HashAlg::Sha256, sig)
}

macro_rules! convenience_pair {
    ($sign_fn:ident, $verify_fn:ident, $hasher:ty, $alg:expr, $doc:literal) => {
        #[doc = $doc]
        pub fn $sign_fn(sk: &RsaSecretKey, message: &[u8]) -> Vec<u8> {
            let hash = <$hasher as Hasher>::hash(message);
            pkcs1v15_sign(sk, &hash, $alg)
        }

        #[doc = $doc]
        pub fn $verify_fn(pk: &RsaPublicKey, message: &[u8], sig: &[u8]) -> bool {
            let hash = <$hasher as Hasher>::hash(message);
            pkcs1v15_verify(pk, &hash, $alg, sig)
        }
    };
}

convenience_pair!(
    pkcs1v15_sign_sha1,
    pkcs1v15_verify_sha1,
    Sha1,
    HashAlg::Sha1,
    "Convenience: hash a message with SHA-1, then sign / verify. \
     **Legacy**: do not use for new designs; SHA-1 is collision-broken."
);

convenience_pair!(
    pkcs1v15_sign_sha384,
    pkcs1v15_verify_sha384,
    Sha384,
    HashAlg::Sha384,
    "Convenience: hash a message with SHA-384, then sign / verify."
);

convenience_pair!(
    pkcs1v15_sign_sha512,
    pkcs1v15_verify_sha512,
    Sha512,
    HashAlg::Sha512,
    "Convenience: hash a message with SHA-512, then sign / verify."
);

convenience_pair!(
    pkcs1v15_sign_sha3_256,
    pkcs1v15_verify_sha3_256,
    Sha3_256,
    HashAlg::Sha3_256,
    "Convenience: hash a message with SHA3-256, then sign / verify."
);

convenience_pair!(
    pkcs1v15_sign_sha3_384,
    pkcs1v15_verify_sha3_384,
    Sha3_384,
    HashAlg::Sha3_384,
    "Convenience: hash a message with SHA3-384, then sign / verify."
);

convenience_pair!(
    pkcs1v15_sign_sha3_512,
    pkcs1v15_verify_sha3_512,
    Sha3_512,
    HashAlg::Sha3_512,
    "Convenience: hash a message with SHA3-512, then sign / verify."
);

convenience_pair!(
    pkcs1v15_sign_ripemd160,
    pkcs1v15_verify_ripemd160,
    Ripemd160,
    HashAlg::Ripemd160,
    "Convenience: hash a message with RIPEMD-160, then sign / verify. \
     **Legacy**: included for compatibility with older systems \
     (Bitcoin, some 2000s X.509 CAs); not recommended for new designs."
);

#[cfg(test)]
mod tests {
    use super::*;

    fn test_rng() -> impl FnMut(&mut [u8]) {
        let mut state: u64 = 0xdeadbeefcafebabe;
        move |buf: &mut [u8]| {
            for b in buf.iter_mut() {
                state = state
                    .wrapping_mul(6364136223846793005)
                    .wrapping_add(1442695040888963407);
                *b = (state >> 33) as u8;
            }
        }
    }

    #[test]
    fn test_pkcs1v15_encrypt_decrypt_roundtrip() {
        let mut rng = test_rng();
        let (pk, sk) = super::super::rsa::rsa_keygen(512, &mut rng);
        let msg = b"Hello, RSA!";
        let ct = pkcs1v15_encrypt(&pk, msg, &mut rng);
        let pt = pkcs1v15_decrypt(&sk, &ct).expect("decryption failed");
        assert_eq!(&pt, msg);
    }

    #[test]
    fn test_pkcs1v15_sign_verify_roundtrip() {
        let mut rng = test_rng();
        let (pk, sk) = super::super::rsa::rsa_keygen(512, &mut rng);
        let message = b"Sign me!";
        let sig = pkcs1v15_sign_sha256(&sk, message);
        assert!(pkcs1v15_verify_sha256(&pk, message, &sig));
        // Tamper with signature => should fail.
        let mut bad_sig = sig.clone();
        bad_sig[0] ^= 0xFF;
        assert!(!pkcs1v15_verify_sha256(&pk, message, &bad_sig));
    }

    /// Round-trip every supported hash through the convenience helpers.
    /// Uses a single 1024-bit key (regenerating per hash would multiply
    /// the test runtime by 7 with no extra coverage).
    ///
    /// 1024 bits is the smallest modulus that fits all 7 EMSA-PKCS1-v1_5
    /// padded values: SHA-512 / SHA3-512 need `t_len = 19 + 64 = 83`
    /// bytes plus 11 bytes of mandatory padding overhead = 94 bytes
    /// minimum, so a 768-bit (96-byte) modulus would just barely work
    /// and 1024 bits (128 bytes) is the next standard size.
    #[test]
    fn pkcs1v15_all_supported_hashes_roundtrip() {
        let mut rng = test_rng();
        let (pk, sk) = super::super::rsa::rsa_keygen(1024, &mut rng);
        let msg = b"hash flexibility test";

        // SHA-1
        let sig = pkcs1v15_sign_sha1(&sk, msg);
        assert!(pkcs1v15_verify_sha1(&pk, msg, &sig));
        // SHA-256 (already covered by the older test, but include here too)
        let sig = pkcs1v15_sign_sha256(&sk, msg);
        assert!(pkcs1v15_verify_sha256(&pk, msg, &sig));
        // SHA-384
        let sig = pkcs1v15_sign_sha384(&sk, msg);
        assert!(pkcs1v15_verify_sha384(&pk, msg, &sig));
        // SHA-512
        let sig = pkcs1v15_sign_sha512(&sk, msg);
        assert!(pkcs1v15_verify_sha512(&pk, msg, &sig));
        // SHA3-256
        let sig = pkcs1v15_sign_sha3_256(&sk, msg);
        assert!(pkcs1v15_verify_sha3_256(&pk, msg, &sig));
        // SHA3-384
        let sig = pkcs1v15_sign_sha3_384(&sk, msg);
        assert!(pkcs1v15_verify_sha3_384(&pk, msg, &sig));
        // SHA3-512
        let sig = pkcs1v15_sign_sha3_512(&sk, msg);
        assert!(pkcs1v15_verify_sha3_512(&pk, msg, &sig));
        // RIPEMD-160
        let sig = pkcs1v15_sign_ripemd160(&sk, msg);
        assert!(pkcs1v15_verify_ripemd160(&pk, msg, &sig));
    }

    /// Verify must reject when the **declared hash algorithm** does
    /// not match what the signer used. This is the property that
    /// makes the DigestInfo prefix actually meaningful: a SHA-256
    /// signature must NOT verify as if it were a SHA-512 signature
    /// even with the same RSA key and the same message bytes.
    ///
    /// Catches a class of attacks where a downgrade in the declared
    /// hash would let an attacker substitute a precomputed-collision
    /// payload under a weaker hash.
    #[test]
    fn pkcs1v15_hash_mismatch_rejected() {
        let mut rng = test_rng();
        let (pk, sk) = super::super::rsa::rsa_keygen(1024, &mut rng);
        let msg = b"hash mismatch test";

        // Sign with SHA-256, verify as SHA-256 -> OK.
        let sig256 = pkcs1v15_sign_sha256(&sk, msg);
        assert!(pkcs1v15_verify_sha256(&pk, msg, &sig256));

        // Same signature, verify as SHA-512 -> must reject (different
        // DigestInfo prefix, different hash length, completely
        // different reconstructed EM).
        assert!(!pkcs1v15_verify_sha512(&pk, msg, &sig256));

        // Same signature, verify as SHA3-256 -> must reject (same
        // hash length but different OID byte in the DigestInfo
        // prefix, so the reconstructed EM differs by exactly one
        // byte). This is the strictest possible mismatch test.
        assert!(!pkcs1v15_verify_sha3_256(&pk, msg, &sig256));

        // Sign with SHA-512, verify as SHA-256 -> must reject.
        let sig512 = pkcs1v15_sign_sha512(&sk, msg);
        assert!(pkcs1v15_verify_sha512(&pk, msg, &sig512));
        assert!(!pkcs1v15_verify_sha256(&pk, msg, &sig512));

        // Sign with SHA3-256, verify as SHA-256 -> must reject (the
        // critical hash-family mixup case).
        let sig3_256 = pkcs1v15_sign_sha3_256(&sk, msg);
        assert!(pkcs1v15_verify_sha3_256(&pk, msg, &sig3_256));
        assert!(!pkcs1v15_verify_sha256(&pk, msg, &sig3_256));
    }

    /// `HashAlg::hash_len()` must agree with the actual `OUTPUT_LEN`
    /// of the underlying `Hasher` implementation. If they ever
    /// drifted (e.g. someone changed SHA-3 truncation), the sign
    /// path would assert-fail at runtime; this test catches that
    /// at compile-then-run time.
    #[test]
    fn hashalg_lengths_agree_with_hasher_output_len() {
        assert_eq!(HashAlg::Sha1.hash_len(), <Sha1 as Hasher>::OUTPUT_LEN);
        assert_eq!(HashAlg::Sha256.hash_len(), <Sha256 as Hasher>::OUTPUT_LEN);
        assert_eq!(HashAlg::Sha384.hash_len(), <Sha384 as Hasher>::OUTPUT_LEN);
        assert_eq!(HashAlg::Sha512.hash_len(), <Sha512 as Hasher>::OUTPUT_LEN);
        assert_eq!(HashAlg::Sha3_256.hash_len(), <Sha3_256 as Hasher>::OUTPUT_LEN);
        assert_eq!(HashAlg::Sha3_384.hash_len(), <Sha3_384 as Hasher>::OUTPUT_LEN);
        assert_eq!(HashAlg::Sha3_512.hash_len(), <Sha3_512 as Hasher>::OUTPUT_LEN);
        assert_eq!(HashAlg::Ripemd160.hash_len(), <Ripemd160 as Hasher>::OUTPUT_LEN);
    }
}