miden_crypto/dsa/falcon512_poseidon2/mod.rs
1//! A deterministic Falcon512 Poseidon2 signature over a message.
2//!
3//! This version differs from the reference implementation in its use of the Poseidon2 algebraic
4//! hash 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::poseidon2::Poseidon2,
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.
86///
87/// [1]: <https://github.com/algorand/falcon/blob/main/falcon-det.pdf>
88const PREVERSIONED_NONCE: [u8; PREVERSIONED_NONCE_LEN] = [
89 9, 70, 65, 76, 67, 79, 78, 45, 80, 79, 83, 69, 73, 68, 79, 78, 50, 45, 68, 69, 84, 0, 0, 0, 0,
90 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
91];
92
93/// Number of filed elements used to encode a nonce.
94const NONCE_ELEMENTS: usize = 8;
95
96/// Public key length as a u8 vector.
97pub const PK_LEN: usize = 897;
98
99/// Secret key length as a u8 vector.
100pub const SK_LEN: usize = 1281;
101
102/// Signature length as a u8 vector.
103const SIG_POLY_BYTE_LEN: usize = 625;
104
105/// Signature size when serialized as a u8 vector.
106#[cfg(test)]
107const SIG_SERIALIZED_LEN: usize = 1524;
108
109/// Bound on the squared-norm of the signature.
110const SIG_L2_BOUND: u64 = 34034726;
111
112/// Standard deviation of the Gaussian over the lattice.
113const SIGMA: f64 = 165.7366171829776;
114
115// TYPE ALIASES
116// ================================================================================================
117
118type ShortLatticeBasis = [Polynomial<i16>; 4];
119
120// NONCE
121// ================================================================================================
122
123/// Nonce of the Falcon signature.
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub struct Nonce([u8; SIG_NONCE_LEN]);
126
127impl Nonce {
128 /// Returns a new deterministic [Nonce].
129 ///
130 /// This is used in deterministic signing following [1] and is composed of two parts:
131 ///
132 /// 1. a byte serving as a version byte,
133 /// 2. a pre-versioned fixed nonce which is the UTF8 encoding of the domain separator
134 /// "FALCON-POSEIDON2-DET" padded with enough zeros to make it of size 39 bytes.
135 ///
136 /// The usefulness of the notion of versioned fixed nonce is discussed in Section 2.1 in [1].
137 ///
138 /// [1]: <https://github.com/algorand/falcon/blob/main/falcon-det.pdf>
139 pub fn deterministic() -> Self {
140 let mut nonce_bytes = [0u8; SIG_NONCE_LEN];
141 nonce_bytes[0] = NONCE_VERSION_BYTE;
142 nonce_bytes[1..].copy_from_slice(&PREVERSIONED_NONCE);
143 Self(nonce_bytes)
144 }
145
146 /// Returns a new [Nonce] drawn from the provided RNG.
147 ///
148 /// This is used only in testing against the test vectors of the reference (non-deterministic)
149 /// Falcon DSA implementation.
150 #[cfg(test)]
151 pub fn random<R: rand::Rng>(rng: &mut R) -> Self {
152 let mut nonce_bytes = [0u8; SIG_NONCE_LEN];
153 rng.fill_bytes(&mut nonce_bytes);
154 Self::from_bytes(nonce_bytes)
155 }
156
157 /// Returns the underlying concatenated bytes of this nonce.
158 pub fn as_bytes(&self) -> [u8; SIG_NONCE_LEN] {
159 self.0
160 }
161
162 /// Returns a `Nonce` given an array of bytes.
163 pub fn from_bytes(nonce_bytes: [u8; SIG_NONCE_LEN]) -> Self {
164 Self(nonce_bytes)
165 }
166
167 /// Converts byte representation of the nonce into field element representation.
168 ///
169 /// Nonce bytes are converted to field elements by taking consecutive 5 byte chunks
170 /// of the nonce and interpreting them as field elements.
171 pub fn to_elements(&self) -> [Felt; NONCE_ELEMENTS] {
172 let mut buffer = [0_u8; 8];
173 let mut result = [ZERO; 8];
174 for (i, bytes) in self.as_bytes().chunks(5).enumerate() {
175 buffer[..5].copy_from_slice(bytes);
176 // we can safely (without overflow) create a new Felt from u64 value here since this
177 // value contains at most 5 bytes
178 result[i] = Felt::new(u64::from_le_bytes(buffer));
179 }
180
181 result
182 }
183}
184
185impl Serializable for &Nonce {
186 fn write_into<W: ByteWriter>(&self, target: &mut W) {
187 target.write_u8(self.0[0])
188 }
189}
190
191impl Deserializable for Nonce {
192 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
193 let nonce_version: u8 = source.read()?;
194
195 let mut nonce_bytes = [0u8; SIG_NONCE_LEN];
196 nonce_bytes[0] = nonce_version;
197 nonce_bytes[1..].copy_from_slice(&PREVERSIONED_NONCE);
198
199 Ok(Self(nonce_bytes))
200 }
201}