123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139(* Copyright 2010-2012, D. E. Shaw Research. All rights reserved.
Copyright (c) 2024, Zolisa Bleki
SPDX-License-Identifier: BSD-3-Clause *)openStdintmodulePhilox:sig(** Philox4x64 (a mnemonic for Product HI LO Xor) is a 64-bit PRNG that uses a
counter-based design based on weaker (and faster) versions of cryptographic
functions. Instances using different values of the key produce independent
sequences. Philox has a period of {m 2^{256} - 1} and supports arbitrary
advancing and jumping the sequence in increments of {m 2^{128}}. These
features allow multiple non-overlapping sequences to be generated. Philox's
round function is applied 10 times each time the PRNG is advanced forward.
The Philox state vector consists of a 256-bit value encoded as a 4-element
unsigned 64-bit tuple and a 128-bit value encoded as a 2-element unsigned
64-bit tuple. The former is a counter which is incremented by 1 for every
4 64-bit randoms produced. The second is a key which determined the sequence
produced. Using different keys produces independent sequences.
{!SeedSequence} is used to produce a high-quality initial state for the
key vector. The counter is set to 0.
The preferred way to use Philox in parallel applications is to use
the {!SeedSequence.spawn} function to obtain entropy values, and to use these
to generate new instance of a Philox bitgenerator:
{@ocaml[
open Bitgen
let gens =
SeedSequence.initialize []
|> SeedSequence.spawn 10
|> fst
|> List.map Philox4x64.initialize
]} *)includeCommon.BITGENvalinitialize_ctr:counter:uint64*uint64*uint64*uint64->Seed.SeedSequence.t->t(** Get the initial state of the generator using a 4-element unsigned 64-bit tuple as
the bitgenerator's [counter] initial state as well as {!SeedSequence.t} for the
initiale state of the generator's [key].*)valjump:t->t(** [jump t] is equivalent to {m 2^{128}} calls to {!Philox4x64.next_uint64}. *)valadvance:uint64*uint64*uint64*uint64->t->t(** [advance n] Advances the generator forward as if [n] draws have been made,
and returns the new advanced state.*)end=structtypet={key:key;ctr:counter;buffer:buffer;buffer_pos:int;ustore:uint32option}andcounter=uint64arrayandkey=uint64arrayandbuffer=uint64arrayletrh0,rh1=Uint128.(of_string"0xD2E7470EE14C6C93",of_string"0xCA5A826395121157")letk0,k1=Uint64.(of_string"0x9E3779B97F4A7C15",of_string"0xBB67AE8584CAA73B")(* golden ratio and sqrt(3)-1 *)letmulhilo64ab=letp=Uint128.(a*of_uint64b)inUint128.[|shift_rightp64|>to_uint64;to_uint64p|](* Apply Philox's round function (r + 1) rounds for r >= 2. *)letrecroundsckr=letc'=matchmulhilo64rh0c.(0),mulhilo64rh1c.(2)with|x,y->Uint64.[|logxory.(0)c.(1)|>logxork.(0);y.(1);logxorx.(0)c.(3)|>logxork.(1);x.(1)|]inmatchrwith|0->c'|i->roundsc'Uint64.[|k.(0)+k0;k.(1)+k1|](i-1)letnextc=letopenUint64inmatchc.(0)+one,c.(1)+one,c.(2)+onewith|c0',c1',c2'when(c0'=zero&&c1'=zero&&c2'=zero)->[|c0';c1';c2';c.(3)+one|]|c0',c1',c2'when(c0'=zero&&c1'=zero)->[|c0';c1';c2';c.(3)|]|c0',c1',_whenc0'=zero->[|c0';c1';c.(2);c.(3)|]|c0',_,_->[|c0';c.(1);c.(2);c.(3)|]letnext_uint64t=matcht.buffer_pos<4with|true->t.buffer.(t.buffer_pos),{twithbuffer_pos=t.buffer_pos+1}|false->letctr=nextt.ctrinletbuffer=roundsctrt.key9in(* perform 10 rounds *)buffer.(0),{twithctr;buffer;buffer_pos=1}letnext_uint32t=matchCommon.next_uint32~next:next_uint64tt.ustorewith|u,s,ustore->u,{swithustore}letnext_doublet=Common.next_double~nextu64:next_uint64tletnext_bounded_uint64boundt=Common.next_bounded_uint64bound~nextu64:next_uint64tletjumpt=letc2'=Uint64.(t.ctr.(2)+one)inletctr=matchUint64.(c2'=zero)with|true->[|t.ctr.(0);t.ctr.(1);c2';Uint64.(t.ctr.(3)+one)|]|false->[|t.ctr.(0);t.ctr.(1);c2';t.ctr.(3)|]in{twithctr;ustore=None;buffer_pos=4}letadvance(d0,d1,d2,d3)t=letauxsxc=letx',c'=matchUint64.(x+one),cwith|v,truewhenUint64.(v=zero)->v,true|v,true->v,false|_,false->x,cinmatchUint64.(x'+s)with|vwhen(v<x'&&c'=false)->v,true|v->v,c'inletc0',p0=auxd0t.ctr.(0)falseinletc1',p1=auxd1t.ctr.(1)p0inletc2',p2=auxd2t.ctr.(2)p1inletc3',_=auxd3t.ctr.(3)p2in{twithctr=[|c0';c1';c2';c3'|];ustore=None;buffer_pos=4}letinitialize_ctr~counter:(w,x,y,z)seed={buffer_pos=4;ctr=[|w;x;y;z|];ustore=None;buffer=Array.make4Uint64.zero;key=Seed.SeedSequence.generate_64bit_state2seed}letinitializeseed=initialize_ctr~counter:Uint64.(zero,zero,zero,zero)seedend