miden-crypto/src/dsa/rpo_falcon512/keys/secret_key.rs
src/dsa/rpo_falcon512/keys/secret_key.rsRaw
1use super::{
2 super::{
3 math::{ffldl, ffsampling, gram, normalize_tree, FalconFelt, FastFft, LdlTree, Polynomial},
4 signature::SignaturePoly,
5 ByteReader, ByteWriter, Deserializable, DeserializationError, Nonce, Serializable,
6 ShortLatticeBasis, Signature, Word, MODULUS, N, SIGMA, SIG_L2_BOUND,
7 },
8 PubKeyPoly, PublicKey,
9};
10use crate::dsa::rpo_falcon512::{
11 hash_to_point::hash_to_point_rpo256, math::ntru_gen, SIG_NONCE_LEN, SK_LEN,
12};
13use alloc::{string::ToString, vec::Vec};
14use num::Complex;
15use num_complex::Complex64;
16use rand::Rng;
17
18#[cfg(not(feature = "std"))]
19use num::Float;
20
21// CONSTANTS
22// ================================================================================================
23
24const WIDTH_BIG_POLY_COEFFICIENT: usize = 8;
25const WIDTH_SMALL_POLY_COEFFICIENT: usize = 6;
26
27// SECRET KEY
28// ================================================================================================
29
30/// The secret key is a quadruple [[g, -f], [G, -F]] of polynomials with integer coefficients.
31///
32/// Each polynomial is of degree at most N = 512 and computations with these polynomials are done
33/// modulo the monic irreducible polynomial ϕ = x^N + 1. The secret key is a basis for a lattice
34/// and has the property of being short with respect to a certain norm and an upper bound
35/// appropriate for a given security parameter. The public key on the other hand is another basis
36/// for the same lattice and can be described by a single polynomial h with integer coefficients
37/// modulo ϕ. The two keys are related by the following relation:
38///
39/// 1. h = g /f [mod ϕ][mod p]
40/// 2. f.G - g.F = p [mod ϕ]
41///
42/// where p = 12289 is the Falcon prime. Equation 2 is called the NTRU equation.
43/// The secret key is generated by first sampling a random pair (f, g) of polynomials using
44/// an appropriate distribution that yields short but not too short polynomials with integer
45/// coefficients modulo ϕ. The NTRU equation is then used to find a matching pair (F, G).
46/// The public key is then derived from the secret key using equation 1.
47///
48/// To allow for fast signature generation, the secret key is pre-processed into a more suitable
49/// form, called the LDL tree, and this allows for fast sampling of short vectors in the lattice
50/// using Fast Fourier sampling during signature generation (ffSampling algorithm 11 in [1]).
51///
52/// [1]: https://falcon-sign.info/falcon.pdf
53#[derive(Debug, Clone)]
54pub struct SecretKey {
55 secret_key: ShortLatticeBasis,
56 tree: LdlTree,
57}
58
59#[allow(clippy::new_without_default)]
60impl SecretKey {
61 // CONSTRUCTORS
62 // --------------------------------------------------------------------------------------------
63
64 /// Generates a secret key from OS-provided randomness.
65 #[cfg(feature = "std")]
66 pub fn new() -> Self {
67 use rand::{rngs::StdRng, SeedableRng};
68
69 let mut rng = StdRng::from_entropy();
70 Self::with_rng(&mut rng)
71 }
72
73 /// Generates a secret_key using the provided random number generator `Rng`.
74 pub fn with_rng<R: Rng>(rng: &mut R) -> Self {
75 let basis = ntru_gen(N, rng);
76 Self::from_short_lattice_basis(basis)
77 }
78
79 /// Given a short basis [[g, -f], [G, -F]], computes the normalized LDL tree i.e., Falcon tree.
80 fn from_short_lattice_basis(basis: ShortLatticeBasis) -> SecretKey {
81 // FFT each polynomial of the short basis.
82 let basis_fft = to_complex_fft(&basis);
83 // compute the Gram matrix.
84 let gram_fft = gram(basis_fft);
85 // construct the LDL tree of the Gram matrix.
86 let mut tree = ffldl(gram_fft);
87 // normalize the leaves of the LDL tree.
88 normalize_tree(&mut tree, SIGMA);
89 Self { secret_key: basis, tree }
90 }
91
92 // PUBLIC ACCESSORS
93 // --------------------------------------------------------------------------------------------
94
95 /// Returns the polynomials of the short lattice basis of this secret key.
96 pub fn short_lattice_basis(&self) -> &ShortLatticeBasis {
97 &self.secret_key
98 }
99
100 /// Returns the public key corresponding to this secret key.
101 pub fn public_key(&self) -> PublicKey {
102 self.compute_pub_key_poly().into()
103 }
104
105 /// Returns the LDL tree associated to this secret key.
106 pub fn tree(&self) -> &LdlTree {
107 &self.tree
108 }
109
110 // SIGNATURE GENERATION
111 // --------------------------------------------------------------------------------------------
112
113 /// Signs a message with this secret key.
114 #[cfg(feature = "std")]
115 pub fn sign(&self, message: Word) -> Signature {
116 use rand::{rngs::StdRng, SeedableRng};
117
118 let mut rng = StdRng::from_entropy();
119 self.sign_with_rng(message, &mut rng)
120 }
121
122 /// Signs a message with the secret key relying on the provided randomness generator.
123 pub fn sign_with_rng<R: Rng>(&self, message: Word, rng: &mut R) -> Signature {
124 let mut nonce_bytes = [0u8; SIG_NONCE_LEN];
125 rng.fill_bytes(&mut nonce_bytes);
126 let nonce = Nonce::new(nonce_bytes);
127
128 let h = self.compute_pub_key_poly();
129 let c = hash_to_point_rpo256(message, &nonce);
130 let s2 = self.sign_helper(c, rng);
131
132 Signature::new(nonce, h, s2)
133 }
134
135 // HELPER METHODS
136 // --------------------------------------------------------------------------------------------
137
138 /// Derives the public key corresponding to this secret key using h = g /f [mod ϕ][mod p].
139 pub fn compute_pub_key_poly(&self) -> PubKeyPoly {
140 let g: Polynomial<FalconFelt> = self.secret_key[0].clone().into();
141 let g_fft = g.fft();
142 let minus_f: Polynomial<FalconFelt> = self.secret_key[1].clone().into();
143 let f = -minus_f;
144 let f_fft = f.fft();
145 let h_fft = g_fft.hadamard_div(&f_fft);
146 h_fft.ifft().into()
147 }
148
149 /// Signs a message polynomial with the secret key.
150 ///
151 /// Takes a randomness generator implementing `Rng` and message polynomial representing `c`
152 /// the hash-to-point of the message to be signed. It outputs a signature polynomial `s2`.
153 fn sign_helper<R: Rng>(&self, c: Polynomial<FalconFelt>, rng: &mut R) -> SignaturePoly {
154 let one_over_q = 1.0 / (MODULUS as f64);
155 let c_over_q_fft = c.map(|cc| Complex::new(one_over_q * cc.value() as f64, 0.0)).fft();
156
157 // B = [[FFT(g), -FFT(f)], [FFT(G), -FFT(F)]]
158 let [g_fft, minus_f_fft, big_g_fft, minus_big_f_fft] = to_complex_fft(&self.secret_key);
159 let t0 = c_over_q_fft.hadamard_mul(&minus_big_f_fft);
160 let t1 = -c_over_q_fft.hadamard_mul(&minus_f_fft);
161
162 loop {
163 let bold_s = loop {
164 let z = ffsampling(&(t0.clone(), t1.clone()), &self.tree, rng);
165 let t0_min_z0 = t0.clone() - z.0;
166 let t1_min_z1 = t1.clone() - z.1;
167
168 // s = (t-z) * B
169 let s0 = t0_min_z0.hadamard_mul(&g_fft) + t1_min_z1.hadamard_mul(&big_g_fft);
170 let s1 =
171 t0_min_z0.hadamard_mul(&minus_f_fft) + t1_min_z1.hadamard_mul(&minus_big_f_fft);
172
173 // compute the norm of (s0||s1) and note that they are in FFT representation
174 let length_squared: f64 =
175 (s0.coefficients.iter().map(|a| (a * a.conj()).re).sum::<f64>()
176 + s1.coefficients.iter().map(|a| (a * a.conj()).re).sum::<f64>())
177 / (N as f64);
178
179 if length_squared > (SIG_L2_BOUND as f64) {
180 continue;
181 }
182
183 break [-s0, s1];
184 };
185
186 let s2 = bold_s[1].ifft();
187 let s2_coef: [i16; N] = s2
188 .coefficients
189 .iter()
190 .map(|a| a.re.round() as i16)
191 .collect::<Vec<i16>>()
192 .try_into()
193 .expect("The number of coefficients should be equal to N");
194
195 if let Ok(s2) = SignaturePoly::try_from(&s2_coef) {
196 return s2;
197 }
198 }
199 }
200}
201
202// SERIALIZATION / DESERIALIZATION
203// ================================================================================================
204
205impl Serializable for SecretKey {
206 fn write_into<W: ByteWriter>(&self, target: &mut W) {
207 let basis = &self.secret_key;
208
209 // header
210 let n = basis[0].coefficients.len();
211 let l = n.checked_ilog2().unwrap() as u8;
212 let header: u8 = (5 << 4) | l;
213
214 let neg_f = &basis[1];
215 let g = &basis[0];
216 let neg_big_f = &basis[3];
217
218 let mut buffer = Vec::with_capacity(1281);
219 buffer.push(header);
220
221 let f_i8: Vec<i8> = neg_f
222 .coefficients
223 .iter()
224 .map(|&a| FalconFelt::new(-a).balanced_value() as i8)
225 .collect();
226 let f_i8_encoded = encode_i8(&f_i8, WIDTH_SMALL_POLY_COEFFICIENT).unwrap();
227 buffer.extend_from_slice(&f_i8_encoded);
228
229 let g_i8: Vec<i8> = g
230 .coefficients
231 .iter()
232 .map(|&a| FalconFelt::new(a).balanced_value() as i8)
233 .collect();
234 let g_i8_encoded = encode_i8(&g_i8, WIDTH_SMALL_POLY_COEFFICIENT).unwrap();
235 buffer.extend_from_slice(&g_i8_encoded);
236
237 let big_f_i8: Vec<i8> = neg_big_f
238 .coefficients
239 .iter()
240 .map(|&a| FalconFelt::new(-a).balanced_value() as i8)
241 .collect();
242 let big_f_i8_encoded = encode_i8(&big_f_i8, WIDTH_BIG_POLY_COEFFICIENT).unwrap();
243 buffer.extend_from_slice(&big_f_i8_encoded);
244 target.write_bytes(&buffer);
245 }
246}
247
248impl Deserializable for SecretKey {
249 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
250 let byte_vector: [u8; SK_LEN] = source.read_array()?;
251
252 // check length
253 if byte_vector.len() < 2 {
254 return Err(DeserializationError::InvalidValue("Invalid encoding length: Failed to decode as length is different from the one expected".to_string()));
255 }
256
257 // read fields
258 let header = byte_vector[0];
259
260 // check fixed bits in header
261 if (header >> 4) != 5 {
262 return Err(DeserializationError::InvalidValue("Invalid header format".to_string()));
263 }
264
265 // check log n
266 let logn = (header & 15) as usize;
267 let n = 1 << logn;
268
269 // match against const variant generic parameter
270 if n != N {
271 return Err(DeserializationError::InvalidValue(
272 "Unsupported Falcon DSA variant".to_string(),
273 ));
274 }
275
276 if byte_vector.len() != SK_LEN {
277 return Err(DeserializationError::InvalidValue("Invalid encoding length: Failed to decode as length is different from the one expected".to_string()));
278 }
279
280 let chunk_size_f = ((n * WIDTH_SMALL_POLY_COEFFICIENT) + 7) >> 3;
281 let chunk_size_g = ((n * WIDTH_SMALL_POLY_COEFFICIENT) + 7) >> 3;
282 let chunk_size_big_f = ((n * WIDTH_BIG_POLY_COEFFICIENT) + 7) >> 3;
283
284 let f = decode_i8(&byte_vector[1..chunk_size_f + 1], WIDTH_SMALL_POLY_COEFFICIENT).unwrap();
285 let g = decode_i8(
286 &byte_vector[chunk_size_f + 1..(chunk_size_f + chunk_size_g + 1)],
287 WIDTH_SMALL_POLY_COEFFICIENT,
288 )
289 .unwrap();
290 let big_f = decode_i8(
291 &byte_vector[(chunk_size_f + chunk_size_g + 1)
292 ..(chunk_size_f + chunk_size_g + chunk_size_big_f + 1)],
293 WIDTH_BIG_POLY_COEFFICIENT,
294 )
295 .unwrap();
296
297 let f = Polynomial::new(f.iter().map(|&c| FalconFelt::new(c.into())).collect());
298 let g = Polynomial::new(g.iter().map(|&c| FalconFelt::new(c.into())).collect());
299 let big_f = Polynomial::new(big_f.iter().map(|&c| FalconFelt::new(c.into())).collect());
300
301 // big_g * f - g * big_f = p (mod X^n + 1)
302 let big_g = g.fft().hadamard_div(&f.fft()).hadamard_mul(&big_f.fft()).ifft();
303 let basis = [
304 g.map(|f| f.balanced_value()),
305 -f.map(|f| f.balanced_value()),
306 big_g.map(|f| f.balanced_value()),
307 -big_f.map(|f| f.balanced_value()),
308 ];
309 Ok(Self::from_short_lattice_basis(basis))
310 }
311}
312
313// HELPER FUNCTIONS
314// ================================================================================================
315
316/// Computes the complex FFT of the secret key polynomials.
317fn to_complex_fft(basis: &[Polynomial<i16>; 4]) -> [Polynomial<Complex<f64>>; 4] {
318 let [g, f, big_g, big_f] = basis.clone();
319 let g_fft = g.map(|cc| Complex64::new(*cc as f64, 0.0)).fft();
320 let minus_f_fft = f.map(|cc| -Complex64::new(*cc as f64, 0.0)).fft();
321 let big_g_fft = big_g.map(|cc| Complex64::new(*cc as f64, 0.0)).fft();
322 let minus_big_f_fft = big_f.map(|cc| -Complex64::new(*cc as f64, 0.0)).fft();
323 [g_fft, minus_f_fft, big_g_fft, minus_big_f_fft]
324}
325
326/// Encodes a sequence of signed integers such that each integer x satisfies |x| < 2^(bits-1)
327/// for a given parameter bits. bits can take either the value 6 or 8.
328pub fn encode_i8(x: &[i8], bits: usize) -> Option<Vec<u8>> {
329 let maxv = (1 << (bits - 1)) - 1_usize;
330 let maxv = maxv as i8;
331 let minv = -maxv;
332
333 for &c in x {
334 if c > maxv || c < minv {
335 return None;
336 }
337 }
338
339 let out_len = ((N * bits) + 7) >> 3;
340 let mut buf = vec![0_u8; out_len];
341
342 let mut acc = 0_u32;
343 let mut acc_len = 0;
344 let mask = ((1_u16 << bits) - 1) as u8;
345
346 let mut input_pos = 0;
347 for &c in x {
348 acc = (acc << bits) | (c as u8 & mask) as u32;
349 acc_len += bits;
350 while acc_len >= 8 {
351 acc_len -= 8;
352 buf[input_pos] = (acc >> acc_len) as u8;
353 input_pos += 1;
354 }
355 }
356 if acc_len > 0 {
357 buf[input_pos] = (acc >> (8 - acc_len)) as u8;
358 }
359
360 Some(buf)
361}
362
363/// Decodes a sequence of bytes into a sequence of signed integers such that each integer x
364/// satisfies |x| < 2^(bits-1) for a given parameter bits. bits can take either the value 6 or 8.
365pub fn decode_i8(buf: &[u8], bits: usize) -> Option<Vec<i8>> {
366 let mut x = [0_i8; N];
367
368 let mut i = 0;
369 let mut j = 0;
370 let mut acc = 0_u32;
371 let mut acc_len = 0;
372 let mask = (1_u32 << bits) - 1;
373 let a = (1 << bits) as u8;
374 let b = ((1 << (bits - 1)) - 1) as u8;
375
376 while i < N {
377 acc = (acc << 8) | (buf[j] as u32);
378 j += 1;
379 acc_len += 8;
380
381 while acc_len >= bits && i < N {
382 acc_len -= bits;
383 let w = (acc >> acc_len) & mask;
384
385 let w = w as u8;
386
387 let z = if w > b { w as i8 - a as i8 } else { w as i8 };
388
389 x[i] = z;
390 i += 1;
391 }
392 }
393
394 if (acc & ((1u32 << acc_len) - 1)) == 0 {
395 Some(x.to_vec())
396 } else {
397 None
398 }
399}