123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292 | # Random Number Generator (RNG) implementation based on ChaCha
use <assert.h>
use <gc.h>
use <gmp.h>
use ./sysrandom.h
use ./chacha.h
use -lgmp
use -lgc
struct chacha_ctx(j0,j1,j2,j3,j4,j5,j6,j7,j8,j9,j10,j11,j12,j13,j14,j15:Int32; secret)
func from_seed(seed:[Byte]=[] -> chacha_ctx)
ctx := chacha_ctx(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
C_code`
uint8_t seed_bytes[KEYSZ + IVSZ] = {};
if (@seed.length <= (int64_t)sizeof(seed_bytes)) {
for (int64_t i = 0; i < (int64_t)sizeof(seed_bytes); i++)
seed_bytes[i] = i < @seed.length ? *(uint8_t*)(@seed.data + i*@seed.stride) : 0;
} else {
// If the seed is too big, we use as many bytes from the start of the seed as we can
// fit and then hash the rest of the seed and use the hash bytes at the end:
for (int64_t i = 0; i < KEYSZ + IVSZ - (int64_t)sizeof(uint64_t); i++)
seed_bytes[i] = i < @seed.length ? *(uint8_t*)(@seed.data + i*@seed.stride) : 0;
List_t rest = List$from(@seed, I((int64_t)sizeof(seed_bytes)));
uint64_t hash = generic_hash(&rest, List$info(&Byte$info));
memcpy(seed_bytes + KEYSZ + IVSZ - sizeof(uint64_t), &hash, sizeof(hash));
}
chacha_keysetup((void*)&@ctx, seed_bytes);
chacha_ivsetup((void*)&@ctx, seed_bytes + KEYSZ);
`
return ctx
random := RandomNumberGenerator.new()
func _os_random_bytes(count:Int64 -> [Byte])
return C_code:[Byte]`
uint8_t *random_bytes = GC_MALLOC_ATOMIC(@count);
assert(getrandom(random_bytes, (size_t)@count, 0) == (size_t)@count);
(List_t){.length=@count, .data=random_bytes, .stride=1, .atomic=1}
`
struct RandomNumberGenerator(_chacha:chacha_ctx, _random_bytes:[Byte]=[]; secret)
func new(seed:[Byte]?=none -> RandomNumberGenerator)
ctx := chacha_ctx.from_seed(seed or _os_random_bytes(40))
return RandomNumberGenerator(ctx, [])
func _rekey(rng:&RandomNumberGenerator)
new_bytes : [Byte]
C_code `
Byte_t new_keystream[KEYSZ + IVSZ] = {};
// Fill the buffer with the keystream
chacha_encrypt_bytes((void*)&@rng->_chacha, new_keystream, new_keystream, sizeof(new_keystream));
// Immediately reinitialize for backtracking resistance
chacha_keysetup((void*)&@rng->_chacha, new_keystream);
chacha_ivsetup((void*)&@rng->_chacha, new_keystream + KEYSZ);
@new_bytes = (List_t){.data=GC_MALLOC_ATOMIC(1024), .length=1024, .stride=1, .atomic=1};
memset(@new_bytes.data, 0, @new_bytes.length);
chacha_encrypt_bytes((void*)&@rng->_chacha, @new_bytes.data, @new_bytes.data, @new_bytes.length);
`
rng._random_bytes = new_bytes
func _fill_bytes(rng:&RandomNumberGenerator, dest:&Memory, needed:Int64)
C_code `
while (@needed > 0) {
if (@rng->_random_bytes.length == 0)
@(rng._rekey());
assert(@rng->_random_bytes.stride == 1);
if (@rng->_random_bytes.data_refcount > 0) {
List$compact(&@rng->_random_bytes, sizeof(Byte_t));
}
int64_t batch_size = MIN(@needed, @rng->_random_bytes.length);
uint8_t *batch_src = @rng->_random_bytes.data;
memcpy(@dest, batch_src, batch_size);
memset(batch_src, 0, batch_size);
@rng->_random_bytes.data += batch_size;
@rng->_random_bytes.length -= batch_size;
@dest += batch_size;
@needed -= batch_size;
}
`
func bytes(rng:&RandomNumberGenerator, count:Int -> [Byte])
count64 := Int64(count)
buf := C_code:@Memory`GC_MALLOC_ATOMIC(@count64)`
rng._fill_bytes(buf, count64)
return C_code:[Byte]`(List_t){.data=@buf, .stride=1, .atomic=1, .length=@count64}`
func byte(rng:&RandomNumberGenerator, min:Byte=0, max:Byte=Byte.max -> Byte)
fail("Random minimum value $min is larger than the maximum value $max") if min > max
return min if min == max
random_byte : &Byte
rng._fill_bytes(random_byte, 1)
if min == 0 and max == Byte.max
return random_byte[]
return C_code:Byte`
Byte_t range = (Byte_t)@max - (Byte_t)@min + 1;
Byte_t min_r = -range % range;
for (;;) {
@(rng._fill_bytes(random_byte, 1));
if (*@random_byte >= min_r) break;
}
@min + (*@random_byte % range)
`
func bool(rng:&RandomNumberGenerator, probability=0.5 -> Bool)
if probability == 0.5
return rng.byte() < 0x80
else
return rng.num(0., 1.) < 0.5
func int64(rng:&RandomNumberGenerator, min=Int64.min, max=Int64.max -> Int64)
fail("Random minimum value $min is larger than the maximum value $max") if min > max
return min if min == max
random_int64 : &Int64
rng._fill_bytes(random_int64, 8)
if min == Int64.min and max == Int64.max
return random_int64
return C_code:Int64`
uint64_t range = (uint64_t)@max - (uint64_t)@min + 1;
uint64_t min_r = -range % range;
uint64_t r;
@random_int64 = (int64_t*)&r;
for (;;) {
@(rng._fill_bytes(random_int64, 8));
if (r >= min_r) break;
}
(int64_t)((uint64_t)@min + (r % range))
`
func int32(rng:&RandomNumberGenerator, min=Int32.min, max=Int32.max -> Int32)
fail("Random minimum value $min is larger than the maximum value $max") if min > max
return min if min == max
random_int32 : &Int32
rng._fill_bytes(random_int32, 8)
if min == Int32.min and max == Int32.max
return random_int32
return C_code:Int32`
uint32_t range = (uint32_t)@max - (uint32_t)@min + 1;
uint32_t min_r = -range % range;
uint32_t r;
@random_int32 = (int32_t*)&r;
for (;;) {
@(rng._fill_bytes(random_int32, 4));
if (r >= min_r) break;
}
(int32_t)((uint32_t)@min + (r % range))
`
func int16(rng:&RandomNumberGenerator, min=Int16.min, max=Int16.max -> Int16)
fail("Random minimum value $min is larger than the maximum value $max") if min > max
return min if min == max
random_int16 : &Int16
rng._fill_bytes(random_int16, 8)
if min == Int16.min and max == Int16.max
return random_int16
return C_code:Int16`
uint16_t range = (uint16_t)@max - (uint16_t)@min + 1;
uint16_t min_r = -range % range;
uint16_t r;
@random_int16 = (int16_t*)&r;
for (;;) {
@(rng._fill_bytes(random_int16, 2));
if (r >= min_r) break;
}
(int16_t)((uint16_t)@min + (r % range))
`
func int8(rng:&RandomNumberGenerator, min=Int8.min, max=Int8.max -> Int8)
fail("Random minimum value $min is larger than the maximum value $max") if min > max
return min if min == max
random_int8 : &Int8
rng._fill_bytes(random_int8, 1)
if min == Int8.min and max == Int8.max
return random_int8[]
return C_code:Int8`
uint8_t range = (uint8_t)@max - (uint8_t)@min + 1;
uint8_t min_r = -range % range;
uint8_t r;
@random_int8 = (int8_t*)&r;
for (;;) {
@(rng._fill_bytes(random_int8, 1));
if (r >= min_r) break;
}
(int8_t)((uint8_t)@min + (r % range))
`
func num(rng:&RandomNumberGenerator, min=0., max=1. -> Num)
if min > max
fail("Random minimum value ($min) is larger than the maximum value ($max)")
return C_code:Num`
if (@min == @max) return @min;
union {
Num_t num;
uint64_t bits;
} r = {.bits=0}, one = {.num=1.0};
@(rng._fill_bytes(C_code:&Num`&r.num`, 8));
// Set r.num to 1.<random-bits>
r.bits &= ~(0xFFFULL << 52);
r.bits |= (one.bits & (0xFFFULL << 52));
r.num -= 1.0;
(@min == 0.0 && @max == 1.0) ? r.num : ((1.0-r.num)*@min + r.num*@max)
`
func num32(rng:&RandomNumberGenerator, min=Num32(0.), max=Num32(1.) -> Num32)
return Num32(rng.num(Num(min), Num(max)))
func int(rng:&RandomNumberGenerator, min:Int, max:Int -> Int)
return C_code:Int`
if (likely(((@min.small & @max.small) & 1) != 0)) {
int32_t r = @(rng.int32(Int32(min), Int32(max)));
return I_small(r);
}
int32_t cmp = @(min <> max);
if (cmp > 0)
fail_text(Texts("Random minimum value (", @min, ") is larger than the maximum value (", @max, ")"));
if (cmp == 0) return @min;
mpz_t range_size;
if (@max.small & 1L) mpz_init_set_si(range_size, @max.small >> 2L);
else mpz_init_set(range_size, @max.big);
if (@min.small & 1) {
mpz_t min_mpz;
mpz_init_set_si(min_mpz, @min.small >> 2);
mpz_sub(range_size, range_size, min_mpz);
} else {
mpz_sub(range_size, range_size, @min.big);
}
gmp_randstate_t gmp_rng;
gmp_randinit_default(gmp_rng);
int64_t seed = @(rng.int64());
gmp_randseed_ui(gmp_rng, (unsigned long)seed);
mpz_t r;
mpz_init(r);
mpz_urandomm(r, gmp_rng, range_size);
gmp_randclear(gmp_rng);
Int_t r_int = (mpz_cmpabs_ui(r, BIGGEST_SMALL_INT) <= 0
? ((Int_t){.small = (mpz_get_si(r) << 2L) | 1L})
: ((Int_t){.big = memcpy(GC_MALLOC(sizeof(__mpz_struct)), r, sizeof(__mpz_struct))}));
Int$plus(@min, r_int)
`
func main()
>> bytes := "asdf".utf8()
rng := RandomNumberGenerator.new(bytes)
>> rng.num()
>> rng.num()
>> rng.num()
>> rng.num(0, 100)
>> rng.byte()
>> rng.bytes(20)
cached := rng
>> assert cached == rng
>> assert rng.int64(1, 1000000) == cached.int64(1, 1000000)
seed1 := [Byte(i) for i in 255]
rng1 := RandomNumberGenerator.new(seed1)
# Similar at the start, but different at the end
seed2 := [(if i == 255 then Byte(0) else Byte(i)) for i in 255]
rng2 := RandomNumberGenerator.new(seed2)
assert rng1 != rng2
>> random.bool()
>> random.bool(0.7)
>> random.byte(1, 10)
>> random.int8(0xB, 0xF)
>> random.int16(1, 10)
>> random.int32(1, 10)
>> random.int64(1, 10)
>> random.num(1, 10)
>> random.num32(1, 10)
|