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}