Skip to main content

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}