tectonic_fn_dsa_comm/lib.rs
1#![no_std]
2
3//! This crate contains utility functions which are used by FN-DSA for
4//! key pair generation, signing, and verifying. It is not meant to
5//! be used directly.
6
7/// Encoding/decoding primitives.
8pub mod codec;
9
10/// Computations with polynomials modulo X^n+1 and modulo q = 12289.
11pub mod mq;
12
13/// SHAKE implementation.
14pub mod shake;
15
16/// Specialized versions of `mq` which use AVX2 opcodes (on x86 CPUs).
17#[cfg(all(
18 not(feature = "no_avx2"),
19 any(target_arch = "x86_64", target_arch = "x86")
20))]
21pub mod mq_avx2;
22
23// Re-export RNG traits to get a smooth dependency management.
24pub use rand_core::{CryptoRng, Error as RngError, RngCore};
25
26/// Symbolic constant for FN-DSA with degree 512 (`logn = 9`).
27pub const FN_DSA_LOGN_512: u32 = 9;
28
29/// Symbolic constant for FN-DSA with degree 1024 (`logn = 10`).
30pub const FN_DSA_LOGN_1024: u32 = 10;
31
32/// Get the size (in bytes) of a signing key for the provided degree
33/// (degree is `n = 2^logn`, with `2 <= logn <= 10`).
34pub const fn sign_key_size(logn: u32) -> usize {
35 let n = 1usize << logn;
36 let nbits_fg = match logn {
37 2..=5 => 8,
38 6..=7 => 7,
39 8..=9 => 6,
40 _ => 5,
41 };
42 1 + (nbits_fg << (logn - 2)) + n
43}
44
45/// Get the size (in bytes) of a verifying key for the provided degree
46/// (degree is `n = 2^logn`, with `2 <= logn <= 10`).
47pub const fn vrfy_key_size(logn: u32) -> usize {
48 1 + (7 << (logn - 2))
49}
50
51/// Get the size (in bytes) of a signature for the provided degree
52/// (degree is `n = 2^logn`, with `2 <= logn <= 10`).
53pub const fn signature_size(logn: u32) -> usize {
54 // logn n size
55 // 2 4 47
56 // 3 8 52
57 // 4 16 63
58 // 5 32 82
59 // 6 64 122
60 // 7 128 200
61 // 8 256 356
62 // 9 512 666
63 // 10 1024 1280
64 44 + 3 * (256 >> (10 - logn))
65 + 2 * (128 >> (10 - logn))
66 + 3 * (64 >> (10 - logn))
67 + 2 * (16 >> (10 - logn))
68 - 2 * (2 >> (10 - logn))
69 - 8 * (1 >> (10 - logn))
70}
71
72/// The message for which a signature is to be generated or verified is
73/// pre-hashed by the caller and provided as a hash value along with
74/// an identifier of the used hash function. The identifier is normally
75/// an encoded ASN.1 OID. A special identifier is used for "raw" messages
76/// (i.e. not pre-hashed at all); it uses a single byte of value 0x00.
77pub struct HashIdentifier<'a>(pub &'a [u8]);
78
79/// Hash function identifier: none.
80///
81/// This is the identifier used internally to specify that signature
82/// generation and verification are performed over a raw message, without
83/// pre-hashing.
84pub const HASH_ID_RAW: HashIdentifier = HashIdentifier(&[0x00]);
85
86/// Hash function identifier: original Falcon design.
87///
88/// This identifier modifies processing of the input so that it follows
89/// the Falcon scheme as it was submitted for round 3 of the post-quantum
90/// cryptography standardization process. When this identifier is used:
91///
92/// - The message is raw (not pre-hashed).
93/// - The domain separation context is not used.
94/// - The public key hash is not included in the signed data.
95///
96/// Supporting the original Falcon design is an obsolescent feature
97/// that will be removed at the latest when the final FN-DSA standard
98/// is published.
99pub const HASH_ID_ORIGINAL_FALCON: HashIdentifier = HashIdentifier(&[0xFF]);
100
101/// Hash function identifier: SHA-256
102pub const HASH_ID_SHA256: HashIdentifier = HashIdentifier(&[
103 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
104]);
105
106/// Hash function identifier: SHA-384
107pub const HASH_ID_SHA384: HashIdentifier = HashIdentifier(&[
108 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02,
109]);
110
111/// Hash function identifier: SHA-512
112pub const HASH_ID_SHA512: HashIdentifier = HashIdentifier(&[
113 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03,
114]);
115
116/// Hash function identifier: SHA-512-256
117pub const HASH_ID_SHA512_256: HashIdentifier = HashIdentifier(&[
118 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x06,
119]);
120
121/// Hash function identifier: SHA3-256
122pub const HASH_ID_SHA3_256: HashIdentifier = HashIdentifier(&[
123 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x08,
124]);
125
126/// Hash function identifier: SHA3-384
127pub const HASH_ID_SHA3_384: HashIdentifier = HashIdentifier(&[
128 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x09,
129]);
130
131/// Hash function identifier: SHA3-512
132pub const HASH_ID_SHA3_512: HashIdentifier = HashIdentifier(&[
133 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0A,
134]);
135
136/// Hash function identifier: SHAKE128
137pub const HASH_ID_SHAKE128: HashIdentifier = HashIdentifier(&[
138 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0B,
139]);
140
141/// Hash function identifier: SHAKE256
142pub const HASH_ID_SHAKE256: HashIdentifier = HashIdentifier(&[
143 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0C,
144]);
145
146/// When a message is signed or verified, it is accompanied with a domain
147/// separation context, which is an arbitrary sequence of bytes of length
148/// at most 255. Such a context is wrapped in a `DomainContext` structure.
149pub struct DomainContext<'a>(pub &'a [u8]);
150
151/// Empty domain separation context.
152pub const DOMAIN_NONE: DomainContext = DomainContext(b"");
153
154/// Hash a message into a polynomial modulo q = 12289.
155///
156/// Parameters are:
157///
158/// - `nonce`: 40-byte random nonce
159/// - `hashed_vrfy_key`: SHAKE256 hash of public (verifying) key (64 bytes)
160/// - `ctx`: domain separation context
161/// - `id`: identifier for pre-hash function
162/// - `hv`: message (pre-hashed)
163/// - `c`: output polynomial
164///
165/// If `id` is `HASH_ID_RAW`, then no-prehashing is applied and the message
166/// itself should be provided as `hv`. Otherwise, the caller is responsible
167/// for applying the pre-hashing, and `hv` shall be the hashed message.
168pub fn hash_to_point(
169 nonce: &[u8],
170 hashed_vrfy_key: &[u8],
171 ctx: &DomainContext,
172 id: &HashIdentifier,
173 hv: &[u8],
174 c: &mut [u16],
175) {
176 // TODO: remove support for original Falcon when the final FN-DSA
177 // is defined and has test vectors. Since the message is used "as is",
178 // this encoding can mimic all others, and thus bypasses any attempt at
179 // domain separation. Moreover, ignoring the domain separation context
180 // is a potential source of security issues, since the caller might
181 // expect a strong binding to the context value.
182
183 // Input order:
184 // With pre-hashing:
185 // nonce || hashed_vrfy_key || 0x01 || len(ctx) || ctx || id || hv
186 // Without pre-hashing:
187 // nonce || hashed_vrfy_key || 0x00 || len(ctx) || ctx || message
188 // 'len(ctx)' is the length of the context over one byte (0 to 255).
189
190 assert_eq!(nonce.len(), 40);
191 assert_eq!(hashed_vrfy_key.len(), 64);
192 assert!(ctx.0.len() <= 255);
193 let orig_falcon = id.0.len() == 1 && id.0[0] == 0xFF;
194 let raw_message = id.0.len() == 1 && id.0[0] == 0x00;
195 let mut sh = shake::SHAKE256::new();
196 sh.inject(nonce);
197 if orig_falcon {
198 sh.inject(hv);
199 } else {
200 sh.inject(hashed_vrfy_key);
201 sh.inject(&[if raw_message { 0u8 } else { 1u8 }]);
202 sh.inject(&[ctx.0.len() as u8]);
203 sh.inject(ctx.0);
204 if !raw_message {
205 sh.inject(id.0);
206 }
207 sh.inject(hv);
208 }
209 sh.flip();
210 let mut i = 0;
211 while i < c.len() {
212 let mut v = [0u8; 2];
213 sh.extract(&mut v);
214 let mut w = ((v[0] as u16) << 8) | (v[1] as u16);
215 if w < 61445 {
216 while w >= 12289 {
217 w -= 12289;
218 }
219 c[i] = w;
220 i += 1;
221 }
222 }
223}
224
225/// A generic hash to point method
226pub trait HashToPoint: Copy + Clone {
227 /// Hash a message into a polynomial modulo q = 12289.
228 ///
229 /// Parameters are:
230 ///
231 /// - `rng`: Random number generator used if needed
232 /// - `hashed_vrfy_key`: SHAKE256 hash of public (verifying) key (64 bytes)
233 /// - `ctx`: domain separation context
234 /// - `id`: identifier for pre-hash function
235 /// - `hv`: message (pre-hashed)
236 /// - `c`: output polynomial
237 ///
238 /// If `id` is `HASH_ID_RAW`, then no-prehashing is applied and the message
239 /// itself should be provided as `hv`. Otherwise, the caller is responsible
240 /// for applying the pre-hashing, and `hv` shall be the hashed message.
241 fn hash_to_point<R: CryptoRng + RngCore>(
242 &mut self,
243 rng: &mut R,
244 hashed_vrfy_key: &[u8],
245 ctx: &DomainContext,
246 id: &HashIdentifier,
247 hv: &[u8],
248 c: &mut [u16],
249 );
250
251 /// The nonce used for signing
252 fn nonce(&self) -> &[u8];
253}
254
255/// The default hash to point implementation
256#[derive(Copy, Clone, Debug)]
257pub struct DefaultHashToPoint {
258 first: bool,
259 orig_falcon: bool,
260 nonce: [u8; 40],
261}
262
263impl Default for DefaultHashToPoint {
264 fn default() -> Self {
265 DefaultHashToPoint {
266 first: true,
267 orig_falcon: false,
268 nonce: [0u8; 40],
269 }
270 }
271}
272
273impl HashToPoint for DefaultHashToPoint {
274 fn hash_to_point<R: CryptoRng + RngCore>(
275 &mut self,
276 rng: &mut R,
277 hashed_vrfy_key: &[u8],
278 ctx: &DomainContext,
279 id: &HashIdentifier,
280 hv: &[u8],
281 c: &mut [u16],
282 ) {
283 if self.first || !self.orig_falcon {
284 rng.fill_bytes(&mut self.nonce);
285 hash_to_point(&self.nonce, hashed_vrfy_key, ctx, id, hv, c);
286 self.first = false;
287
288 // TODO: remove when switching to final test vectors.
289 self.orig_falcon = id.0.len() == 1 && id.0[0] == 0xFF;
290 }
291 }
292
293 fn nonce(&self) -> &[u8] {
294 &self.nonce
295 }
296}
297
298#[cfg(feature = "eth_falcon")]
299/// Support for ETHFALCON methods
300pub mod eth_falcon {
301 extern crate alloc;
302 use super::{
303 codec, mq, vrfy_key_size, DomainContext, HashIdentifier, HashToPoint, FN_DSA_LOGN_512,
304 };
305
306 use alloc::vec::Vec;
307 use rand_core::{CryptoRng, RngCore};
308 use tiny_keccak::{Hasher, Keccak};
309
310 const KECCAK_OUTPUT: usize = 32;
311
312 /// The output length of the pubkey from calling `decode_pubkey_to_ntt_packed`
313 pub const PUBKEY_NTT_PACKED_LENGTH: usize = 1024;
314
315 /// The output length of the signature from calling `decode_signature_to_packed`
316 pub const SIGNATURE_ABI_PACKED_LENGTH: usize = 1024;
317
318 /// The required length for salts
319 pub const SALT_LEN: usize = 40;
320
321 // Q = 12289, which is less than 2^16 = 65536, so this is always true
322 // Removed the runtime check to avoid overflow warning
323 const Q: usize = 12289;
324 const N: usize = 512;
325
326 /// KeccakXOF implements the Keccak PRNG as used in ETHFALCON
327 #[derive(Clone, Copy, Debug)]
328 pub struct EthFalconHashToPoint {
329 salt: [u8; SALT_LEN],
330 }
331
332 impl EthFalconHashToPoint {
333 /// Create a new hasher
334 pub fn new(salt: [u8; SALT_LEN]) -> EthFalconHashToPoint {
335 Self { salt }
336 }
337 }
338
339 impl HashToPoint for EthFalconHashToPoint {
340 fn hash_to_point<R: CryptoRng + RngCore>(
341 &mut self,
342 _rng: &mut R,
343 _hashed_vrfy_key: &[u8],
344 _ctx: &DomainContext,
345 _id: &HashIdentifier,
346 hv: &[u8],
347 c: &mut [u16],
348 ) {
349 hash_to_point_keccak(N, hv, &self.salt, c)
350 }
351
352 fn nonce(&self) -> &[u8] {
353 &self.salt
354 }
355 }
356
357 /// KeccakXOF implements the Keccak PRNG as used in ETHFALCON
358 /// This replaces SHAKE256 in standard Falcon
359 ///
360 /// Keccak-based XOF implementation matching the Python KeccakPRNG
361 /// Reference: https://github.com/zknoxhq/ETHFALCON/python-ref/keccak_prng.py
362 #[derive(Default)]
363 struct KeccakXOF {
364 buffer: Vec<u8>,
365 state: [u8; KECCAK_OUTPUT],
366 counter: u64,
367 finalized: bool,
368
369 out_buffer: [u8; KECCAK_OUTPUT],
370 out_buffer_pos: usize,
371 out_buffer_len: usize,
372 }
373
374 impl KeccakXOF {
375 /// Inject (absorb) data into XOF state
376 /// This is called "update" in the SHAKE256 interface
377 pub fn update(&mut self, data: &[u8]) {
378 assert!(!self.finalized, "Cannot update after finalizing");
379 // Use dynamic buffer - no size limit
380 self.buffer.extend_from_slice(data);
381 }
382
383 /// Finalize the XOF state and prepare for output generation
384 /// This is called "flip" in the XOF interface
385 pub fn flip(&mut self) {
386 assert!(!self.finalized, "Cannot flip after finalizing");
387
388 // Hash the buffer to create initial state
389 let mut keccak = Keccak::v256();
390 keccak.update(&self.buffer);
391 keccak.finalize(&mut self.state);
392
393 self.finalized = true;
394
395 // Reset output buffer
396 self.out_buffer_pos = 0;
397 self.out_buffer_len = 0;
398 }
399
400 /// Extract (squeeze) output from the XOF
401 /// This is called "read" in the XOF interface
402 pub fn read(&mut self, output: &mut [u8]) {
403 assert!(self.finalized, "XOF not finalized");
404
405 let mut offset = 0;
406
407 // First, use any bytes remaining in the output buffer
408 if self.out_buffer_len > self.out_buffer_pos {
409 let available = self.out_buffer_len - self.out_buffer_pos;
410 let to_copy = core::cmp::min(output.len(), available);
411
412 output[offset..offset + to_copy].copy_from_slice(
413 &self.out_buffer[self.out_buffer_pos..self.out_buffer_pos + to_copy],
414 );
415 self.out_buffer_pos += to_copy;
416 offset += to_copy;
417
418 // If we've satisfied the request, return early
419 if offset >= output.len() {
420 return;
421 }
422 }
423
424 // Generate more output blocks as needed
425 while offset < output.len() {
426 // Prepare input block: state || counter (big-endian)
427 let mut block = [0u8; KECCAK_OUTPUT + 8];
428 block[..KECCAK_OUTPUT].copy_from_slice(&self.state);
429 block[KECCAK_OUTPUT..].copy_from_slice(&self.counter.to_be_bytes());
430
431 // Generate next block using Keccak-256
432 let mut keccak = Keccak::v256();
433 keccak.update(&block);
434 keccak.finalize(&mut self.out_buffer);
435
436 // Update buffer state
437 self.out_buffer_len = KECCAK_OUTPUT;
438 self.out_buffer_pos = 0;
439
440 // Copy output
441 let remaining = output.len() - offset;
442 let to_copy = core::cmp::min(remaining, KECCAK_OUTPUT);
443
444 output[offset..offset + to_copy].copy_from_slice(&self.out_buffer[..to_copy]);
445 self.out_buffer_pos = to_copy;
446 offset += to_copy;
447
448 // Increment counter for next block
449 self.counter += 1;
450 }
451 }
452
453 /// Reset the XOF to initial state (for future use)
454 #[allow(dead_code)]
455 pub fn reset(&mut self) {
456 self.buffer.clear();
457 self.counter = 0;
458 self.finalized = false;
459 self.out_buffer_pos = 0;
460 self.out_buffer_len = 0;
461 self.state = [0u8; KECCAK_OUTPUT];
462 self.out_buffer = [0u8; KECCAK_OUTPUT];
463 }
464 }
465
466 /// Hash a message and salt to a point in Z[x] mod(Phi, q)
467 /// This follows the same logic as standard Falcon but uses Keccak XOF instead of SHAKE256
468 ///
469 /// Args:
470 /// n: Degree of the polynomial (512 for Falcon-512)
471 /// message: The message to hash
472 /// salt: The salt value (40 bytes in standard Falcon)
473 ///
474 /// Returns:
475 /// A vector of n coefficients in [0, q)
476 pub fn hash_to_point_keccak(n: usize, message: &[u8], salt: &[u8], c: &mut [u16]) {
477 const K: u32 = (1u32 << 16) / (Q as u32);
478
479 assert_eq!(c.len(), n);
480
481 // Create XOF and hash the inputs
482 // Note: In ETHFALCON/KeccakPRNG mode, the order is reversed compared to SHAKE256
483 // Python code: if xof != SHAKE: salt, message = message, salt
484 let mut xof = KeccakXOF::default();
485
486 // ETHFALCON uses message first, then salt (reversed from SHAKE256)
487 xof.update(message);
488 xof.update(salt);
489
490 xof.flip();
491
492 // Output pseudorandom coefficients using rejection sampling
493 let mut i = 0;
494 let mut two_bytes = [0u8; 2];
495
496 while i < n {
497 // Read 2 bytes and interpret as a 16-bit integer
498 xof.read(&mut two_bytes);
499
500 // Big-endian: (byte[0] << 8) + byte[1]
501 let elt = ((two_bytes[0] as u32) << 8) + (two_bytes[1] as u32);
502
503 // Rejection sampling: accept if elt < k * q
504 if elt < K * (Q as u32) {
505 c[i] = (elt % (Q as u32)) as u16;
506 i += 1;
507 }
508 }
509 }
510
511 /// Decode a Falcon public key to NTT abi.encodePacked format
512 ///
513 /// Returns the public key polynomial h in NTT form, abi.encodePacked(uint256[32]) format
514 ///
515 /// Converts a Falcon public key to ETHFALCOM Solidity format (abi.encodePacked, NTT form)
516 ///
517 /// NOTE:
518 /// Decode Falcon public key to abi.encodePacked NTT format
519 ///
520 /// Falcon public key format: [header (1 byte)] + [compressed h]
521 /// abi.encodePacked format: 1024 bytes (32 uint256 values × 32 bytes each, h in NTT form)
522 pub fn decode_pubkey_to_ntt_packed(
523 pubkey: &[u8],
524 ) -> Result<[u8; SIGNATURE_ABI_PACKED_LENGTH], &'static str> {
525 if pubkey.len() < 1 {
526 return Err("Public key too short");
527 }
528
529 let header = pubkey[0];
530 let logn = (header & 0x0F) as u32;
531
532 if pubkey.len() != vrfy_key_size(logn) {
533 return Err("Invalid public key length");
534 }
535
536 // Decode h from compressed format
537 let mut h = [0u16; N];
538 codec::modq_decode(&pubkey[1..], &mut h).ok_or("Failed to decode public key")?;
539
540 // Convert h to NTT form
541 mq::mqpoly_ext_to_int(logn, &mut h);
542 mq::mqpoly_int_to_NTT(logn, &mut h);
543
544 // Convert h_ntt to abi.encodePacked(uint256[32]) format
545 // 512 coefficients → 32 uint256 (16 coefficients per uint256, LSB-first)
546 let mut packed = [0u8; PUBKEY_NTT_PACKED_LENGTH];
547 decode(logn, &h, &mut packed)?;
548
549 Ok(packed)
550 }
551
552 /// Decode a Falcon signature to extract s2 coefficients
553 ///
554 /// Returns the s2 polynomial in abi.encodePacked(uint256[32]) format
555 ///
556 /// NOTE:
557 /// Decode Falcon compressed signature to abi.encodePacked format
558 ///
559 /// Falcon signature format: [header (1 byte)] + [salt (40 bytes)] + [compressed s2]
560 /// abi.encodePacked format: 1024 bytes (32 uint256 values × 32 bytes each)/
561 pub fn decode_signature_to_packed(
562 signature: &[u8],
563 ) -> Result<[u8; SIGNATURE_ABI_PACKED_LENGTH], &'static str> {
564 if signature.len() < 41 {
565 return Err("Signature too short");
566 }
567
568 let header = signature[0];
569 let logn = (header & 0x0F) as u32;
570
571 let compressed_s2 = &signature[41..];
572
573 // Decompress s2 using fn-dsa's codec
574 let mut s2 = [0i16; N];
575 if !codec::comp_decode(compressed_s2, &mut s2) {
576 return Err("Failed to decompress signature");
577 }
578 let mut s2_u16 = [0u16; N];
579 for (c, &coeff) in s2_u16.iter_mut().zip(s2.iter()) {
580 // Convert signed i16 to unsigned u16 (mod q)
581 *c = if coeff < 0 {
582 (Q as i32 + coeff as i32) as u16
583 } else {
584 coeff as u16
585 };
586 }
587
588 // Convert s2 to abi.encodePacked(uint256[32]) format
589 // 512 coefficients → 32 uint256 (16 coefficients per uint256, LSB-first)
590 let mut packed = [0u8; SIGNATURE_ABI_PACKED_LENGTH];
591 decode(logn, &s2_u16, &mut packed)?;
592
593 Ok(packed)
594 }
595
596 fn decode(logn: u32, coefficients: &[u16], packed: &mut [u8]) -> Result<(), &'static str> {
597 if logn != FN_DSA_LOGN_512 {
598 return Err("Only Falcon-512 (logn=9) supported");
599 }
600 assert_eq!(1usize << logn, N);
601
602 for chunk_idx in 0..32 {
603 let mut value = [0u8; 32]; // Big-endian uint256
604
605 // Pack 16 coefficients into this uint256 (LSB-first)
606 for coeff_idx in 0..16 {
607 let h_idx = chunk_idx * 16 + coeff_idx;
608 let coeff = coefficients[h_idx];
609
610 // Pack into uint256 at correct position (rightmost = coeff 0)
611 let byte_offset = 30 - (coeff_idx * 2); // Rightmost bytes first
612 value[byte_offset] = (coeff >> 8) as u8;
613 value[byte_offset + 1] = coeff as u8;
614 }
615
616 // Copy to output
617 packed[chunk_idx * 32..(chunk_idx + 1) * 32].copy_from_slice(&value);
618 }
619
620 Ok(())
621 }
622
623 #[cfg(test)]
624 mod tests {
625 use super::*;
626
627 #[test]
628 fn test_deterministic_32_bytes() {
629 let mut xof = KeccakXOF::default();
630 xof.update(b"test input");
631 xof.flip();
632 let mut output = [0u8; 32];
633 xof.read(&mut output);
634
635 let expected =
636 hex::decode("5b9e99370fa4b753ac6bf0d246b3cec353c84a67839f5632cb2679b4ae565601")
637 .unwrap();
638 assert_eq!(
639 &output[..],
640 expected,
641 "KeccakPRNG output mismatch for 'test input' (32 bytes)"
642 );
643 }
644
645 #[test]
646 fn test_deterministic_64_bytes_second_half() {
647 let mut xof = KeccakXOF::default();
648 xof.update(b"test input");
649 xof.flip();
650 let mut output = [0u8; 64];
651 xof.read(&mut output);
652
653 // Check the second half (bytes 32-64)
654 let expected_second_half =
655 hex::decode("569857b781dd8b81dd9cb45d06999916742043ff52f1cf165e161bcc9938b705")
656 .unwrap();
657 assert_eq!(
658 &output[32..],
659 &expected_second_half[..],
660 "KeccakPRNG second half mismatch"
661 );
662 }
663
664 #[test]
665 fn test_testinput_no_space() {
666 let mut xof = KeccakXOF::default();
667 xof.update(b"testinput");
668 xof.flip();
669 let mut output = [0u8; 32];
670 xof.read(&mut output);
671
672 let expected =
673 hex::decode("120f76b5b7198706bc294a942f8d17467aadb2bb1fa2cc1fecadbaba93c0dd74")
674 .unwrap();
675 assert_eq!(
676 &output[..],
677 expected,
678 "KeccakPRNG output mismatch for 'testinput'"
679 );
680 }
681
682 #[test]
683 fn test_incremental_inject() {
684 // Inject "testinput" as one chunk
685 let mut xof1 = KeccakXOF::default();
686 xof1.update(b"testinput");
687 xof1.flip();
688 let mut output1 = [0u8; 32];
689 xof1.read(&mut output1);
690
691 // Inject "test" then "input" as two chunks
692 let mut xof2 = KeccakXOF::default();
693 xof2.update(b"test");
694 xof2.update(b"input");
695 xof2.flip();
696 let mut output2 = [0u8; 32];
697 xof2.read(&mut output2);
698
699 assert_eq!(
700 output1, output2,
701 "Incremental inject should produce same output"
702 );
703 }
704
705 #[test]
706 fn test_multiple_extractions() {
707 let mut xof = KeccakXOF::default();
708 xof.update(b"test sequence");
709 xof.flip();
710
711 let mut output1 = [0u8; 16];
712 let mut output2 = [0u8; 16];
713 let mut output3 = [0u8; 16];
714
715 xof.read(&mut output1);
716 xof.read(&mut output2);
717 xof.read(&mut output3);
718
719 let expected1 = hex::decode("9e96b1e50719da6f0ea5b664ac8bbac5").unwrap();
720 let expected2 = hex::decode("eb409b4db770b124363b393a0c96b5d6").unwrap();
721 let expected3 = hex::decode("1be071eca45961aca979e88e3784a751").unwrap();
722
723 assert_eq!(&output1[..], expected1, "First extraction mismatch");
724 assert_eq!(&output2[..], expected2, "Second extraction mismatch");
725 assert_eq!(&output3[..], expected3, "Third extraction mismatch");
726
727 // All three should be different
728 assert_ne!(output1, output2);
729 assert_ne!(output2, output3);
730 assert_ne!(output1, output3);
731 }
732
733 #[test]
734 fn test_extract_2_2_vs_4() {
735 let mut xof1 = KeccakXOF::default();
736 xof1.update(b"Danette");
737 xof1.flip();
738 let mut out1a = [0u8; 2];
739 let mut out1b = [0u8; 2];
740 xof1.read(&mut out1a);
741 xof1.read(&mut out1b);
742 let combined1 = [&out1a[..], &out1b[..]].concat();
743
744 let mut xof2 = KeccakXOF::default();
745 xof2.update(b"Danette");
746 xof2.flip();
747 let mut out2 = [0u8; 4];
748 xof2.read(&mut out2);
749
750 assert_eq!(combined1, out2, "Reading 2+2 should equal reading 4");
751 }
752 }
753}
754
755/// Trait for a deterministic pseudorandom generator.
756///
757/// The trait `PRNG` characterizes a stateful object that produces
758/// pseudorandom bytes (and larger values) in a cryptographically secure
759/// way; the object is created with a source seed, and the output is
760/// indistinguishable from uniform randomness up to exhaustive enumeration
761/// of the possible values of the seed.
762///
763/// `PRNG` instances must also implement `Copy` and `Clone` so that they
764/// may be embedded in clonable structures. This implies that copying a
765/// `PRNG` instance is supposed to clone its internal state, and the copy
766/// will output the same values as the original.
767pub trait PRNG: Copy + Clone {
768 /// Create a new instance over the provided seed.
769 fn new(seed: &[u8]) -> Self;
770 /// Get the next byte from the PRNG.
771 fn next_u8(&mut self) -> u8;
772 /// Get the 16-bit value from the PRNG.
773 fn next_u16(&mut self) -> u16;
774 /// Get the 64-bit value from the PRNG.
775 fn next_u64(&mut self) -> u64;
776}
777
778#[cfg(all(
779 not(feature = "no_avx2"),
780 any(target_arch = "x86_64", target_arch = "x86")
781))]
782cpufeatures::new!(cpuid_avx2, "avx2");
783
784/// Do a rutime check for AVX2 support (x86 and x86_64 only).
785///
786/// This is a specialized subcase of the is_x86_feature_detected macro,
787/// except that this function is compatible with `no_std` builds.
788#[cfg(all(
789 not(feature = "no_avx2"),
790 any(target_arch = "x86_64", target_arch = "x86")
791))]
792pub fn has_avx2() -> bool {
793 cpuid_avx2::get()
794}