miden_crypto/dsa/falcon512_rpo/mod.rs
1//! A deterministic RPO Falcon512 signature over a message.
2//!
3//! This version differs from the reference implementation in its use of the RPO algebraic hash
4//! function in its hash-to-point algorithm.
5//!
6//! Another point of difference is the determinism in the signing process. The approach used to
7//! achieve this is the one proposed in [1].
8//! The main challenge in making the signing procedure deterministic is ensuring that the same
9//! secret key is never used to produce two inequivalent signatures for the same `c`.
10//! For a precise definition of equivalence of signatures see [1].
11//! The reference implementation uses a random nonce per signature in order to make sure that,
12//! with overwhelming probability, no two c-s will ever repeat and this non-repetition turns out
13//! to be enough to make the security proof of the underlying construction go through in
14//! the random-oracle model.
15//!
16//! Making the signing process deterministic means that we cannot rely on the above use of nonce
17//! in the hash-to-point algorithm, i.e., the hash-to-point algorithm is deterministic. It also
18//! means that we have to derandomize the trapdoor sampling process and use the entropy in
19//! the secret key, together with the message, as the seed of a CPRNG. This is exactly the approach
20//! taken in [2] but, as explained at length in [1], this is not enough. The reason for this
21//! is that the sampling process during signature generation must be ensured to be consistent
22//! across the entire computing stack i.e., hardware, compiler, OS, sampler implementations ...
23//!
24//! This is made even more difficult by the extensive use of floating-point arithmetic by
25//! the sampler. In relation to this point, the current implementation does not use any platform
26//! specific optimizations (e.g., AVX2, NEON, FMA ...) and relies solely on the builtin `f64` type.
27//! Moreover, as per the time of this writing, the implementation does not use any methods or
28//! functions from `std::f64` that have non-deterministic precision mentioned in their
29//! documentation.
30//!
31//! [1]: https://github.com/algorand/falcon/blob/main/falcon-det.pdf
32//! [2]: https://datatracker.ietf.org/doc/html/rfc6979#section-3.5
33
34use crate::{
35 Felt, ZERO,
36 hash::rpo::Rpo256,
37 utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
38};
39
40mod hash_to_point;
41mod keys;
42mod math;
43mod signature;
44
45#[cfg(test)]
46mod tests;
47
48pub use self::{
49 keys::{PublicKey, SecretKey},
50 math::Polynomial,
51 signature::{Signature, SignatureHeader, SignaturePoly},
52};
53
54// CONSTANTS
55// ================================================================================================
56
57// The Falcon modulus p.
58const MODULUS: i16 = 12289;
59
60// Number of bits needed to encode an element in the Falcon field.
61const FALCON_ENCODING_BITS: u32 = 14;
62
63// The Falcon parameters for Falcon-512. This is the degree of the polynomial `phi := x^N + 1`
64// defining the ring Z_p[x]/(phi).
65const N: usize = 512;
66const LOG_N: u8 = 9;
67
68/// Length of nonce used for signature generation.
69const SIG_NONCE_LEN: usize = 40;
70
71/// Length of the preversioned portion of the fixed nonce.
72///
73/// Since we use one byte to encode the version of the nonce, this is equal to `SIG_NONCE_LEN - 1`.
74const PREVERSIONED_NONCE_LEN: usize = 39;
75
76/// Current version of the fixed nonce.
77///
78/// The usefulness of the notion of versioned fixed nonce is discussed in Section 2.1 in [1].
79///
80/// [1]: https://github.com/algorand/falcon/blob/main/falcon-det.pdf
81const NONCE_VERSION_BYTE: u8 = 1;
82
83/// The preversioned portion of the fixed nonce constructed following [1].
84///
85/// Note that reference [1] uses the term salt instead of nonce.
86const PREVERSIONED_NONCE: [u8; PREVERSIONED_NONCE_LEN] = [
87 9, 82, 80, 79, 45, 70, 65, 76, 67, 79, 78, 45, 68, 69, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
88 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
89];
90
91/// Number of filed elements used to encode a nonce.
92const NONCE_ELEMENTS: usize = 8;
93
94/// Public key length as a u8 vector.
95pub const PK_LEN: usize = 897;
96
97/// Secret key length as a u8 vector.
98pub const SK_LEN: usize = 1281;
99
100/// Signature length as a u8 vector.
101const SIG_POLY_BYTE_LEN: usize = 625;
102
103/// Signature size when serialized as a u8 vector.
104#[cfg(test)]
105const SIG_SERIALIZED_LEN: usize = 1524;
106
107/// Bound on the squared-norm of the signature.
108const SIG_L2_BOUND: u64 = 34034726;
109
110/// Standard deviation of the Gaussian over the lattice.
111const SIGMA: f64 = 165.7366171829776;
112
113// TYPE ALIASES
114// ================================================================================================
115
116type ShortLatticeBasis = [Polynomial<i16>; 4];
117
118// NONCE
119// ================================================================================================
120
121/// Nonce of the Falcon signature.
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct Nonce([u8; SIG_NONCE_LEN]);
124
125impl Nonce {
126 /// Returns a new deterministic [Nonce].
127 ///
128 /// This is used in deterministic signing following [1] and is composed of two parts:
129 ///
130 /// 1. a byte serving as a version byte,
131 /// 2. a pre-versioned fixed nonce which is the UTF8 encoding of the domain separator
132 /// "RPO-FALCON-DET" padded with enough zeros to make it of size 39 bytes.
133 ///
134 /// The usefulness of the notion of versioned fixed nonce is discussed in Section 2.1 in [1].
135 ///
136 /// [1]: https://github.com/algorand/falcon/blob/main/falcon-det.pdf
137 pub fn deterministic() -> Self {
138 let mut nonce_bytes = [0u8; SIG_NONCE_LEN];
139 nonce_bytes[0] = NONCE_VERSION_BYTE;
140 nonce_bytes[1..].copy_from_slice(&PREVERSIONED_NONCE);
141 Self(nonce_bytes)
142 }
143
144 /// Returns a new [Nonce] drawn from the provided RNG.
145 ///
146 /// This is used only in testing against the test vectors of the reference (non-deterministic)
147 /// Falcon DSA implementation.
148 #[cfg(test)]
149 pub fn random<R: rand::Rng>(rng: &mut R) -> Self {
150 let mut nonce_bytes = [0u8; SIG_NONCE_LEN];
151 rng.fill_bytes(&mut nonce_bytes);
152 Self::from_bytes(nonce_bytes)
153 }
154
155 /// Returns the underlying concatenated bytes of this nonce.
156 pub fn as_bytes(&self) -> [u8; SIG_NONCE_LEN] {
157 self.0
158 }
159
160 /// Returns a `Nonce` given an array of bytes.
161 pub fn from_bytes(nonce_bytes: [u8; SIG_NONCE_LEN]) -> Self {
162 Self(nonce_bytes)
163 }
164
165 /// Converts byte representation of the nonce into field element representation.
166 ///
167 /// Nonce bytes are converted to field elements by taking consecutive 5 byte chunks
168 /// of the nonce and interpreting them as field elements.
169 pub fn to_elements(&self) -> [Felt; NONCE_ELEMENTS] {
170 let mut buffer = [0_u8; 8];
171 let mut result = [ZERO; 8];
172 for (i, bytes) in self.as_bytes().chunks(5).enumerate() {
173 buffer[..5].copy_from_slice(bytes);
174 // we can safely (without overflow) create a new Felt from u64 value here since this
175 // value contains at most 5 bytes
176 result[i] = Felt::new(u64::from_le_bytes(buffer));
177 }
178
179 result
180 }
181}
182
183impl Serializable for &Nonce {
184 fn write_into<W: ByteWriter>(&self, target: &mut W) {
185 target.write_u8(self.0[0])
186 }
187}
188
189impl Deserializable for Nonce {
190 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
191 let nonce_version: u8 = source.read()?;
192
193 let mut nonce_bytes = [0u8; SIG_NONCE_LEN];
194 nonce_bytes[0] = nonce_version;
195 nonce_bytes[1..].copy_from_slice(&PREVERSIONED_NONCE);
196
197 Ok(Self(nonce_bytes))
198 }
199}