Source file seed.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
open Stdint
let xshift = Ctypes.sizeof Ctypes_static.uint32_t * 8 / 2
let mult_a, mult_l, mult_r = Uint32.(of_int 0x931e8875, of_int 0xca01f9dd, of_int 0x4973f715)
let hashmix const value =
let open Uint32 in
let const' = const * mult_a in
let value' = (logxor value const) * const' in
const', logxor (shift_right value' xshift) value'
let mixhashmix x y const =
let const', x' = hashmix const x in
let res = Uint32.(mult_l * y - mult_r * x') in
const', Uint32.(logxor (shift_right res xshift) res)
let mask32 = Uint128.of_int 0xffffffff
let to_u32_array l =
let f n = Some Uint128.(logand n mask32 |> to_uint32, shift_right n 32) in
let split n = Seq.unfold f n |> Seq.take_while (fun x -> Uint32.(x > zero))
|> (fun s -> if Seq.is_empty s then Seq.(return Uint32.zero) else s)
in List.to_seq l |> Seq.concat_map split |> Array.of_seq
let randbits128 () =
let x, y = Int64.abs (Random.bits64 ()), Int64.abs (Random.bits64 ()) in
Uint128.(logor (shift_left (of_int64 x) 64) (of_int64 y))
module SeedSequence : sig
(** SeedSequence mixes sources of entropy in a reproducible way to set the
initial state for independent and very probably non-overlapping BitGenerators.
Once the SeedSequence is initialized, one can call the
{!SeedSequence.generate_64bit_state} or {!SeedSequence.generate_32bit_state}
functions to get an appropriately sized seed that can be used to initialize
any of the supported PRNG's. Calling {!SeedSequence.spawn} will create
[n] SeedSequence's that can be used to seed independent BitGenerators.
Best practice for achieving reproducible bit streams is to use
the empty list as an initializing value, and then use {!SeedSequence.entropy}
to log the entropy for reproducibility:
{@ocaml[
open Bitgen
let ss = SeedSequence.initialize []
SeedSequence.entropy ss |> List.map Uint128.to_string
(*: string list = ["152280350332430215596244075920305924447"] *)
]} *)
type t
(** [t] is the type of a SeedSequence *)
val initialize : ?spawn_key:uint128 list -> ?pool_size:int -> uint128 list -> t
(** [initialize seed] Creates a new seed sequence type given a list of unsigned 128-bit
integers [seed]. [~spawn_key] is an additional source of entropy based on
the position of type in the tree of such types created with {!SeedSequence.spawn}.
Typically users need not set this parameter. [~pool_size] is the size of
the pooled entropy to store. It defaults to the value 4 to give a 128-bit
entropy pool. *)
val generate_32bit_state : int -> t -> uint32 array
(** [generate_32bit_state n t] returns an array of [n] unsigned 32-bit integers for
seeding a PRNG and generating it's initial 32-bit state. *)
val generate_64bit_state : int -> t -> uint64 array
(** [generate_64bit_state n t] returns an array of [n] unsigned 64-bit integers for
seeding a PRNG and generating it's initial state. *)
val spawn : int -> t -> t list * t
(** [spawn n t] creates [n] independent child types of [t] that can be used
to initialize [n] bitgenerator instances. *)
val children_spawned : t -> int
(** The number of children already spawned by this instance of a SeedSequence type. *)
val entropy : t -> uint128 list
(** [entropy t] gives a list representing the entropy used to create [t]. It
can be used as an argument of {!SeedSequence.initialize} to reproduce the
the bit stream of [t]. *)
end = struct
type t = {
entropy : uint128 list;
spawn_key : uint128 list;
children_spawned : int;
pool : uint32 array;
}
let entropy t = t.entropy
let children_spawned t = t.children_spawned
let mix_entropy pool_size entropy =
let values, leftover = match Array.length entropy, pool_size with
| le, lp when le > lp -> entropy, le - lp
| le, lp -> Array.append entropy (Array.init (lp - le) (fun _ -> Uint32.zero)), 0 in
let hash, pool = Array.fold_left_map hashmix (Uint32.of_int 0x43b0d7e5) values in
let indices = Array.init pool_size (fun x -> x) in
let f (acc0, acc1) j = Array.fold_left_map
(fun c i -> if i <> j then mixhashmix acc1.(j) acc1.(i) c else c, acc1.(i)) acc0 indices in
let hash', pool' = Array.fold_left f (hash, pool) indices in
if leftover > 0 then
let leftover_indices = (Array.init leftover (fun i -> pool_size + i)) in
let f (acc0, acc1) j = Array.fold_left_map
(fun c i -> mixhashmix entropy.(j) acc1.(i) c) acc0 indices in
Array.fold_left f (hash', pool') leftover_indices |> snd
else pool'
let assembled_entropy entropy spawn_key pool_size =
let run_entropy = to_u32_array entropy
and spawn_entropy = to_u32_array spawn_key in
let l = match Array.(length spawn_entropy > 0 && length run_entropy < pool_size) with
| true -> Array.init (pool_size - Array.length run_entropy) (fun _ -> Uint32.zero)
| false -> [||]
in Array.concat [run_entropy; l; spawn_entropy]
let initialize ?(spawn_key=[]) ?(pool_size=4) entropy =
let entropy' = if List.compare_length_with entropy 0 = 0 then [randbits128 ()] else entropy in
let assembled = assembled_entropy entropy' spawn_key pool_size in
{spawn_key; entropy = entropy'; children_spawned = 0; pool = mix_entropy pool_size assembled}
let spawn n t =
let f i =
let psize = Array.length t.pool
and spawn_key = t.spawn_key @ [i] in
let pool = assembled_entropy t.entropy spawn_key psize |> mix_entropy psize in
Some ({t with pool; spawn_key; children_spawned = 0}, Uint128.(i + one))
in Seq.unfold f Uint128.(of_int n) |> Seq.take n |> List.of_seq,
{t with children_spawned = t.children_spawned + n}
let init_b, mult_b = Uint32.(of_int 0x8b51f9dd, of_int 0x58f38ded)
let generate_32bit_state n_words t =
let f acc a =
let a' = Uint32.(logxor a acc)
and acc' = Uint32.(acc * mult_b) in
let a'' = Uint32.(a' * acc') in
acc', Uint32.(shift_right a'' xshift |> logxor a'')
in Array.to_seq t.pool |> Seq.cycle |> Seq.take n_words
|> Array.of_seq |> Array.fold_left_map f init_b |> snd
let generate_64bit_state n_words t =
let o = generate_32bit_state (n_words * 2) t |> Array.map Uint64.of_uint32 in
Array.init n_words (fun i -> Uint64.logor o.(i*2) (Uint64.shift_left o.(i*2 + 1) 32))
end