logos-storage-docs-obsidian/10 Notes/How to generate a random number using BearSSL.md
2025-07-02 19:55:28 +02:00

1.3 KiB

Extracted from nim-libp2p (libp2p/crypto/crypto.nim) and codex/rng.nim in nim-codex so that it does not depend on libp2p anymore:

{.push raises: [].}

import bearssl/[rand, hash]

proc newRng*(): ref HmacDrbgContext =
  # You should only create one instance of the RNG per application / library
  # Ref is used so that it can be shared between components
  # TODO consider moving to bearssl
  var seeder = prngSeederSystem(nil)
  if seeder == nil:
    return nil

  var rng = (ref HmacDrbgContext)()
  hmacDrbgInit(rng[], addr sha256Vtable, nil, 0)
  if seeder(addr rng.vtable) == 0:
    return nil
  rng

type
  RngSampleError = object of CatchableError
  Rng* = ref HmacDrbgContext

var rng {.threadvar.}: Rng

proc instance*(t: type Rng): Rng =
  if rng.isNil:
    rng = newRng()
  rng

# Random helpers: similar as in stdlib, but with HmacDrbgContext rng
# TODO: Move these somewhere else?
const randMax = 18_446_744_073_709_551_615'u64

proc rand*(rng: Rng, max: Natural): int =
  if max == 0:
    return 0

  while true:
    let x = rng[].generate(uint64)
    if x < randMax - (randMax mod (uint64(max) + 1'u64)): # against modulo bias
      return int(x mod (uint64(max) + 1'u64))