rand: convenience API (#28)
Helpers to generate random numbers, including simple Nim types * disable C++ tests * there are too many `const` vs `non-const` issues to run these tests - C++ never actually worked * add generateBytes to make fresh seqs Co-authored-by: Mamy Ratsimbazafy <mamy_github@numforge.co> Co-authored-by: Etan Kissling <etan@status.im>
This commit is contained in:
parent
c4aec8b664
commit
f0ea93f77e
|
@ -163,4 +163,3 @@ jobs:
|
|||
nimble --version
|
||||
nimble install -y --depsOnly
|
||||
env TEST_LANG="c" nimble test
|
||||
env TEST_LANG="cpp" nimble test
|
||||
|
|
|
@ -36,6 +36,11 @@ In general, the mappings follow the conventions of the original BearSSL library
|
|||
* `uint` used instead of `csize_t` - these are the same type in Nim, but spelled more conveniently
|
||||
* Canonical nim code will have to be careful when converting existing `int` lengths, looking out for out-of-range values
|
||||
|
||||
In addition to the raw `C`-like api, convenience functions are added where applicable - these follow a similar set of conventions:
|
||||
|
||||
* named after the function they simplify, but take advantage of types and overload support in Nim
|
||||
* help turn pointers and bytes into Nim types
|
||||
|
||||
## Installation
|
||||
|
||||
You can install the developement version of the library through nimble with the following command:
|
||||
|
|
100
bearssl/rand.nim
100
bearssl/rand.nim
|
@ -1,8 +1,100 @@
|
|||
import
|
||||
./abi/bearssl_rand
|
||||
typetraits,
|
||||
./abi/[bearssl_hash, bearssl_rand]
|
||||
|
||||
export bearssl_rand
|
||||
|
||||
func hmacDrbgGenerate*(ctx: var HmacDrbgContext, output: var openArray[byte]) =
|
||||
if output.len > 0:
|
||||
hmacDrbgGenerate(ctx, addr output[0], uint output.len)
|
||||
# About types used in helpers:
|
||||
# `bool` types are problematic because because they only use one bit of the
|
||||
# entire byte - a similar problem occurs with `object` types with alignment
|
||||
# gaps - `supportsCopyMem` is wrong here, we should be using `supportsMemCmp` or
|
||||
# something similar that takes into account these issues, but alas, there's no
|
||||
# such trait as of now
|
||||
|
||||
proc init*[S](T: type HmacDrbgContext, seed: openArray[S]): HmacDrbgContext =
|
||||
## Create a new randomness context with the given seed - typically, a single
|
||||
## instance per thread should be created.
|
||||
##
|
||||
## The seed can later be topped up with `update`.
|
||||
static: doAssert supportsCopyMem(S) and sizeof(S) > 0 and S isnot bool
|
||||
|
||||
if seed.len == 0:
|
||||
hmacDrbgInit(result, addr bearssl_hash.sha256Vtable, nil, 0)
|
||||
else:
|
||||
# In theory the multiplication can overflow, but practically we can't
|
||||
# allocate that much memory, so it won't
|
||||
hmacDrbgInit(
|
||||
result, addr sha256Vtable, unsafeAddr seed[0], uint seed.len * sizeof(S))
|
||||
|
||||
proc new*(T: type HmacDrbgContext): ref HmacDrbgContext =
|
||||
## Create a new randomness context intended to be shared between randomness
|
||||
## consumers - typically, a single instance per thread should be created.
|
||||
##
|
||||
## The context is seeded with randomness from the OS / system.
|
||||
## Returns `nil` if the OS / system has no randomness API.
|
||||
let seeder = prngSeederSystem(nil)
|
||||
if seeder == nil:
|
||||
return nil
|
||||
|
||||
let rng = (ref HmacDrbgContext)()
|
||||
hmacDrbgInit(rng[], addr sha256Vtable, nil, 0)
|
||||
|
||||
if seeder(addr rng.vtable) == 0:
|
||||
return nil
|
||||
|
||||
rng
|
||||
|
||||
func generate*(ctx: var HmacDrbgContext, v: var auto) =
|
||||
## Fill `v` with random data - `v` must be a simple type
|
||||
static: doAssert supportsCopyMem(type v)
|
||||
|
||||
when sizeof(v) > 0:
|
||||
when v is bool:
|
||||
# `bool` would result in a heavily biased value because >0 == true
|
||||
var tmp: byte
|
||||
hmacDrbgGenerate(ctx, addr tmp, uint sizeof(tmp))
|
||||
v = (tmp and 1'u8) == 1
|
||||
else:
|
||||
hmacDrbgGenerate(ctx, addr v, uint sizeof(v))
|
||||
|
||||
func generate*[V](ctx: var HmacDrbgContext, v: var openArray[V]) =
|
||||
## Fill `v` with random data - `T` must be a simple type
|
||||
static: doAssert supportsCopyMem(V) and sizeof(V) > 0
|
||||
|
||||
when V is bool:
|
||||
for b in v.mitems:
|
||||
ctx.generate(b)
|
||||
else:
|
||||
if v.len > 0:
|
||||
# In theory the multiplication can overflow, but practically we can't
|
||||
# allocate that much memory, so it won't
|
||||
hmacDrbgGenerate(ctx, addr v[0], uint v.len * sizeof(V))
|
||||
|
||||
template generate*[V](ctx: var HmacDrbgContext, v: var seq[V]) =
|
||||
generate(ctx, v.toOpenArray(0, v.high()))
|
||||
|
||||
func generateBytes*(ctx: var HmacDrbgContext, n: int): seq[byte] =
|
||||
# https://github.com/nim-lang/Nim/issues/19357
|
||||
if n > 0:
|
||||
result = newSeqUninitialized[byte](n)
|
||||
ctx.generate(result)
|
||||
|
||||
func generate*(ctx: var HmacDrbgContext, T: type): T {.noinit.} =
|
||||
## Create a new instance of `T` filled with random data - `T` must be
|
||||
## a simple type
|
||||
ctx.generate(result)
|
||||
|
||||
func update*[S](ctx: var HmacDrbgContext, seed: openArray[S]) =
|
||||
## Update context with additional seed data
|
||||
static: doAssert supportsCopyMem(S) and sizeof(S) > 0 and S isnot bool
|
||||
|
||||
if seed.len > 0:
|
||||
# In theory the multiplication can overflow, but practically we can't
|
||||
# allocate that much memory, so it won't
|
||||
hmacDrbgUpdate(ctx, unsafeAddr seed[0], uint seed.len * sizeof(S))
|
||||
|
||||
# Convenience helpers using bearssl naming
|
||||
|
||||
template hmacDrbgGenerate*(
|
||||
ctx: var HmacDrbgContext, output: var openArray[byte]) =
|
||||
generate(ctx, output)
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
import
|
||||
unittest2,
|
||||
../bearssl/rand
|
||||
|
||||
{.used.}
|
||||
|
||||
suite "random":
|
||||
test "simple random ops":
|
||||
# Some of these tests may end up triggering false fails, but given their
|
||||
# probability, should be fine
|
||||
|
||||
let rng = HmacDrbgContext.new()
|
||||
|
||||
var v: array[1024, byte]
|
||||
rng[].generate(v)
|
||||
|
||||
let v2 = rng[].generate(array[1024, byte])
|
||||
check:
|
||||
v != default(array[1024, byte]) # probable
|
||||
v2 != default(array[1024, byte]) # probable
|
||||
|
||||
for i in 0..<1000:
|
||||
doAssert cast[int](rng[].generate(bool)) in [0, 1]
|
||||
|
||||
var bools: array[64 * 1024, bool]
|
||||
rng[].generate(bools)
|
||||
|
||||
check:
|
||||
true in bools # probable
|
||||
false in bools # probable
|
||||
|
||||
var
|
||||
xxx = newSeq[int](1024)
|
||||
yyy = xxx
|
||||
rng[].generate(xxx)
|
||||
check:
|
||||
xxx != yyy # probable
|
||||
|
||||
test "seed":
|
||||
for seed in [@[byte 0], @[byte 1], @[byte 1, 1], @[byte 42, 13, 37]]:
|
||||
var
|
||||
rng = HmacDrbgContext.init(seed)
|
||||
rng2 = HmacDrbgContext.init(seed)
|
||||
|
||||
check:
|
||||
rng.generate(uint64) == rng2.generate(uint64)
|
||||
|
||||
for seed in [@[0], @[1], @[1, 1], @[42, 1337, -5]]:
|
||||
var
|
||||
rng = HmacDrbgContext.init(seed)
|
||||
rng2 = HmacDrbgContext.init(seed)
|
||||
|
||||
check:
|
||||
rng.generate(uint64) == rng2.generate(uint64)
|
||||
|
||||
test "antiseed":
|
||||
var
|
||||
rng = HmacDrbgContext.init([0])
|
||||
rng2 = HmacDrbgContext.init([1])
|
||||
|
||||
check:
|
||||
rng.generate(array[1024, byte]) != rng2.generate(array[1024, byte])
|
Loading…
Reference in New Issue