miden-crypto/src/dsa/rpo_falcon512/signature.rs
src/dsa/rpo_falcon512/signature.rsRaw
1use super::{
2 ByteReader, ByteWriter, Deserializable, DeserializationError, Felt, NonceBytes, NonceElements,
3 Polynomial, PublicKeyBytes, Rpo256, Serializable, SignatureBytes, 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.
44#[derive(Debug, Clone)]
45pub struct Signature {
46 pub(super) pk: PublicKeyBytes,
47 pub(super) sig: SignatureBytes,
48
49 // Cached polynomial decoding for public key and signatures
50 pub(super) pk_polynomial: OnceCell<Polynomial>,
51 pub(super) sig_polynomial: OnceCell<Polynomial>,
52}
53
54impl Signature {
55 // PUBLIC ACCESSORS
56 // --------------------------------------------------------------------------------------------
57
58 /// Returns the public key polynomial h.
59 pub fn pub_key_poly(&self) -> Polynomial {
60 *self.pk_polynomial.get_or_init(|| {
61 // we assume that the signature was constructed with a valid public key, and thus
62 // expect() is OK here.
63 Polynomial::from_pub_key(&self.pk).expect("invalid public key")
64 })
65 }
66
67 /// Returns the nonce component of the signature represented as field elements.
68 ///
69 /// Nonce bytes are converted to field elements by taking consecutive 5 byte chunks
70 /// of the nonce and interpreting them as field elements.
71 pub fn nonce(&self) -> NonceElements {
72 // we assume that the signature was constructed with a valid signature, and thus
73 // expect() is OK here.
74 let nonce = self.sig[1..41].try_into().expect("invalid signature");
75 decode_nonce(nonce)
76 }
77
78 // Returns the polynomial representation of the signature in Z_p[x]/(phi).
79 pub fn sig_poly(&self) -> Polynomial {
80 *self.sig_polynomial.get_or_init(|| {
81 // we assume that the signature was constructed with a valid signature, and thus
82 // expect() is OK here.
83 Polynomial::from_signature(&self.sig).expect("invalid signature")
84 })
85 }
86
87 // HASH-TO-POINT
88 // --------------------------------------------------------------------------------------------
89
90 /// Returns a polynomial in Z_p\[x\]/(phi) representing the hash of the provided message.
91 pub fn hash_to_point(&self, message: Word) -> Polynomial {
92 hash_to_point(message, &self.nonce())
93 }
94
95 // SIGNATURE VERIFICATION
96 // --------------------------------------------------------------------------------------------
97 /// Returns true if this signature is a valid signature for the specified message generated
98 /// against key pair matching the specified public key commitment.
99 pub fn verify(&self, message: Word, pubkey_com: Word) -> bool {
100 // Make sure the expanded public key matches the provided public key commitment
101 let h = self.pub_key_poly();
102 let h_digest: Word = Rpo256::hash_elements(&h.to_elements()).into();
103 if h_digest != pubkey_com {
104 return false;
105 }
106
107 // Make sure the signature is valid
108 let s2 = self.sig_poly();
109 let c = self.hash_to_point(message);
110
111 let s1 = c - s2 * h;
112
113 let sq_norm = s1.sq_norm() + s2.sq_norm();
114 sq_norm <= SIG_L2_BOUND
115 }
116}
117
118// SERIALIZATION / DESERIALIZATION
119// ================================================================================================
120
121impl Serializable for Signature {
122 fn write_into<W: ByteWriter>(&self, target: &mut W) {
123 target.write_bytes(&self.pk);
124 target.write_bytes(&self.sig);
125 }
126}
127
128impl Deserializable for Signature {
129 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
130 let pk: PublicKeyBytes = source.read_array()?;
131 let sig: SignatureBytes = source.read_array()?;
132
133 // make sure public key and signature can be decoded correctly
134 let pk_polynomial = Polynomial::from_pub_key(&pk)
135 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?
136 .into();
137 let sig_polynomial = Polynomial::from_signature(&sig)
138 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?
139 .into();
140
141 Ok(Self { pk, sig, pk_polynomial, sig_polynomial })
142 }
143}
144
145// HELPER FUNCTIONS
146// ================================================================================================
147
148/// Returns a polynomial in Z_p[x]/(phi) representing the hash of the provided message and
149/// nonce.
150fn hash_to_point(message: Word, nonce: &NonceElements) -> Polynomial {
151 let mut state = [ZERO; Rpo256::STATE_WIDTH];
152
153 // absorb the nonce into the state
154 for (&n, s) in nonce.iter().zip(state[Rpo256::RATE_RANGE].iter_mut()) {
155 *s = n;
156 }
157 Rpo256::apply_permutation(&mut state);
158
159 // absorb message into the state
160 for (&m, s) in message.iter().zip(state[Rpo256::RATE_RANGE].iter_mut()) {
161 *s = m;
162 }
163
164 // squeeze the coefficients of the polynomial
165 let mut i = 0;
166 let mut res = [0_u16; N];
167 for _ in 0..64 {
168 Rpo256::apply_permutation(&mut state);
169 for a in &state[Rpo256::RATE_RANGE] {
170 res[i] = (a.as_int() % MODULUS as u64) as u16;
171 i += 1;
172 }
173 }
174
175 // using the raw constructor is OK here because we reduce all coefficients by the modulus above
176 unsafe { Polynomial::new(res) }
177}
178
179/// Converts byte representation of the nonce into field element representation.
180fn decode_nonce(nonce: &NonceBytes) -> NonceElements {
181 let mut buffer = [0_u8; 8];
182 let mut result = [ZERO; 8];
183 for (i, bytes) in nonce.chunks(5).enumerate() {
184 buffer[..5].copy_from_slice(bytes);
185 // we can safely (without overflow) create a new Felt from u64 value here since this value
186 // contains at most 5 bytes
187 result[i] = Felt::new(u64::from_le_bytes(buffer));
188 }
189
190 result
191}
192
193// TESTS
194// ================================================================================================
195
196#[cfg(all(test, feature = "std"))]
197mod tests {
198 use super::{
199 super::{ffi::*, Felt, KeyPair},
200 *,
201 };
202 use libc::c_void;
203 use rand_utils::rand_vector;
204
205 // Wrappers for unsafe functions
206 impl Rpo128Context {
207 /// Initializes the RPO state.
208 pub fn init() -> Self {
209 let mut ctx = Rpo128Context { content: [0u64; 13] };
210 unsafe {
211 rpo128_init(&mut ctx as *mut Rpo128Context);
212 }
213 ctx
214 }
215
216 /// Absorbs data into the RPO state.
217 pub fn absorb(&mut self, data: &[u8]) {
218 unsafe {
219 rpo128_absorb(
220 self as *mut Rpo128Context,
221 data.as_ptr() as *const c_void,
222 data.len(),
223 )
224 }
225 }
226
227 /// Finalizes the RPO state to prepare for squeezing.
228 pub fn finalize(&mut self) {
229 unsafe { rpo128_finalize(self as *mut Rpo128Context) }
230 }
231 }
232
233 #[test]
234 fn test_hash_to_point() {
235 // Create a random message and transform it into a u8 vector
236 let msg_felts: Word = rand_vector::<Felt>(4).try_into().unwrap();
237 let msg_bytes = msg_felts.iter().flat_map(|e| e.as_int().to_le_bytes()).collect::<Vec<_>>();
238
239 // Create a nonce i.e. a [u8; 40] array and pack into a [Felt; 8] array.
240 let nonce: [u8; 40] = rand_vector::<u8>(40).try_into().unwrap();
241
242 let mut buffer = [0_u8; 64];
243 for i in 0..8 {
244 buffer[8 * i] = nonce[5 * i];
245 buffer[8 * i + 1] = nonce[5 * i + 1];
246 buffer[8 * i + 2] = nonce[5 * i + 2];
247 buffer[8 * i + 3] = nonce[5 * i + 3];
248 buffer[8 * i + 4] = nonce[5 * i + 4];
249 }
250
251 // Initialize the RPO state
252 let mut rng = Rpo128Context::init();
253
254 // Absorb the nonce and message into the RPO state
255 rng.absorb(&buffer);
256 rng.absorb(&msg_bytes);
257 rng.finalize();
258
259 // Generate the coefficients of the hash-to-point polynomial.
260 let mut res: [u16; N] = [0; N];
261
262 unsafe {
263 PQCLEAN_FALCON512_CLEAN_hash_to_point_rpo(
264 &mut rng as *mut Rpo128Context,
265 res.as_mut_ptr(),
266 9,
267 );
268 }
269
270 // Check that the coefficients are correct
271 let nonce = decode_nonce(&nonce);
272 assert_eq!(res, hash_to_point(msg_felts, &nonce).inner());
273 }
274
275 #[test]
276 fn test_serialization_round_trip() {
277 let key = KeyPair::new().unwrap();
278 let signature = key.sign(Word::default()).unwrap();
279 let serialized = signature.to_bytes();
280 let deserialized = Signature::read_from_bytes(&serialized).unwrap();
281 assert_eq!(signature.sig_poly(), deserialized.sig_poly());
282 assert_eq!(signature.pub_key_poly(), deserialized.pub_key_poly());
283 }
284}