sqisign_verify/params/level1.rs
1//!
2//! Prime: `p = 5 * 2^248 - 1` (251 bits).
3//! Field uses 5 limbs of 51-bit radix (unsaturated Montgomery form).
4
5use super::{Level1, SecurityLevel};
6use hybrid_array::sizes::{U129, U148, U212, U288, U32, U4, U5, U64, U65};
7
8/// The Level 1 prime `p = 5 * 2^248 - 1` as 32 little-endian bytes.
9///
10/// In hex this is `0x04ff..ff` (top byte `0x04`, then 31 bytes of `0xff`),
11/// matching `p` in `sqisign_parameters.txt`.
12pub const PRIME_LE_BYTES: [u8; 32] = {
13 let mut bytes = [0xffu8; 32];
14 bytes[31] = 0x04;
15 bytes
16};
17
18impl SecurityLevel for Level1 {
19 /// 5 limbs × 51-bit radix = 255 bits of storage for the 251-bit prime.
20 type FpLimbs = U5;
21 /// 4 limbs × 64 bits = 256-bit scalars for order arithmetic.
22 type MpLimbs = U4;
23 /// `p` fits in 32 bytes (251 bits).
24 type FpEncodedBytes = U32;
25 /// Two `Fp` elements = 64 bytes.
26 type Fp2EncodedBytes = U64;
27 /// Public key: 1-byte header + 2 × 32 bytes for the `Fp2` j-invariant.
28 type PkLen = U65;
29 /// Standard signature (148 bytes).
30 type SigLen = U148;
31 /// Expanded signature (212 bytes).
32 type ExpandedSigLen = U212;
33 /// Compressed signature (129 bytes).
34 type CompressedSigLen = U129;
35 /// Secret key: ideal norm + generator coords + basis-change matrix (288 bytes).
36 type SkLen = U288;
37
38 fn prime_le_bytes() -> &'static [u8] {
39 &PRIME_LE_BYTES
40 }
41
42 /// 128-bit post-quantum security.
43 const LAMBDA: u32 = 128;
44
45 /// `p + 1 = 5 × 2^248`, so the full `2^248`-torsion is available.
46 const F_CHR: u32 = 248;
47 /// Response isogeny has degree `2^126`.
48 const E_RSP: u32 = 126;
49 /// Challenge scalar is 128 bits (matching `LAMBDA`).
50 const E_CHL: u32 = 128;
51 /// Up to 64 SHAKE256 squeeze attempts to find a valid challenge.
52 const HASH_ITERATIONS: u32 = 64;
53 /// 4 limbs × 64 = 256-bit scalar width.
54 const NWORDS_ORDER: usize = 4;
55 /// `v_2(p + 1) = 248`.
56 const TORSION_EVEN_POWER: u32 = 248;
57 /// `(p + 1) / 2^248 = 5`, which is 3 bits.
58 const P_COFACTOR_FOR_2F_BITLENGTH: usize = 3;
59 /// Response isogeny length = 126 bits (same as `E_RSP`).
60 const SQISIGN_RESPONSE_LENGTH: u32 = 126;
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 /// Reconstruct `p` from `PRIME_LE_BYTES` and verify it equals
68 /// `5 * 2^248 - 1`.
69 ///
70 /// We do the comparison limb-by-limb: little-endian, bytes 0..31 all
71 /// `0xFF` except byte 31 which is `0x04`. The integer is therefore
72 /// `4 * 2^248 + (2^248 - 1) = 5 * 2^248 - 1`.
73 #[test]
74 fn level1_prime_is_correct() {
75 let bytes = Level1::prime_le_bytes();
76 assert_eq!(bytes.len(), 32);
77 for &b in &bytes[..31] {
78 assert_eq!(b, 0xFF, "low 31 bytes of p must all be 0xFF");
79 }
80 assert_eq!(bytes[31], 0x04, "top byte of p must be 0x04");
81 }
82
83 /// `p = 5 * 2^248 - 1`, so `p mod 4 = (5*2^248 mod 4) - 1 mod 4 =
84 /// 0 - 1 mod 4 = 3`. Required for the 𝔽p² = 𝔽p[i]/(i² + 1)
85 /// construction and the Fermat-style `Fp` square root.
86 #[test]
87 fn level1_prime_is_3_mod_4() {
88 let bytes = Level1::prime_le_bytes();
89 // Bottom byte determines the value mod 4 (since 256 = 0 mod 4).
90 assert_eq!(bytes[0] & 0b11, 3, "p mod 4 must be 3");
91 }
92
93 /// Sanity: protocol exponents are in expected ranges. The two
94 /// `const _: () = assert!(...)` blocks fire at compile time if the
95 /// invariants are violated by future edits; the runtime asserts
96 /// just make the constants observable in the test output.
97 const _: () = assert!(Level1::F_CHR > Level1::LAMBDA);
98 const _: () = assert!(Level1::E_RSP > 0);
99
100 #[test]
101 fn level1_protocol_exponents_in_range() {
102 assert_eq!(Level1::LAMBDA, 128);
103 assert_eq!(Level1::F_CHR, 248);
104 assert_eq!(Level1::E_RSP, 126);
105 }
106}