miden_crypto/dsa/falcon512_rpo/
signature.rs

1use alloc::{string::ToString, vec::Vec};
2use core::ops::Deref;
3
4use num::Zero;
5
6use super::{
7    ByteReader, ByteWriter, Deserializable, DeserializationError, LOG_N, MODULUS, N, Nonce,
8    SIG_L2_BOUND, SIG_POLY_BYTE_LEN, Serializable,
9    hash_to_point::hash_to_point_rpo256,
10    keys::PublicKey,
11    math::{FalconFelt, FastFft, Polynomial},
12};
13use crate::Word;
14
15// FALCON SIGNATURE
16// ================================================================================================
17
18/// A deterministic RPO Falcon512 signature over a message.
19///
20/// The signature is a pair of polynomials (s1, s2) in (Z_p\[x\]/(phi))^2 a nonce `r`, and a public
21/// key polynomial `h` where:
22/// - p := 12289
23/// - phi := x^512 + 1
24///
25/// The signature  verifies against a public key `pk` if and only if:
26/// 1. s1 = c - s2 * h
27/// 2. |s1|^2 + |s2|^2 <= SIG_L2_BOUND
28///
29/// where |.| is the norm and:
30/// - c = HashToPoint(r || message)
31/// - pk = Rpo256::hash(h)
32///
33/// Here h is a polynomial representing the public key and pk is its digest using the Rpo256 hash
34/// function. c is a polynomial that is the hash-to-point of the message being signed.
35///  
36///  To summarize the main points of differences with the reference implementation, we have that:
37///
38/// 1. the hash-to-point algorithm is made deterministic by using a fixed nonce `r`. This fixed
39///    nonce is formed as `nonce_version_byte || preversioned_nonce` where `preversioned_nonce` is a
40///    39-byte string that is defined as: i. a byte representing `log_2(512)`, followed by ii. the
41///    UTF8 representation of the string "RPO-FALCON-DET", followed by iii. the required number of
42///    0_u8 padding to make the total length equal 39 bytes. Note that the above means in particular
43///    that only the `nonce_version_byte` needs to be serialized when serializing the signature.
44///    This reduces the deterministic signature compared to the reference implementation by 39
45///    bytes.
46/// 2. the RNG used in the trapdoor sampler (i.e., the ffSampling algorithm) is ChaCha20Rng seeded
47///    with the `Blake3` hash of `log_2(512) || sk || message`.
48///
49/// The signature is serialized as:
50///
51/// 1. A header byte specifying the algorithm used to encode the coefficients of the `s2` polynomial
52///    together with the degree of the irreducible polynomial phi. For RPO Falcon512, the header
53///    byte is set to `10111001` to differentiate it from the standardized instantiation of the
54///    Falcon signature.
55/// 2. 1 byte for the nonce version.
56/// 4. 625 bytes encoding the `s2` polynomial above.
57///
58/// In addition to the signature itself, the polynomial h is also serialized with the signature as:
59///
60/// 1. 1 byte representing the log2(512) i.e., 9.
61/// 2. 896 bytes for the public key itself.
62///
63/// The total size of the signature (including the extended public key) is 1524 bytes.
64///
65/// [1]: https://github.com/algorand/falcon/blob/main/falcon-det.pdf
66/// [2]: https://datatracker.ietf.org/doc/html/rfc6979#section-3.5
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct Signature {
69    header: SignatureHeader,
70    nonce: Nonce,
71    s2: SignaturePoly,
72    h: PublicKey,
73}
74
75impl Signature {
76    // CONSTRUCTOR
77    // --------------------------------------------------------------------------------------------
78
79    /// Creates a new signature from the given nonce, public key polynomial, and signature
80    /// polynomial.
81    pub fn new(nonce: Nonce, h: PublicKey, s2: SignaturePoly) -> Signature {
82        Self {
83            header: SignatureHeader::default(),
84            nonce,
85            s2,
86            h,
87        }
88    }
89
90    // PUBLIC ACCESSORS
91    // --------------------------------------------------------------------------------------------
92
93    /// Returns the public key polynomial h.
94    pub fn public_key(&self) -> &PublicKey {
95        &self.h
96    }
97
98    /// Returns the polynomial representation of the signature in Z_p\[x\]/(phi).
99    pub fn sig_poly(&self) -> &Polynomial<FalconFelt> {
100        &self.s2
101    }
102
103    /// Returns the nonce component of the signature.
104    pub fn nonce(&self) -> &Nonce {
105        &self.nonce
106    }
107
108    // SIGNATURE VERIFICATION
109    // --------------------------------------------------------------------------------------------
110
111    /// Returns true if this signature is a valid signature for the specified message generated
112    /// against the secret key matching the specified public key commitment.
113    pub fn verify(&self, message: Word, pub_key: &PublicKey) -> bool {
114        if self.h != *pub_key {
115            return false;
116        }
117        let c = hash_to_point_rpo256(message, &self.nonce);
118        verify_helper(&c, &self.s2, pub_key)
119    }
120}
121
122impl Serializable for Signature {
123    fn write_into<W: ByteWriter>(&self, target: &mut W) {
124        target.write(&self.header);
125        target.write(&self.nonce);
126        target.write(&self.s2);
127        target.write(&self.h);
128    }
129}
130
131impl Deserializable for Signature {
132    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
133        let header = source.read()?;
134        let nonce = source.read()?;
135        let s2 = source.read()?;
136        let h = source.read()?;
137
138        Ok(Self { header, nonce, s2, h })
139    }
140}
141
142// SIGNATURE HEADER
143// ================================================================================================
144
145/// The header byte used to encode the signature metadata.
146#[derive(Debug, Clone, PartialEq, Eq)]
147pub struct SignatureHeader(u8);
148
149impl Default for SignatureHeader {
150    /// According to section 3.11.3 in the specification [1],  the signature header has the format
151    /// `0cc1nnnn` where:
152    ///
153    /// 1. `cc` signifies the encoding method. `01` denotes using the compression encoding method
154    ///    and `10` denotes encoding using the uncompressed method.
155    /// 2. `nnnn` encodes `LOG_N`.
156    ///
157    /// For RPO Falcon 512 we use compression encoding and N = 512. Moreover, to differentiate the
158    /// RPO Falcon variant from the reference variant using SHAKE256, we flip the first bit in the
159    /// header. Thus, for RPO Falcon 512 the header is `10111001`
160    ///
161    /// [1]: https://falcon-sign.info/falcon.pdf
162    fn default() -> Self {
163        Self(0b1011_1001)
164    }
165}
166
167impl Serializable for &SignatureHeader {
168    fn write_into<W: ByteWriter>(&self, target: &mut W) {
169        target.write_u8(self.0)
170    }
171}
172
173impl Deserializable for SignatureHeader {
174    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
175        let header = source.read_u8()?;
176        let (encoding, log_n) = (header >> 4, header & 0b00001111);
177        if encoding != 0b1011 {
178            return Err(DeserializationError::InvalidValue(
179                "Failed to decode signature: not supported encoding algorithm".to_string(),
180            ));
181        }
182
183        if log_n != LOG_N {
184            return Err(DeserializationError::InvalidValue(format!(
185                "Failed to decode signature: only supported irreducible polynomial degree is 512, 2^{log_n} was provided"
186            )));
187        }
188
189        Ok(Self(header))
190    }
191}
192
193// SIGNATURE POLYNOMIAL
194// ================================================================================================
195
196/// A polynomial used as the `s2` component of the signature.
197#[derive(Debug, Clone, PartialEq, Eq)]
198pub struct SignaturePoly(pub Polynomial<FalconFelt>);
199
200impl Deref for SignaturePoly {
201    type Target = Polynomial<FalconFelt>;
202
203    fn deref(&self) -> &Self::Target {
204        &self.0
205    }
206}
207
208impl From<Polynomial<FalconFelt>> for SignaturePoly {
209    fn from(pk_poly: Polynomial<FalconFelt>) -> Self {
210        Self(pk_poly)
211    }
212}
213
214impl TryFrom<&[i16; N]> for SignaturePoly {
215    type Error = ();
216
217    fn try_from(coefficients: &[i16; N]) -> Result<Self, Self::Error> {
218        if are_coefficients_valid(coefficients) {
219            Ok(Self(coefficients.to_vec().into()))
220        } else {
221            Err(())
222        }
223    }
224}
225
226impl Serializable for &SignaturePoly {
227    fn write_into<W: ByteWriter>(&self, target: &mut W) {
228        let sig_coeff: Vec<i16> = self.0.coefficients.iter().map(|a| a.balanced_value()).collect();
229        let mut sk_bytes = vec![0_u8; SIG_POLY_BYTE_LEN];
230
231        let mut acc = 0;
232        let mut acc_len = 0;
233        let mut v = 0;
234        let mut t;
235        let mut w;
236
237        // For each coefficient of x:
238        // - the sign is encoded on 1 bit
239        // - the 7 lower bits are encoded naively (binary)
240        // - the high bits are encoded in unary encoding
241        //
242        // Algorithm 17 p. 47 of the specification [1].
243        //
244        // [1]: https://falcon-sign.info/falcon.pdf
245        for &c in sig_coeff.iter() {
246            acc <<= 1;
247            t = c;
248
249            if t < 0 {
250                t = -t;
251                acc |= 1;
252            }
253            w = t as u16;
254
255            acc <<= 7;
256            let mask = 127_u32;
257            acc |= (w as u32) & mask;
258            w >>= 7;
259
260            acc_len += 8;
261
262            acc <<= w + 1;
263            acc |= 1;
264            acc_len += w + 1;
265
266            while acc_len >= 8 {
267                acc_len -= 8;
268
269                sk_bytes[v] = (acc >> acc_len) as u8;
270                v += 1;
271            }
272        }
273
274        if acc_len > 0 {
275            sk_bytes[v] = (acc << (8 - acc_len)) as u8;
276        }
277        target.write_bytes(&sk_bytes);
278    }
279}
280
281impl Deserializable for SignaturePoly {
282    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
283        let input = source.read_array::<SIG_POLY_BYTE_LEN>()?;
284
285        let mut input_idx = 0;
286        let mut acc = 0u32;
287        let mut acc_len = 0;
288        let mut coefficients = [FalconFelt::zero(); N];
289
290        // Algorithm 18 p. 48 of the specification [1].
291        //
292        // [1]: https://falcon-sign.info/falcon.pdf
293        for c in coefficients.iter_mut() {
294            acc = (acc << 8) | (input[input_idx] as u32);
295            input_idx += 1;
296            let b = acc >> acc_len;
297            let s = b & 128;
298            let mut m = b & 127;
299
300            loop {
301                if acc_len == 0 {
302                    acc = (acc << 8) | (input[input_idx] as u32);
303                    input_idx += 1;
304                    acc_len = 8;
305                }
306                acc_len -= 1;
307                if ((acc >> acc_len) & 1) != 0 {
308                    break;
309                }
310                m += 128;
311                if m >= 2048 {
312                    return Err(DeserializationError::InvalidValue(format!(
313                        "Failed to decode signature: high bits {m} exceed 2048",
314                    )));
315                }
316            }
317            if s != 0 && m == 0 {
318                return Err(DeserializationError::InvalidValue(
319                    "Failed to decode signature: -0 is forbidden".to_string(),
320                ));
321            }
322
323            let felt = if s != 0 { (MODULUS as u32 - m) as u16 } else { m as u16 };
324            *c = FalconFelt::new(felt as i16);
325        }
326
327        if (acc & ((1 << acc_len) - 1)) != 0 {
328            return Err(DeserializationError::InvalidValue(
329                "Failed to decode signature: Non-zero unused bits in the last byte".to_string(),
330            ));
331        }
332        Ok(Polynomial::new(coefficients.to_vec()).into())
333    }
334}
335
336// HELPER FUNCTIONS
337// ================================================================================================
338
339/// Takes the hash-to-point polynomial `c` of a message, the signature polynomial over
340/// the message `s2` and a public key polynomial and returns `true` is the signature is a valid
341/// signature for the given parameters, otherwise it returns `false`.
342fn verify_helper(c: &Polynomial<FalconFelt>, s2: &SignaturePoly, h: &PublicKey) -> bool {
343    let h_fft = h.fft();
344    let s2_fft = s2.fft();
345    let c_fft = c.fft();
346
347    // compute the signature polynomial s1 using s1 = c - s2 * h
348    let s1_fft = c_fft - s2_fft.hadamard_mul(&h_fft);
349    let s1 = s1_fft.ifft();
350
351    // compute the norm squared of (s1, s2)
352    let length_squared_s1 = s1.norm_squared();
353    let length_squared_s2 = s2.norm_squared();
354    let length_squared = length_squared_s1 + length_squared_s2;
355
356    length_squared < SIG_L2_BOUND
357}
358
359/// Checks whether a set of coefficients is a valid one for a signature polynomial.
360fn are_coefficients_valid(x: &[i16]) -> bool {
361    if x.len() != N {
362        return false;
363    }
364
365    for &c in x {
366        if !(-2047..=2047).contains(&c) {
367            return false;
368        }
369    }
370
371    true
372}
373
374// TESTS
375// ================================================================================================
376
377#[cfg(test)]
378mod tests {
379    use rand::SeedableRng;
380    use rand_chacha::ChaCha20Rng;
381
382    use super::{
383        super::{SIG_SERIALIZED_LEN, SecretKey},
384        *,
385    };
386
387    #[test]
388    fn test_serialization_round_trip() {
389        let seed = [0_u8; 32];
390        let mut rng = ChaCha20Rng::from_seed(seed);
391
392        let sk = SecretKey::with_rng(&mut rng);
393        let signature = sk.sign_with_rng(Word::default(), &mut rng);
394        let serialized = signature.to_bytes();
395        assert_eq!(serialized.len(), SIG_SERIALIZED_LEN);
396        let deserialized = Signature::read_from_bytes(&serialized).unwrap();
397        assert_eq!(signature.sig_poly(), deserialized.sig_poly());
398    }
399}