diff --git a/CHANGELOG.md b/CHANGELOG.md index cb0bec4..4b0d3c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +- Implement enacl:randombytes_int32/0. Returns a random 32bit unsigned + integer, by means of the underlying random source. +- Implement enacl:randombytes_uniform/1. Takes up to a 32bit unsigned + integer and produces a uniform integer in the range [0..N). Note + that the implementation avoids the typical non-uniformness which + would be present on a modulus operation on the nearest power-of-two + integer. +- Added a nix shell for easier development ## [0.17.2] diff --git a/c_src/enacl_nif.c b/c_src/enacl_nif.c index 4eb5d2b..870478c 100644 --- a/c_src/enacl_nif.c +++ b/c_src/enacl_nif.c @@ -1029,6 +1029,31 @@ static ERL_NIF_TERM enif_randombytes(ErlNifEnv *env, int argc, return enif_make_binary(env, &result); } +static ERL_NIF_TERM enif_randombytes_int32(ErlNifEnv *env, int argc, + ERL_NIF_TERM const argv[]) { + ErlNifUInt64 result; + + if (argc != 0) { + return enif_make_badarg(env); + } + + result = randombytes_random(); + return enif_make_uint64(env, result); +} + +static ERL_NIF_TERM enif_randombytes_uniform(ErlNifEnv *env, int argc, + ERL_NIF_TERM const argv[]) { + unsigned upper_bound; + ErlNifUInt64 result; + + if ((argc != 1) || (!enif_get_uint(env, argv[0], &upper_bound))) { + return enif_make_badarg(env); + } + + result = randombytes_uniform(upper_bound); + return enif_make_uint64(env, result); +} + /* Key exchange */ static ERL_NIF_TERM enif_crypto_kx_SECRETKEYBYTES(ErlNifEnv *env, int argc, @@ -1741,7 +1766,14 @@ static ErlNifFunc nif_funcs[] = { {"crypto_sign_ed25519_SECRETKEYBYTES", 0, enif_crypto_sign_ed25519_SECRETKEYBYTES}, + // Linux might block here if early in the boot sequence, so get it off the + // main scheduler. Otherwise, it it would probably be fine to run on the + // main scheduler. This plays it safe, albeit with a performance hit. erl_nif_dirty_job_cpu_bound_macro("randombytes", 1, enif_randombytes), + erl_nif_dirty_job_cpu_bound_macro("randombytes_int32", 0, + enif_randombytes_int32), + erl_nif_dirty_job_cpu_bound_macro("randombytes_uniform", 1, + enif_randombytes_uniform), erl_nif_dirty_job_cpu_bound_macro("crypto_kx_keypair", 0, enif_crypto_kx_keypair), diff --git a/src/enacl.erl b/src/enacl.erl index 15ea2c2..d1bd28d 100644 --- a/src/enacl.erl +++ b/src/enacl.erl @@ -114,7 +114,7 @@ hash/1, verify_16/2, verify_32/2, - + %% No Tests! unsafe_memzero/1 ]). @@ -122,7 +122,9 @@ %% Randomness -export([ %% EQC - randombytes/1 + randombytes/1, + randombytes_int32/0, + randombytes_uniform/1 ]). %%% Specific primitives @@ -204,6 +206,9 @@ -define(CRYPTO_GENERICHASH_KEYBYTES_MAX, 64). -define(CRYPTO_GENERICHASH_KEYBYTES, 32). +%% Size limits +-define(MAX_32BIT_INT, 1 bsl 32). + %% @doc Verify makes sure the constants defined in libsodium matches ours verify() -> true = equals(binary:copy(<<0>>, enacl_nif:crypto_box_ZEROBYTES()), ?P_ZEROBYTES), @@ -1119,6 +1124,18 @@ aead_chacha20poly1305_MESSAGEBYTES_MAX() -> randombytes(N) -> enacl_nif:randombytes(N). +%% @doc randombytes_int32/0 produces an integer in the 32bit range +%% @end +-spec randombytes_int32() -> integer(). +randombytes_int32() -> + enacl_nif:randombytes_int32(). + +%% @doc randombytes_uniform/1 produces a random integer in the space [0..N) +%% That is with the upper bound excluded. Fails for integers above 32bit size +%% @end +randombytes_uniform(N) when N < ?MAX_32BIT_INT -> + enacl_nif:randombytes_uniform(N). + %% Helpers %% @doc bump/4 bumps a reduction budget linearly before returning the result diff --git a/src/enacl_nif.erl b/src/enacl_nif.erl index 798ef53..7b48252 100644 --- a/src/enacl_nif.erl +++ b/src/enacl_nif.erl @@ -153,7 +153,9 @@ %% Access to the RNG -export([ - randombytes/1 + randombytes/1, + randombytes_int32/0, + randombytes_uniform/1 ]). %% Undocumented features :> @@ -296,5 +298,7 @@ crypto_kx_PUBLICKEYBYTES() -> erlang:nif_error(nif_not_loaded). crypto_kx_SECRETKEYBYTES() -> erlang:nif_error(nif_not_loaded). randombytes(_RequestedSize) -> erlang:nif_error(nif_not_loaded). +randombytes_int32() -> erlang:nif_error(nif_not_loaded). +randombytes_uniform(_UpperBound) -> erlang:nif_error(nif_not_loaded). scramble_block_16(_Block, _Key) -> erlang:nif_error(nif_not_loaded).