sqisign_verify/params/level5.rs
1//!
2//! Prime: `p = 27 * 2^500 - 1` (505 bits).
3//! Field uses 9 limbs of 57-bit radix (unsaturated Montgomery form).
4
5use super::{Level5, SecurityLevel};
6use hybrid_array::sizes::{U128, U129, U257, U292, U420, U576, U64, U8, U9};
7
8/// The Level 5 prime `p = 27 * 2^500 - 1` as 64 little-endian bytes.
9///
10/// In hex: `0x01af...ff` (505 bits). The bottom 62 bytes are `0xff`,
11/// byte 62 is `0xAF`, and byte 63 is `0x01`.
12///
13/// Derivation: `p + 1 = 27 * 2^500 = 0x1B * 2^500`. Bit 500 lies at
14/// bit-position 4 within byte 62 (since 500 = 62 * 8 + 4). Subtracting
15/// 1 sets all 500 low bits to 1 and decrements the upper portion from
16/// `0x1B` to `0x1A`, which straddles bytes 62 and 63 as `0xAF` and
17/// `0x01` respectively.
18pub const PRIME_LE_BYTES: [u8; 64] = {
19 let mut bytes = [0xffu8; 64];
20 bytes[62] = 0xAF;
21 bytes[63] = 0x01;
22 bytes
23};
24
25impl SecurityLevel for Level5 {
26 /// 9 limbs × 57-bit radix = 513 bits of storage for the 505-bit prime.
27 type FpLimbs = U9;
28 /// 8 limbs × 64 bits = 512-bit scalars for order arithmetic.
29 type MpLimbs = U8;
30 /// `p` fits in 64 bytes (505 bits).
31 type FpEncodedBytes = U64;
32 /// Two `Fp` elements = 128 bytes.
33 type Fp2EncodedBytes = U128;
34 /// Public key: 1-byte header + 2 × 64 bytes for the `Fp2` j-invariant.
35 type PkLen = U129;
36 /// Signature: compressed response isogeny encoding (292 bytes).
37 type SigLen = U292;
38 /// Expanded signature (420 bytes).
39 type ExpandedSigLen = U420;
40 /// Compressed signature (257 bytes).
41 type CompressedSigLen = U257;
42 /// Secret key: ideal norm + generator coords + basis-change matrix.
43 /// Actual content is 572 bytes; U576 is the next upstream hybrid-array
44 /// size. The 4 trailing bytes are zero-padded.
45 type SkLen = U576;
46
47 fn prime_le_bytes() -> &'static [u8] {
48 &PRIME_LE_BYTES
49 }
50
51 /// 256-bit post-quantum security.
52 const LAMBDA: u32 = 256;
53
54 /// `p + 1 = 27 * 2^500`, so the full `2^500`-torsion is available.
55 const F_CHR: u32 = 500;
56 /// Response isogeny has degree `2^253`.
57 const E_RSP: u32 = 253;
58 /// Challenge scalar is 256 bits (matching `LAMBDA`).
59 const E_CHL: u32 = 256;
60 /// Up to 512 SHAKE256 squeeze attempts to find a valid challenge.
61 const HASH_ITERATIONS: u32 = 512;
62 /// 8 limbs × 64 = 512-bit scalar width.
63 const NWORDS_ORDER: usize = 8;
64 /// `v_2(p + 1) = 500`.
65 const TORSION_EVEN_POWER: u32 = 500;
66 /// `(p + 1) / 2^500 = 27 = 0b11011`, which is 5 bits.
67 const P_COFACTOR_FOR_2F_BITLENGTH: usize = 5;
68 /// Response isogeny length = 253 bits (same as `E_RSP`).
69 const SQISIGN_RESPONSE_LENGTH: u32 = 253;
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn level5_prime_is_correct() {
78 let bytes = Level5::prime_le_bytes();
79 assert_eq!(bytes.len(), 64);
80 for &b in &bytes[..62] {
81 assert_eq!(b, 0xFF, "low 62 bytes of p must all be 0xFF");
82 }
83 assert_eq!(bytes[62], 0xAF, "byte 62 of p must be 0xAF");
84 assert_eq!(bytes[63], 0x01, "top byte of p must be 0x01");
85 }
86
87 #[test]
88 fn level5_prime_is_3_mod_4() {
89 let bytes = Level5::prime_le_bytes();
90 assert_eq!(bytes[0] & 0b11, 3, "p mod 4 must be 3");
91 }
92
93 /// Verify the bit-length of p. The most significant byte is 0x01
94 /// (bit 504 set), giving 505 bits total. This matches `27 * 2^500 - 1`.
95 #[test]
96 fn level5_prime_bitlength() {
97 let bytes = Level5::prime_le_bytes();
98 // Byte 63 = 0x01 means bit 504 is set, bits 505-511 are zero.
99 assert_eq!(bytes[63], 0x01);
100 // Byte 62 = 0xAF = 0b1010_1111, so bit 503 (bit 7 of byte 62) is set.
101 // The topmost set bit is bit 504 (in byte 63), giving 505-bit prime.
102 assert_eq!(bytes[62] & 0x80, 0x80, "bit 503 must be set");
103 }
104
105 const _: () = assert!(Level5::F_CHR > Level5::LAMBDA);
106 const _: () = assert!(Level5::E_RSP > 0);
107
108 #[test]
109 fn level5_protocol_exponents_in_range() {
110 assert_eq!(Level5::LAMBDA, 256);
111 assert_eq!(Level5::F_CHR, 500);
112 assert_eq!(Level5::E_RSP, 253);
113 }
114}