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)
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, KeyPair},
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
272 #[test]
273 fn test_serialization_round_trip() {
274 let key = KeyPair::new().unwrap();
275 let signature = key.sign(Word::default()).unwrap();
276 let serialized = signature.to_bytes();
277 let deserialized = Signature::read_from_bytes(&serialized).unwrap();
278 assert_eq!(signature.sig_poly(), deserialized.sig_poly());
279 assert_eq!(signature.pub_key_poly(), deserialized.pub_key_poly());
280 }
281}