diff --git a/README.md b/README.md index 1cb43b2..8b6b96a 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ In addition, I would like to thank Steve Vinoski and Sverker Eriksson for provid # TODO -* Write Eunit/Common Test cases which verifies that the byte-output of the functions matches the expected output from the NaCl library. +* Write simple correctness unit tests for the different NaCl primitives. +* Introduce `iodata()` and `eqc_gen:largebinary/2` support to test the code base for very large binaries and iodata input. The current test cases mostly concerns themselves about the rather small input. # Overview @@ -54,9 +55,9 @@ Also, while the standard `crypto` bindings in Erlang does a great job at providi ## Scheduler handling -The major problem which a NIF library has to address is the problem of blocking Erlang schedulers. A long-running NIF messes with the scheduler in many ways, the worst of which is breaking it. To avoid this, we have to address long-running work on the NIF. The current method used is to care about the *progress* of the system rather than the *precision*. That is, we guarantee the system will always quickly progress toward a new process, even when running many cryptographic NIFs are run in a given process. However, we don't care about the precision of the progress. A cryptographic NIF may get either a free ride on the reduction budget, or be penalized more than it should be. +To avoid long running NIFs, the library switches to the use of dirty schedulers for large encryption tasks. The target is roughly set at 1/10th of the 1ms budget at 100μs. That is, we have a threshold set such that work taking more than roughly 100μs will invoke the dirty scheduler. We currently care much more about the *progress* of the system rather than the *precision*. We care that another Erlang process gets to use the core so one process is unable to monopolize the scheduler thread. On the other hand, the price that a process pays to use encryption is something we care less about. A process may get a free ride or it may get penalized more than it should if it invokes crypto-code. -The current approach is to switch between blocking NIF calls and dirty scheduler use at a breakoff threshold. Currently, we use the meaurements obtained by assuming a schedule of 100μs is 1/10th of a 1ms budget. And then we set a reduction budget based on these values. 100μs is roughly set at 200 reductions. And to be on the safe side, we multiply these values by two to handle older CPUs as well too. Measurements are obtained by running: +We currently use measurements to obtain some rough figures on the reduction counts different operations take. You can run these measurements by invoking: enacl_timing:all(). @@ -64,9 +65,9 @@ The current "typical modern machine" is: Intel Core i7-4900QM -When running benchmarks, we warm the CPU a bit before conducting the benchmark. Also, the script `benchmark.sh` can be used (altered to your CPU type), to disable the powersave mode of CPUs in order to obtain realistic benchmarks. Do note nothing was done to get a realistic disable of Intel's Turbo Boost functionality and this is a one-core benchmark. +When running benchmarks, we warm the CPU a bit before conducting the benchmark. Also, the script `benchmark.sh` can be used (altered to your CPU type), to disable the powersave mode of CPUs in order to obtain realistic benchmarks. Do note nothing was done to get a realistic disable of Intel's Turbo Boost functionality and this is a one-core benchmark. The numbers given are used as an input to the reduction budget. If a task takes roughly 134μs we assume it costs `134*2` reductions. -I'm interested in machines for which the schedules end up being far off. That is, machines for which the current CPU schedule takes more than 250μs. This is especially interesting for virtual machines. If you are running on very slow machines, you may have to tune the reduction counts and threshold sizes to get good latency on the system. +I'm interested in machines for which the schedules end up being far off. That is, machines for which the current CPU schedule takes more than 250μs. This is especially interesting for virtual machines, and machines with ARM cores. If you are running on very slow machines, you may have to tune the reduction counts and threshold sizes to get good latency on the system. # Testing diff --git a/c_src/enacl_nif.c b/c_src/enacl_nif.c index 4a8a285..4b31333 100644 --- a/c_src/enacl_nif.c +++ b/c_src/enacl_nif.c @@ -40,7 +40,7 @@ ERL_NIF_TERM enif_crypto_verify_16(ErlNifEnv *env, int argc, ERL_NIF_TERM const ErlNifBinary x,y; if ((argc != 2) || (!enif_inspect_binary(env, argv[0], &x)) - || (!enif_inspect_iolist_as_binary(env, argv[1], &y))) { + || (!enif_inspect_binary(env, argv[1], &y))) { return enif_make_badarg(env); } @@ -59,8 +59,8 @@ static ERL_NIF_TERM enif_crypto_verify_32(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { ErlNifBinary x,y; - if ((argc != 2) || (!enif_inspect_iolist_as_binary(env, argv[0], &x)) - || (!enif_inspect_iolist_as_binary(env, argv[1], &y))) { + if ((argc != 2) || (!enif_inspect_binary(env, argv[0], &x)) + || (!enif_inspect_binary(env, argv[1], &y))) { return enif_make_badarg(env); } @@ -130,9 +130,9 @@ ERL_NIF_TERM enif_crypto_box(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[] if ( (argc != 4) || (!enif_inspect_iolist_as_binary(env, argv[0], &padded_msg)) || - (!enif_inspect_iolist_as_binary(env, argv[1], &nonce)) || - (!enif_inspect_iolist_as_binary(env, argv[2], &pk)) || - (!enif_inspect_iolist_as_binary(env, argv[3], &sk))) { + (!enif_inspect_binary(env, argv[1], &nonce)) || + (!enif_inspect_binary(env, argv[2], &pk)) || + (!enif_inspect_binary(env, argv[3], &sk))) { return enif_make_badarg(env); } @@ -164,9 +164,9 @@ ERL_NIF_TERM enif_crypto_box_open(ErlNifEnv *env, int argc, ERL_NIF_TERM const a if ( (argc != 4) || (!enif_inspect_iolist_as_binary(env, argv[0], &padded_ciphertext)) || - (!enif_inspect_iolist_as_binary(env, argv[1], &nonce)) || - (!enif_inspect_iolist_as_binary(env, argv[2], &pk)) || - (!enif_inspect_iolist_as_binary(env, argv[3], &sk))) { + (!enif_inspect_binary(env, argv[1], &nonce)) || + (!enif_inspect_binary(env, argv[2], &pk)) || + (!enif_inspect_binary(env, argv[3], &sk))) { return enif_make_badarg(env); } @@ -234,7 +234,7 @@ ERL_NIF_TERM enif_crypto_sign(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[ if ( (argc != 2) || (!enif_inspect_iolist_as_binary(env, argv[0], &m)) || - (!enif_inspect_iolist_as_binary(env, argv[1], &sk))) { + (!enif_inspect_binary(env, argv[1], &sk))) { return enif_make_badarg(env); } @@ -259,7 +259,7 @@ ERL_NIF_TERM enif_crypto_sign_open(ErlNifEnv *env, int argc, ERL_NIF_TERM const if ( (argc != 2) || (!enif_inspect_iolist_as_binary(env, argv[0], &sm)) || - (!enif_inspect_iolist_as_binary(env, argv[1], &pk))) { + (!enif_inspect_binary(env, argv[1], &pk))) { return enif_make_badarg(env); } @@ -338,8 +338,8 @@ ERL_NIF_TERM enif_crypto_secretbox(ErlNifEnv *env, int argc, ERL_NIF_TERM const if ( (argc != 3) || (!enif_inspect_iolist_as_binary(env, argv[0], &padded_msg)) || - (!enif_inspect_iolist_as_binary(env, argv[1], &nonce)) || - (!enif_inspect_iolist_as_binary(env, argv[2], &key))) { + (!enif_inspect_binary(env, argv[1], &nonce)) || + (!enif_inspect_binary(env, argv[2], &key))) { return enif_make_badarg(env); } @@ -373,8 +373,8 @@ ERL_NIF_TERM enif_crypto_secretbox_open(ErlNifEnv *env, int argc, ERL_NIF_TERM c if ( (argc != 3) || (!enif_inspect_iolist_as_binary(env, argv[0], &padded_ciphertext)) || - (!enif_inspect_iolist_as_binary(env, argv[1], &nonce)) || - (!enif_inspect_iolist_as_binary(env, argv[2], &key))) { + (!enif_inspect_binary(env, argv[1], &nonce)) || + (!enif_inspect_binary(env, argv[2], &key))) { return enif_make_badarg(env); } @@ -414,8 +414,8 @@ ERL_NIF_TERM enif_crypto_stream(ErlNifEnv *env, int argc, ERL_NIF_TERM const arg if ( (argc != 3) || (!enif_get_uint64(env, argv[0], &clen)) || - (!enif_inspect_iolist_as_binary(env, argv[1], &n)) || - (!enif_inspect_iolist_as_binary(env, argv[2], &k))) { + (!enif_inspect_binary(env, argv[1], &n)) || + (!enif_inspect_binary(env, argv[2], &k))) { return enif_make_badarg(env); } @@ -441,8 +441,8 @@ ERL_NIF_TERM enif_crypto_stream_xor(ErlNifEnv *env, int argc, ERL_NIF_TERM const if ( (argc != 3) || (!enif_inspect_iolist_as_binary(env, argv[0], &m)) || - (!enif_inspect_iolist_as_binary(env, argv[1], &n)) || - (!enif_inspect_iolist_as_binary(env, argv[2], &k))) { + (!enif_inspect_binary(env, argv[1], &n)) || + (!enif_inspect_binary(env, argv[2], &k))) { return enif_make_badarg(env); } @@ -468,7 +468,7 @@ ERL_NIF_TERM enif_crypto_auth(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[ if ( (argc != 2) || (!enif_inspect_iolist_as_binary(env, argv[0], &m)) || - (!enif_inspect_iolist_as_binary(env, argv[1], &k))) { + (!enif_inspect_binary(env, argv[1], &k))) { return enif_make_badarg(env); } @@ -491,9 +491,9 @@ ERL_NIF_TERM enif_crypto_auth_verify(ErlNifEnv *env, int argc, ERL_NIF_TERM cons if ( (argc != 3) || - (!enif_inspect_iolist_as_binary(env, argv[0], &a)) || + (!enif_inspect_binary(env, argv[0], &a)) || (!enif_inspect_iolist_as_binary(env, argv[1], &m)) || - (!enif_inspect_iolist_as_binary(env, argv[2], &k))) { + (!enif_inspect_binary(env, argv[2], &k))) { return enif_make_badarg(env); } @@ -517,7 +517,7 @@ ERL_NIF_TERM enif_crypto_onetimeauth(ErlNifEnv *env, int argc, ERL_NIF_TERM cons if ( (argc != 2) || (!enif_inspect_iolist_as_binary(env, argv[0], &m)) || - (!enif_inspect_iolist_as_binary(env, argv[1], &k))) { + (!enif_inspect_binary(env, argv[1], &k))) { return enif_make_badarg(env); } @@ -540,9 +540,9 @@ ERL_NIF_TERM enif_crypto_onetimeauth_verify(ErlNifEnv *env, int argc, ERL_NIF_TE if ( (argc != 3) || - (!enif_inspect_iolist_as_binary(env, argv[0], &a)) || + (!enif_inspect_binary(env, argv[0], &a)) || (!enif_inspect_iolist_as_binary(env, argv[1], &m)) || - (!enif_inspect_iolist_as_binary(env, argv[2], &k))) { + (!enif_inspect_binary(env, argv[2], &k))) { return enif_make_badarg(env); } diff --git a/src/enacl.erl b/src/enacl.erl index fe9da24..f09af10 100644 --- a/src/enacl.erl +++ b/src/enacl.erl @@ -71,20 +71,20 @@ %% To get a grip for these, call `enacl_timing:all/0' on your system. The numbers here are %% described in the README.md file. -define(HASH_SIZE, 32 * 1024). --define(HASH_REDUCTIONS, 104 * 2 * 2). +-define(HASH_REDUCTIONS, 104 * 2). -define(BOX_SIZE, 32 * 1024). --define(BOX_REDUCTIONS, 115 *2 *2). +-define(BOX_REDUCTIONS, 115 * 2). -define(SIGN_SIZE, 16 * 1024). --define(SIGN_REDUCTIONS, 160 * 2 *2). +-define(SIGN_REDUCTIONS, 160 * 2). -define(SECRETBOX_SIZE, 64 * 1024). --define(SECRETBOX_REDUCTIONS, 107 * 2 * 2). --define(SECRETBOX_OPEN_REDUCTIONS, 51 * 2 * 2). +-define(SECRETBOX_REDUCTIONS, 107 * 2). +-define(SECRETBOX_OPEN_REDUCTIONS, 51 * 2). -define(STREAM_SIZE, 128 * 1024). --define(STREAM_REDUCTIONS, 120 * 2 * 2). +-define(STREAM_REDUCTIONS, 120 * 2). -define(AUTH_SIZE, 32 * 1024). --define(AUTH_REDUCTIONS, 102 * 2 * 2). +-define(AUTH_REDUCTIONS, 102 * 2). -define(ONETIME_AUTH_SIZE, 128 * 1024). --define(ONETIME_AUTH_REDUCTIONS, 105 * 2 * 2). +-define(ONETIME_AUTH_REDUCTIONS, 105 * 2). %% Count reductions and number of scheduler yields for Fun. Fun is assumed %% to be one of the above exor variants. @@ -118,8 +118,10 @@ reds(Fun) -> hash(Bin) -> case iolist_size(Bin) of - K when K =< ?HASH_SIZE -> bump(enacl_nif:crypto_hash_b(Bin), ?HASH_REDUCTIONS, ?HASH_SIZE, K); - _ -> enacl_nif:crypto_hash(Bin) + K when K =< ?HASH_SIZE -> + bump(enacl_nif:crypto_hash_b(Bin), ?HASH_REDUCTIONS, ?HASH_SIZE, K); + _ -> + enacl_nif:crypto_hash(Bin) end. %% @doc verify_16/2 implements constant time 16-byte binary() verification @@ -258,7 +260,7 @@ sign(M, SK) -> sign_open(SM, PK) -> case iolist_size(SM) of K when K =< ?SIGN_SIZE -> - R = case enacl_nif:crypto_sign_open(SM, PK) of + R = case enacl_nif:crypto_sign_open_b(SM, PK) of M when is_binary(M) -> {ok, M}; {error, Err} -> {error, Err} end, @@ -371,7 +373,15 @@ stream(_, _, _) -> error(badarg). Key :: binary(), CipherText :: binary(). stream_xor(Msg, Nonce, Key) -> - enacl_nif:crypto_stream_xor(Msg, Nonce, Key). + case iolist_size(Msg) of + K when K =< ?STREAM_SIZE -> + bump(enacl_nif:crypto_stream_xor_b(Msg, Nonce, Key), + ?STREAM_REDUCTIONS, + ?STREAM_SIZE, + K); + _ -> + enacl_nif:crypto_stream_xor(Msg, Nonce, Key) + end. %% @doc auth_key_size/0 returns the byte-size of the authentication key %% @end @@ -389,10 +399,16 @@ auth_size() -> enacl_nif:crypto_auth_BYTES(). %% @end -spec auth(Msg, Key) -> Authenticator when - Msg :: binary(), + Msg :: iodata(), Key :: binary(), Authenticator :: binary(). -auth(Msg, Key) -> enacl_nif:crypto_auth(Msg, Key). +auth(Msg, Key) -> + case iolist_size(Msg) of + K when K =< ?AUTH_SIZE -> + bump(enacl_nif:crypto_auth_b(Msg, Key), ?AUTH_REDUCTIONS, ?AUTH_SIZE, K); + _ -> + enacl_nif:crypto_auth(Msg, Key) + end. %% @doc auth_verify/3 verifies an authenticator for a message %% Given an `Authenticator', a `Msg' and a `Key'; verify that the MAC for the pair `{Msg, Key}' is really `Authenticator'. Returns @@ -401,20 +417,38 @@ auth(Msg, Key) -> enacl_nif:crypto_auth(Msg, Key). -spec auth_verify(Authenticator, Msg, Key) -> boolean() when Authenticator :: binary(), - Msg :: binary(), + Msg :: iodata(), Key :: binary(). -auth_verify(A, M, K) -> enacl_nif:crypto_auth_verify(A, M, K). +auth_verify(A, M, K) -> + case iolist_size(M) of + K when K =< ?AUTH_SIZE -> + bump(enacl_nif:crypto_auth_verify_b(A, M, K), + ?AUTH_REDUCTIONS, + ?AUTH_SIZE, + K); + _ -> + enacl_nif:crypto_auth_verify(A, M, K) + end. %% @doc onetime_auth/2 produces a ONE-TIME authenticator for a message %% This function works like {@link auth/2} except that the key must not be used again for subsequent messages. That is, the pair -%% `{Msg, Key}' is unique and only to be used once. The advantage is primarily faster execution. +%% `{Msg, Key}' is unique and only to be used once. The advantage is noticably faster execution. %% @end -spec onetime_auth(Msg, Key) -> Authenticator when - Msg :: binary(), + Msg :: iodata(), Key :: binary(), Authenticator :: binary(). -onetime_auth(Msg, Key) -> enacl_nif:crypto_onetimeauth(Msg, Key). +onetime_auth(Msg, Key) -> + case iolist_size(Msg) of + K when K =< ?ONETIME_AUTH_SIZE -> + bump(enacl_nif:crypto_onetimeauth_b(Msg, Key), + ?ONETIME_AUTH_REDUCTIONS, + ?ONETIME_AUTH_SIZE, + K); + _ -> + enacl_nif:crypto_onetimeauth(Msg, Key) + end. %% @doc onetime_auth_verify/3 verifies an ONE-TIME authenticator for a message %% Given an `Authenticator', a `Msg' and a `Key'; verify that the MAC for the pair `{Msg, Key}' is really `Authenticator'. Returns @@ -424,9 +458,18 @@ onetime_auth(Msg, Key) -> enacl_nif:crypto_onetimeauth(Msg, Key). -spec onetime_auth_verify(Authenticator, Msg, Key) -> boolean() when Authenticator :: binary(), - Msg :: binary(), + Msg :: iodata(), Key :: binary(). -onetime_auth_verify(A, M, K) -> enacl_nif:crypto_onetimeauth_verify(A, M, K). +onetime_auth_verify(A, M, K) -> + case iolist_size(M) of + K when K =< ?ONETIME_AUTH_SIZE -> + bump(enacl_nif:crypto_onetimeauth_verify_b(A, M, K), + ?ONETIME_AUTH_REDUCTIONS, + ?ONETIME_AUTH_SIZE, + K); + _ -> + enacl_nif:crypto_onetimeauth_verify(A, M, K) + end. %% @doc onetime_auth_size/0 returns the number of bytes of the one-time authenticator %% @end