miden_crypto/dsa/rpo_falcon512/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
34#[cfg(test)]
35use rand::Rng;
36
37use crate::{
38 Felt, ZERO,
39 hash::rpo::Rpo256,
40 utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
41};
42
43mod hash_to_point;
44mod keys;
45mod math;
46mod signature;
47
48#[cfg(all(test, feature = "std"))]
49mod tests;
50
51pub use self::{
52 keys::{PublicKey, SecretKey},
53 math::Polynomial,
54 signature::{Signature, SignatureHeader, SignaturePoly},
55};
56
57// CONSTANTS
58// ================================================================================================
59
60// The Falcon modulus p.
61const MODULUS: i16 = 12289;
62
63// Number of bits needed to encode an element in the Falcon field.
64const FALCON_ENCODING_BITS: u32 = 14;
65
66// The Falcon parameters for Falcon-512. This is the degree of the polynomial `phi := x^N + 1`
67// defining the ring Z_p[x]/(phi).
68const N: usize = 512;
69const LOG_N: u8 = 9;
70
71/// Length of nonce used for signature generation.
72const SIG_NONCE_LEN: usize = 40;
73
74/// Length of the preversioned portion of the fixed nonce.
75///
76/// Since we use one byte to encode the version of the nonce, this is equal to `SIG_NONCE_LEN - 1`.
77const PREVERSIONED_NONCE_LEN: usize = 39;
78
79/// Current version of the fixed nonce.
80///
81/// The usefulness of the notion of versioned fixed nonce is discussed in Section 2.1 in [1].
82///
83/// [1]: https://github.com/algorand/falcon/blob/main/falcon-det.pdf
84const NONCE_VERSION_BYTE: u8 = 1;
85
86/// The preversioned portion of the fixed nonce constructed following [1].
87///
88/// Note that reference [1] uses the term salt instead of nonce.
89const PREVERSIONED_NONCE: [u8; PREVERSIONED_NONCE_LEN] = [
90 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,
91 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
92];
93
94/// Number of filed elements used to encode a nonce.
95const NONCE_ELEMENTS: usize = 8;
96
97/// Public key length as a u8 vector.
98pub const PK_LEN: usize = 897;
99
100/// Secret key length as a u8 vector.
101pub const SK_LEN: usize = 1281;
102
103/// Signature length as a u8 vector.
104const SIG_POLY_BYTE_LEN: usize = 625;
105
106/// Signature size when serialized as a u8 vector.
107#[cfg(test)]
108const SIG_SERIALIZED_LEN: usize = 1524;
109
110/// Bound on the squared-norm of the signature.
111const SIG_L2_BOUND: u64 = 34034726;
112
113/// Standard deviation of the Gaussian over the lattice.
114const SIGMA: f64 = 165.7366171829776;
115
116// TYPE ALIASES
117// ================================================================================================
118
119type ShortLatticeBasis = [Polynomial<i16>; 4];
120
121// NONCE
122// ================================================================================================
123
124/// Nonce of the Falcon signature.
125#[derive(Debug, Clone, PartialEq, Eq)]
126pub struct Nonce([u8; SIG_NONCE_LEN]);
127
128impl Nonce {
129 /// Returns a new deterministic [Nonce].
130 ///
131 /// This is used in deterministic signing following [1] and is composed of two parts:
132 ///
133 /// 1. a byte serving as a version byte,
134 /// 2. a pre-versioned fixed nonce which is the UTF8 encoding of the domain separator
135 /// "RPO-FALCON-DET" padded with enough zeros to make it of size 39 bytes.
136 ///
137 /// The usefulness of the notion of versioned fixed nonce is discussed in Section 2.1 in [1].
138 ///
139 /// [1]: https://github.com/algorand/falcon/blob/main/falcon-det.pdf
140 pub fn deterministic() -> Self {
141 let mut nonce_bytes = [0u8; SIG_NONCE_LEN];
142 nonce_bytes[0] = NONCE_VERSION_BYTE;
143 nonce_bytes[1..].copy_from_slice(&PREVERSIONED_NONCE);
144 Self(nonce_bytes)
145 }
146
147 /// Returns a new [Nonce] drawn from the provided RNG.
148 ///
149 /// This is used only in testing against the test vectors of the reference (non-deterministic)
150 /// Falcon DSA implementation.
151 #[cfg(test)]
152 pub fn random<R: Rng>(rng: &mut R) -> Self {
153 let mut nonce_bytes = [0u8; SIG_NONCE_LEN];
154 rng.fill_bytes(&mut nonce_bytes);
155 Self::from_bytes(nonce_bytes)
156 }
157
158 /// Returns the underlying concatenated bytes of this nonce.
159 pub fn as_bytes(&self) -> [u8; SIG_NONCE_LEN] {
160 self.0
161 }
162
163 /// Returns a `Nonce` given an array of bytes.
164 pub fn from_bytes(nonce_bytes: [u8; SIG_NONCE_LEN]) -> Self {
165 Self(nonce_bytes)
166 }
167
168 /// Converts byte representation of the nonce into field element representation.
169 ///
170 /// Nonce bytes are converted to field elements by taking consecutive 5 byte chunks
171 /// of the nonce and interpreting them as field elements.
172 pub fn to_elements(&self) -> [Felt; NONCE_ELEMENTS] {
173 let mut buffer = [0_u8; 8];
174 let mut result = [ZERO; 8];
175 for (i, bytes) in self.as_bytes().chunks(5).enumerate() {
176 buffer[..5].copy_from_slice(bytes);
177 // we can safely (without overflow) create a new Felt from u64 value here since this
178 // value contains at most 5 bytes
179 result[i] = Felt::new(u64::from_le_bytes(buffer));
180 }
181
182 result
183 }
184}
185
186impl Serializable for &Nonce {
187 fn write_into<W: ByteWriter>(&self, target: &mut W) {
188 target.write_u8(self.0[0])
189 }
190}
191
192impl Deserializable for Nonce {
193 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
194 let nonce_version: u8 = source.read()?;
195
196 let mut nonce_bytes = [0u8; SIG_NONCE_LEN];
197 nonce_bytes[0] = nonce_version;
198 nonce_bytes[1..].copy_from_slice(&PREVERSIONED_NONCE);
199
200 Ok(Self(nonce_bytes))
201 }
202}