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:
Jacek Sieka 2022-06-15 22:53:40 +02:00 committed by GitHub
parent c4aec8b664
commit f0ea93f77e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 163 additions and 5 deletions

View File

@ -163,4 +163,3 @@ jobs:
nimble --version
nimble install -y --depsOnly
env TEST_LANG="c" nimble test
env TEST_LANG="cpp" nimble test

View File

@ -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:

View File

@ -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)

62
tests/test_rand.nim Normal file
View File

@ -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])