@@ -419,7 +419,7 @@ where
419419mod tests {
420420 use super :: * ;
421421
422- use std:: collections:: HashMap ;
422+ use std:: collections:: { HashMap , HashSet } ;
423423
424424 use crate :: drgraph:: new_seed;
425425 use crate :: hasher:: { Blake2sHasher , PedersenHasher , Sha256Hasher } ;
@@ -542,4 +542,43 @@ mod tests {
542542 let parents_cache_lock = zigzag_graph. parents_cache . read ( ) . unwrap ( ) ;
543543 ( * parents_cache_lock) [ zigzag_graph. get_cache_index ( ) ] . len ( )
544544 }
545+
546+ // Test that 3 (or more) rounds of the Feistel cipher can be used
547+ // as a pseudorandom permutation, that is, each input will be mapped
548+ // to a unique output (and though not test here, since the cipher
549+ // is symmetric, the decryption rounds also work as the inverse
550+ // permutation), for more details see:
551+ // https://en.wikipedia.org/wiki/Feistel_cipher#Theoretical_work.
552+ #[ test]
553+ fn test_shuffle ( ) {
554+ let n = 2_u64 . pow ( 10 ) ;
555+ let d = DEFAULT_EXPANSION_DEGREE as u64 ;
556+ // Use a relatively small value of `n` as Feistel is expensive (but big
557+ // enough that `n >> d`).
558+
559+ let mut shuffled: HashSet < u64 > = HashSet :: with_capacity ( ( n * d) as usize ) ;
560+
561+ let feistel_keys = & [ 1 , 2 , 3 , 4 ] ;
562+ let feistel_precomputed = feistel:: precompute ( ( n * d) as feistel:: Index ) ;
563+
564+ for i in 0 ..n {
565+ for k in 0 ..d {
566+ let permuted =
567+ feistel:: permute ( n * d, i * d + k, feistel_keys, feistel_precomputed) ;
568+
569+ // Since the permutation implies a one-to-one correspondence,
570+ // traversing the entire input space should generate the entire
571+ // output space (in `shuffled`) without repetitions (since a duplicate
572+ // output would imply there is another output that wasn't generated
573+ // and the permutation would be incomplete).
574+ assert ! ( shuffled. insert( permuted) ) ;
575+ }
576+ }
577+
578+ // Actually implied by the previous `assert!` this is left in place as an
579+ // extra safety check that indeed the permutation preserved all the output
580+ // space (of `n * d` nodes) without repetitions (which the `HashSet` would
581+ // have skipped as duplicates).
582+ assert_eq ! ( shuffled. len( ) , ( n * d) as usize ) ;
583+ }
545584}
0 commit comments