arnaucube
arnaucube/miden-cryptoPublic Self-hosted repository
miden-crypto/src/dsa/rpo_falcon512/signature.rs
src/dsa/rpo_falcon512/signature.rsRaw
  1use super::{
  2    ByteReader, ByteWriter, Deserializable, DeserializationError, NonceBytes, NonceElements,
  3    Polynomial, PublicKeyBytes, Rpo256, Serializable, SignatureBytes, StarkField, Word, MODULUS, N,
  4    SIG_L2_BOUND, ZERO,
  5};
  6use crate::utils::string::ToString;
  7use core::cell::OnceCell;
  8
  9// FALCON SIGNATURE
 10// ================================================================================================
 11
 12/// An RPO Falcon512 signature over a message.
 13///
 14/// The signature is a pair of polynomials (s1, s2) in (Z_p\[x\]/(phi))^2, where:
 15/// - p := 12289
 16/// - phi := x^512 + 1
 17/// - s1 = c - s2 * h
 18/// - h is a polynomial representing the public key and c is a polynomial that is the hash-to-point
 19///   of the message being signed.
 20///
 21/// The signature  verifies if and only if:
 22/// 1. s1 = c - s2 * h
 23/// 2. |s1|^2 + |s2|^2 <= SIG_L2_BOUND
 24///
 25/// where |.| is the norm.
 26///
 27/// [Signature] also includes the extended public key which is serialized as:
 28/// 1. 1 byte representing the log2(512) i.e., 9.
 29/// 2. 896 bytes for the public key. This is decoded into the `h` polynomial above.
 30///
 31/// The actual signature is serialized as:
 32/// 1. A header byte specifying the algorithm used to encode the coefficients of the `s2` polynomial
 33///    together with the degree of the irreducible polynomial phi.
 34///    The general format of this byte is 0b0cc1nnnn where:
 35///     a. cc is either 01 when the compressed encoding algorithm is used and 10 when the
 36///     uncompressed algorithm is used.
 37///     b. nnnn is log2(N) where N is the degree of the irreducible polynomial phi.
 38///    The current implementation works always with cc equal to 0b01 and nnnn equal to 0b1001 and
 39///    thus the header byte is always equal to 0b00111001.
 40/// 2. 40 bytes for the nonce.
 41/// 3. 625 bytes encoding the `s2` polynomial above.
 42///
 43/// The total size of the signature (including the extended public key) is 1563 bytes.
 44pub struct Signature {
 45    pub(super) pk: PublicKeyBytes,
 46    pub(super) sig: SignatureBytes,
 47
 48    // Cached polynomial decoding for public key and signatures
 49    pub(super) pk_polynomial: OnceCell<Polynomial>,
 50    pub(super) sig_polynomial: OnceCell<Polynomial>,
 51}
 52
 53impl Signature {
 54    // PUBLIC ACCESSORS
 55    // --------------------------------------------------------------------------------------------
 56
 57    /// Returns the public key polynomial h.
 58    pub fn pub_key_poly(&self) -> Polynomial {
 59        *self.pk_polynomial.get_or_init(|| {
 60            // we assume that the signature was constructed with a valid public key, and thus
 61            // expect() is OK here.
 62            Polynomial::from_pub_key(&self.pk).expect("invalid public key")
 63        })
 64    }
 65
 66    /// Returns the nonce component of the signature represented as field elements.
 67    ///
 68    /// Nonce bytes are converted to field elements by taking consecutive 5 byte chunks
 69    /// of the nonce and interpreting them as field elements.
 70    pub fn nonce(&self) -> NonceElements {
 71        // we assume that the signature was constructed with a valid signature, and thus
 72        // expect() is OK here.
 73        let nonce = self.sig[1..41].try_into().expect("invalid signature");
 74        decode_nonce(nonce)
 75    }
 76
 77    // Returns the polynomial representation of the signature in Z_p[x]/(phi).
 78    pub fn sig_poly(&self) -> Polynomial {
 79        *self.sig_polynomial.get_or_init(|| {
 80            // we assume that the signature was constructed with a valid signature, and thus
 81            // expect() is OK here.
 82            Polynomial::from_signature(&self.sig).expect("invalid signature")
 83        })
 84    }
 85
 86    // HASH-TO-POINT
 87    // --------------------------------------------------------------------------------------------
 88
 89    /// Returns a polynomial in Z_p\[x\]/(phi) representing the hash of the provided message.
 90    pub fn hash_to_point(&self, message: Word) -> Polynomial {
 91        hash_to_point(message, &self.nonce())
 92    }
 93
 94    // SIGNATURE VERIFICATION
 95    // --------------------------------------------------------------------------------------------
 96    /// Returns true if this signature is a valid signature for the specified message generated
 97    /// against key pair matching the specified public key commitment.
 98    pub fn verify(&self, message: Word, pubkey_com: Word) -> bool {
 99        // Make sure the expanded public key matches the provided public key commitment
100        let h = self.pub_key_poly();
101        let h_digest: Word = Rpo256::hash_elements(&h.to_elements()).into();
102        if h_digest != pubkey_com {
103            return false;
104        }
105
106        // Make sure the signature is valid
107        let s2 = self.sig_poly();
108        let c = self.hash_to_point(message);
109
110        let s1 = c - s2 * h;
111
112        let sq_norm = s1.sq_norm() + s2.sq_norm();
113        sq_norm <= SIG_L2_BOUND
114    }
115}
116
117// SERIALIZATION / DESERIALIZATION
118// ================================================================================================
119
120impl Serializable for Signature {
121    fn write_into<W: ByteWriter>(&self, target: &mut W) {
122        target.write_bytes(&self.pk);
123        target.write_bytes(&self.sig);
124    }
125}
126
127impl Deserializable for Signature {
128    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
129        let pk: PublicKeyBytes = source.read_array()?;
130        let sig: SignatureBytes = source.read_array()?;
131
132        // make sure public key and signature can be decoded correctly
133        let pk_polynomial = Polynomial::from_pub_key(&pk)
134            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?
135            .into();
136        let sig_polynomial = Polynomial::from_signature(&sig[41..])
137            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?
138            .into();
139
140        Ok(Self { pk, sig, pk_polynomial, sig_polynomial })
141    }
142}
143
144// HELPER FUNCTIONS
145// ================================================================================================
146
147/// Returns a polynomial in Z_p[x]/(phi) representing the hash of the provided message and
148/// nonce.
149fn hash_to_point(message: Word, nonce: &NonceElements) -> Polynomial {
150    let mut state = [ZERO; Rpo256::STATE_WIDTH];
151
152    // absorb the nonce into the state
153    for (&n, s) in nonce.iter().zip(state[Rpo256::RATE_RANGE].iter_mut()) {
154        *s = n;
155    }
156    Rpo256::apply_permutation(&mut state);
157
158    // absorb message into the state
159    for (&m, s) in message.iter().zip(state[Rpo256::RATE_RANGE].iter_mut()) {
160        *s = m;
161    }
162
163    // squeeze the coefficients of the polynomial
164    let mut i = 0;
165    let mut res = [0_u16; N];
166    for _ in 0..64 {
167        Rpo256::apply_permutation(&mut state);
168        for a in &state[Rpo256::RATE_RANGE] {
169            res[i] = (a.as_int() % MODULUS as u64) as u16;
170            i += 1;
171        }
172    }
173
174    // using the raw constructor is OK here because we reduce all coefficients by the modulus above
175    unsafe { Polynomial::new(res) }
176}
177
178/// Converts byte representation of the nonce into field element representation.
179fn decode_nonce(nonce: &NonceBytes) -> NonceElements {
180    let mut buffer = [0_u8; 8];
181    let mut result = [ZERO; 8];
182    for (i, bytes) in nonce.chunks(5).enumerate() {
183        buffer[..5].copy_from_slice(bytes);
184        result[i] = u64::from_le_bytes(buffer).into();
185    }
186
187    result
188}
189
190// TESTS
191// ================================================================================================
192
193#[cfg(all(test, feature = "std"))]
194mod tests {
195    use super::{
196        super::{ffi::*, Felt},
197        *,
198    };
199    use libc::c_void;
200    use rand_utils::rand_vector;
201
202    // Wrappers for unsafe functions
203    impl Rpo128Context {
204        /// Initializes the RPO state.
205        pub fn init() -> Self {
206            let mut ctx = Rpo128Context { content: [0u64; 13] };
207            unsafe {
208                rpo128_init(&mut ctx as *mut Rpo128Context);
209            }
210            ctx
211        }
212
213        /// Absorbs data into the RPO state.
214        pub fn absorb(&mut self, data: &[u8]) {
215            unsafe {
216                rpo128_absorb(
217                    self as *mut Rpo128Context,
218                    data.as_ptr() as *const c_void,
219                    data.len(),
220                )
221            }
222        }
223
224        /// Finalizes the RPO state to prepare for squeezing.
225        pub fn finalize(&mut self) {
226            unsafe { rpo128_finalize(self as *mut Rpo128Context) }
227        }
228    }
229
230    #[test]
231    fn test_hash_to_point() {
232        // Create a random message and transform it into a u8 vector
233        let msg_felts: Word = rand_vector::<Felt>(4).try_into().unwrap();
234        let msg_bytes = msg_felts.iter().flat_map(|e| e.as_int().to_le_bytes()).collect::<Vec<_>>();
235
236        // Create a nonce i.e. a [u8; 40] array and pack into a [Felt; 8] array.
237        let nonce: [u8; 40] = rand_vector::<u8>(40).try_into().unwrap();
238
239        let mut buffer = [0_u8; 64];
240        for i in 0..8 {
241            buffer[8 * i] = nonce[5 * i];
242            buffer[8 * i + 1] = nonce[5 * i + 1];
243            buffer[8 * i + 2] = nonce[5 * i + 2];
244            buffer[8 * i + 3] = nonce[5 * i + 3];
245            buffer[8 * i + 4] = nonce[5 * i + 4];
246        }
247
248        // Initialize the RPO state
249        let mut rng = Rpo128Context::init();
250
251        // Absorb the nonce and message into the RPO state
252        rng.absorb(&buffer);
253        rng.absorb(&msg_bytes);
254        rng.finalize();
255
256        // Generate the coefficients of the hash-to-point polynomial.
257        let mut res: [u16; N] = [0; N];
258
259        unsafe {
260            PQCLEAN_FALCON512_CLEAN_hash_to_point_rpo(
261                &mut rng as *mut Rpo128Context,
262                res.as_mut_ptr(),
263                9,
264            );
265        }
266
267        // Check that the coefficients are correct
268        let nonce = decode_nonce(&nonce);
269        assert_eq!(res, hash_to_point(msg_felts, &nonce).inner());
270    }
271}