1- function randomInRange ( min : number , max : number , seed : Uint8Array = crypto . getRandomValues ( new Uint8Array ( 4 ) ) ) : number {
2- const range = max - min + 1 ;
3- const randomValue = ( seed [ 0 ] << 24 ) | ( seed [ 1 ] << 16 ) | ( seed [ 2 ] << 8 ) | seed [ 3 ] ;
4- return min + ( randomValue % range ) ;
1+ function makePRNG ( seed : number ) {
2+ let state = seed >>> 0 ;
3+ return ( ) => {
4+ // xorshift32
5+ state ^= state << 13 ;
6+ state ^= state >>> 17 ;
7+ state ^= state << 5 ;
8+ return state >>> 0 ;
9+ } ;
510}
611
712export function makeRandomSeed ( ) : Uint8Array {
813 return crypto . getRandomValues ( new Uint8Array ( 4 ) ) ;
914}
1015
11- export function secureShuffle < T > ( array : T [ ] , seed : Uint8Array = crypto . getRandomValues ( new Uint8Array ( 4 ) ) ) : T [ ] {
12- const numbers = [ ] ;
16+ function randomInRange ( min : number , max : number , nextRand : ( ) => number ) : number {
17+ const range = max - min + 1 ;
18+ const maxRand = 0xFFFFFFFF ;
19+ const limit = Math . floor ( maxRand / range ) * range ;
1320
14- // asynchronously generate an array of random numbers using a CSPRNG
15- for ( let i = array . length - 1 ; i > 0 ; i -- ) {
16- numbers . push ( randomInRange ( 0 , i , seed ) ) ;
17- }
21+ let randomValue : number ;
22+ do {
23+ randomValue = nextRand ( ) ;
24+ } while ( randomValue >= limit ) ;
25+
26+ return min + ( randomValue % range ) ;
27+ }
28+
29+ export function secureShuffle < T > ( array : T [ ] , seedArray ?: Uint8Array ) : T [ ] {
30+ // Convert Uint8Array seed (4 bytes) into 32-bit integer
31+ const seed =
32+ seedArray
33+ ? ( ( seedArray [ 0 ] << 24 ) | ( seedArray [ 1 ] << 16 ) | ( seedArray [ 2 ] << 8 ) | seedArray [ 3 ] ) >>> 0
34+ : crypto . getRandomValues ( new Uint8Array ( 4 ) ) . reduce ( ( acc , v , i ) => acc | ( v << ( ( 3 - i ) * 8 ) ) , 0 ) ;
1835
19- // apply durstenfeld shuffle with previously generated random numbers
20- for ( let i = array . length - 1 ; i > 0 ; i -- ) {
21- const j = numbers [ array . length - i - 1 ] ;
22- const temp = array [ i ] ;
23- array [ i ] = array [ j ] ;
24- array [ j ] = temp ;
36+ const nextRand = makePRNG ( seed ) ;
37+ const result = array . slice ( ) ;
38+
39+ // Fisher–Yates shuffle using seeded randoms
40+ for ( let i = result . length - 1 ; i > 0 ; i -- ) {
41+ const j = randomInRange ( 0 , i , nextRand ) ;
42+ [ result [ i ] , result [ j ] ] = [ result [ j ] , result [ i ] ] ;
2543 }
2644
27- return array ;
28- }
45+ return result ;
46+ }
0 commit comments