From a5dab7acf0a8601d00fab5a901e1318d3db6fc8c Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Thu, 1 Mar 2018 08:41:53 +0100 Subject: [PATCH 1/4] Whitespace --- c_src/enacl_nif.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/c_src/enacl_nif.c b/c_src/enacl_nif.c index 1046af7..a6e54b5 100644 --- a/c_src/enacl_nif.c +++ b/c_src/enacl_nif.c @@ -35,7 +35,7 @@ int enif_crypto_load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info) { if( !(generichash_state_type = enif_open_resource_type(env, NULL, CRYPTO_GENERICHASH_STATE_RESOURCE, NULL, ERL_NIF_RT_CREATE, NULL)) ) { return -1; } - + return sodium_init(); } @@ -1261,7 +1261,7 @@ ERL_NIF_TERM enif_crypto_pwhash(ErlNifEnv *env, int argc, ERL_NIF_TERM const arg ERL_NIF_TERM ok = enif_make_atom(env, ATOM_OK); ERL_NIF_TERM ret = enif_make_binary(env, &h); - + return enif_make_tuple2(env, ok, ret); } @@ -1289,7 +1289,7 @@ ERL_NIF_TERM enif_crypto_pwhash_str(ErlNifEnv *env, int argc, ERL_NIF_TERM const ERL_NIF_TERM ok = enif_make_atom(env, ATOM_OK); ERL_NIF_TERM ret = enif_make_binary(env, &h); - + return enif_make_tuple2(env, ok, ret); } @@ -1308,7 +1308,7 @@ ERL_NIF_TERM enif_crypto_pwhash_str_verify(ErlNifEnv *env, int argc, ERL_NIF_TER if( crypto_pwhash_str_verify((char *)h.data, (char *)p.data, p.size) != 0) { /* wrong password */ retVal = enif_make_atom(env, ATOM_FALSE); - } + } return retVal; } @@ -1384,10 +1384,10 @@ ERL_NIF_TERM enif_crypto_generichash(ErlNifEnv *env, int argc, ERL_NIF_TERM cons enif_release_binary(&hash); return nacl_error_tuple(env, "hash_error"); } - + ERL_NIF_TERM ok = enif_make_atom(env, ATOM_OK); ERL_NIF_TERM ret = enif_make_binary(env, &hash); - + return enif_make_tuple2(env, ok, ret); } @@ -1428,8 +1428,8 @@ ERL_NIF_TERM enif_crypto_generichash_init(ErlNifEnv *env, int argc, ERL_NIF_TERM if( 0 != crypto_generichash_init(state, k, key.size, hashSize) ) { return nacl_error_tuple(env, "hash_init_error"); } - - + + // Create return values ERL_NIF_TERM e1 = enif_make_atom(env, "hashstate"); ERL_NIF_TERM e2 = argv[0]; @@ -1475,7 +1475,7 @@ ERL_NIF_TERM enif_crypto_generichash_update(ErlNifEnv *env, int argc, ERL_NIF_TE ERL_NIF_TERM e1 = enif_make_atom(env, "hashstate"); ERL_NIF_TERM e2 = argv[0]; ERL_NIF_TERM e3 = enif_make_resource(env, state); - + // return a tuple return enif_make_tuple3(env, e1, e2, e3); } @@ -1483,7 +1483,7 @@ ERL_NIF_TERM enif_crypto_generichash_update(ErlNifEnv *env, int argc, ERL_NIF_TE static ERL_NIF_TERM enif_crypto_generichash_final(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { ErlNifBinary hash; - + unsigned hashSize; crypto_generichash_state *state; @@ -1511,10 +1511,10 @@ ERL_NIF_TERM enif_crypto_generichash_final(ErlNifEnv *env, int argc, ERL_NIF_TER enif_release_binary(&hash); return nacl_error_tuple(env, "hash_error"); } - + ERL_NIF_TERM ok = enif_make_atom(env, ATOM_OK); ERL_NIF_TERM ret = enif_make_binary(env, &hash); - + return enif_make_tuple2(env, ok, ret); } @@ -1529,7 +1529,7 @@ static ErlNifFunc nif_funcs[] = { erl_nif_dirty_job_cpu_bound_macro("crypto_box_keypair", 0, enif_crypto_box_keypair), - + erl_nif_dirty_job_cpu_bound_macro("crypto_box", 4, enif_crypto_box), erl_nif_dirty_job_cpu_bound_macro("crypto_box_open", 4, enif_crypto_box_open), @@ -1634,7 +1634,7 @@ static ErlNifFunc nif_funcs[] = { {"crypto_generichash_init", 2, enif_crypto_generichash_init}, {"crypto_generichash_update", 3, enif_crypto_generichash_update}, {"crypto_generichash_final", 2, enif_crypto_generichash_final} - + }; ERL_NIF_INIT(enacl_nif, nif_funcs, enif_crypto_load, NULL, NULL, NULL); From 9dfbe8cc90aca1e70a934ec8854ac689cb073853 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Thu, 1 Mar 2018 08:42:31 +0100 Subject: [PATCH 2/4] Expose AEAD ChaCha20 Poly1305 functionality Note: We expose the modern IETF version but still name it plain aead_chacha20poly1305. --- c_src/enacl_nif.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++ src/enacl.erl | 62 +++++++++++++++++++++++++++++ src/enacl_nif.erl | 14 +++++++ 3 files changed, 175 insertions(+) diff --git a/c_src/enacl_nif.c b/c_src/enacl_nif.c index a6e54b5..a6d422b 100644 --- a/c_src/enacl_nif.c +++ b/c_src/enacl_nif.c @@ -1313,6 +1313,98 @@ ERL_NIF_TERM enif_crypto_pwhash_str_verify(ErlNifEnv *env, int argc, ERL_NIF_TER return retVal; } +/* + * AEAD ChaCha20 Poly1305 + */ +static +ERL_NIF_TERM enif_crypto_aead_chacha20poly1305_KEYBYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + return enif_make_int64(env, crypto_aead_chacha20poly1305_ietf_KEYBYTES); +} + +static +ERL_NIF_TERM enif_crypto_aead_chacha20poly1305_NPUBBYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + return enif_make_int64(env, crypto_aead_chacha20poly1305_ietf_NPUBBYTES); +} + +static +ERL_NIF_TERM enif_crypto_aead_chacha20poly1305_ABYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + return enif_make_int64(env, crypto_aead_chacha20poly1305_ietf_ABYTES); +} + +static +ERL_NIF_TERM enif_crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + return enif_make_int64(env, crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX); +} + +static +ERL_NIF_TERM enif_crypto_aead_chacha20poly1305_encrypt(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + ERL_NIF_TERM result; + ErlNifBinary key, nonce, ad, message, ciphertext; + + if ((argc != 4) || (!enif_inspect_binary(env, argv[0], &key)) + || (!enif_inspect_binary(env, argv[1], &nonce)) + || (!enif_inspect_binary(env, argv[2], &ad)) + || (!enif_inspect_binary(env, argv[3], &message)) + || (key.size != crypto_aead_chacha20poly1305_ietf_KEYBYTES) + || (nonce.size != crypto_aead_chacha20poly1305_ietf_NPUBBYTES)) { + return enif_make_badarg(env); + } + + do + { + if (!enif_alloc_binary(message.size + crypto_aead_chacha20poly1305_ietf_ABYTES, &ciphertext)) { + result = nacl_error_tuple(env, "alloc_failed"); + continue; + } + + if (crypto_aead_chacha20poly1305_ietf_encrypt(ciphertext.data, NULL, message.data, message.size, + ad.data, ad.size, NULL, nonce.data, key.data) < 0) { + result = nacl_error_tuple(env, "aead_chacha20poly1305_ietf_encrypt_failed"); + continue; + } + + result = enif_make_binary(env, &ciphertext); + } while (0); + + return result; + +} + +static +ERL_NIF_TERM enif_crypto_aead_chacha20poly1305_decrypt(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + ERL_NIF_TERM result; + ErlNifBinary key, nonce, ad, message, ciphertext; + + if ((argc != 4) || (!enif_inspect_binary(env, argv[0], &key)) + || (!enif_inspect_binary(env, argv[1], &nonce)) + || (!enif_inspect_binary(env, argv[2], &ad)) + || (!enif_inspect_binary(env, argv[3], &ciphertext)) + || (ciphertext.size < crypto_aead_chacha20poly1305_ietf_ABYTES) + || (key.size != crypto_aead_chacha20poly1305_ietf_KEYBYTES) + || (nonce.size != crypto_aead_chacha20poly1305_ietf_NPUBBYTES)) { + return enif_make_badarg(env); + } + + do + { + if (!enif_alloc_binary(ciphertext.size - crypto_aead_chacha20poly1305_ietf_ABYTES, &message)) { + result = nacl_error_tuple(env, "alloc_failed"); + continue; + } + + if (crypto_aead_chacha20poly1305_ietf_decrypt(message.data, NULL, NULL, ciphertext.data, ciphertext.size, + ad.data, ad.size, nonce.data, key.data) < 0) { + result = nacl_error_tuple(env, "aead_chacha20poly1305_ietf_decrypt_failed"); + continue; + } + + result = enif_make_binary(env, &message); + } while (0); + + return result; + +} + /* * Generic hash */ @@ -1624,6 +1716,13 @@ static ErlNifFunc nif_funcs[] = { {"scramble_block_16", 2, enif_scramble_block_16}, + {"crypto_aead_chacha20poly1305_KEYBYTES", 0, enif_crypto_aead_chacha20poly1305_KEYBYTES}, + {"crypto_aead_chacha20poly1305_NPUBBYTES", 0, enif_crypto_aead_chacha20poly1305_NPUBBYTES}, + {"crypto_aead_chacha20poly1305_ABYTES", 0, enif_crypto_aead_chacha20poly1305_ABYTES}, + {"crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX", 0, enif_crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX}, + erl_nif_dirty_job_cpu_bound_macro("crypto_aead_chacha20poly1305_encrypt", 4, enif_crypto_aead_chacha20poly1305_encrypt), + erl_nif_dirty_job_cpu_bound_macro("crypto_aead_chacha20poly1305_decrypt", 4, enif_crypto_aead_chacha20poly1305_decrypt), + {"crypto_generichash_BYTES", 0, enif_crypto_generichash_BYTES}, {"crypto_generichash_BYTES_MIN", 0, enif_crypto_generichash_BYTES_MIN}, {"crypto_generichash_BYTES_MAX", 0, enif_crypto_generichash_BYTES_MAX}, diff --git a/src/enacl.erl b/src/enacl.erl index 76e834c..10ecc1c 100644 --- a/src/enacl.erl +++ b/src/enacl.erl @@ -54,6 +54,13 @@ stream_chacha20/3, stream_chacha20_xor/3, + aead_chacha20poly1305_encrypt/4, + aead_chacha20poly1305_decrypt/4, + aead_chacha20poly1305_KEYBYTES/0, + aead_chacha20poly1305_NONCEBYTES/0, + aead_chacha20poly1305_ABYTES/0, + aead_chacha20poly1305_MESSAGEBYTES_MAX/0, + stream_key_size/0, stream_nonce_size/0, stream/3, @@ -997,7 +1004,62 @@ kx_public_key_size() -> kx_secret_key_size() -> enacl_nif:crypto_kx_SECRETKEYBYTES(). +%% AEAD ChaCha20 Poly1305 +%% ---------------------- +%% @doc aead_chacha20poly1305_encrypt/4 encrypts `Message` with additional data +%% `AD` using `Key` and `Nonce`. Returns the encrypted message followed by +%% `aead_chacha20poly1305_ABYTES/0` bytes of MAC. +%% @end +-spec aead_chacha20poly1305_encrypt(Key, Nonce, AD, Msg) -> binary() | {error, term()} + when Key :: binary(), + Nonce :: pos_integer(), + AD :: binary(), + Msg :: binary(). +aead_chacha20poly1305_encrypt(Key, Nonce, AD, Msg) -> + NonceBin = <<0:32, Nonce:64/little-unsigned-integer>>, + enacl_nif:crypto_aead_chacha20poly1305_encrypt(Key, NonceBin, AD, Msg). +%% @doc aead_chacha20poly1305_decrypt/4 decrypts ciphertext `CT` with additional +%% data `AD` using `Key` and `Nonce`. Note: `CipherText` should contain +%% `aead_chacha20poly1305_ABYTES/0` bytes that is the MAC. Returns the decrypted +%% message. +%% @end +-spec aead_chacha20poly1305_decrypt(Key, Nonce, AD, CT) -> binary() | {error, term()} + when Key :: binary(), + Nonce :: pos_integer(), + AD :: binary(), + CT :: binary(). +aead_chacha20poly1305_decrypt(Key, Nonce, AD, CT) -> + NonceBin = <<0:32, Nonce:64/little-unsigned-integer>>, + enacl_nif:crypto_aead_chacha20poly1305_decrypt(Key, NonceBin, AD, CT). + +%% @doc aead_chacha20poly1305_KEYBYTES/0 returns the number of bytes +%% of the key used in AEAD ChaCha20 Poly1305 encryption/decryption. +%% @end +-spec aead_chacha20poly1305_KEYBYTES() -> pos_integer(). +aead_chacha20poly1305_KEYBYTES() -> + enacl_nif:crypto_aead_chacha20poly1305_KEYBYTES(). + +%% @doc aead_chacha20poly1305_NONCEBYTES/0 returns the number of bytes +%% of the Nonce in AEAD ChaCha20 Poly1305 encryption/decryption. +%% @end +-spec aead_chacha20poly1305_NONCEBYTES() -> pos_integer(). +aead_chacha20poly1305_NONCEBYTES() -> + enacl_nif:crypto_aead_chacha20poly1305_NPUBBYTES(). + +%% @doc aead_chacha20poly1305_ABYTES/0 returns the number of bytes +%% of the MAC in AEAD ChaCha20 Poly1305 encryption/decryption. +%% @end +-spec aead_chacha20poly1305_ABYTES() -> pos_integer(). +aead_chacha20poly1305_ABYTES() -> + enacl_nif:crypto_aead_chacha20poly1305_ABYTES(). + +%% @doc aead_chacha20poly1305_MESSAGEBYTES_MAX/0 returns the max number of bytes +%% allowed in a message in AEAD ChaCha20 Poly1305 encryption/decryption. +%% @end +-spec aead_chacha20poly1305_MESSAGEBYTES_MAX() -> pos_integer(). +aead_chacha20poly1305_MESSAGEBYTES_MAX() -> + enacl_nif:crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX(). %% Obtaining random bytes diff --git a/src/enacl_nif.erl b/src/enacl_nif.erl index 2b0f85b..8e807b1 100644 --- a/src/enacl_nif.erl +++ b/src/enacl_nif.erl @@ -67,6 +67,13 @@ crypto_stream_xor/3, crypto_stream_xor_b/3, + crypto_aead_chacha20poly1305_encrypt/4, + crypto_aead_chacha20poly1305_decrypt/4, + crypto_aead_chacha20poly1305_KEYBYTES/0, + crypto_aead_chacha20poly1305_NPUBBYTES/0, + crypto_aead_chacha20poly1305_ABYTES/0, + crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX/0, + crypto_auth_BYTES/0, crypto_auth_KEYBYTES/0, @@ -240,6 +247,13 @@ crypto_stream_b(_Bytes, _Nonce, _Key) -> erlang:nif_error(nif_not_loaded). crypto_stream_xor(_M, _Nonce, _Key) -> erlang:nif_error(nif_not_loaded). crypto_stream_xor_b(_M, _Nonce, _Key) -> erlang:nif_error(nif_not_loaded). +crypto_aead_chacha20poly1305_encrypt(_Key, _Nonce, _AD, _Message) -> erlang:nif_error(nif_not_loaded). +crypto_aead_chacha20poly1305_decrypt(_Key, _Nonce, _AD, _Message) -> erlang:nif_error(nif_not_loaded). +crypto_aead_chacha20poly1305_KEYBYTES() -> erlang:nif_error(nif_not_loaded). +crypto_aead_chacha20poly1305_NPUBBYTES() -> erlang:nif_error(nif_not_loaded). +crypto_aead_chacha20poly1305_ABYTES() -> erlang:nif_error(nif_not_loaded). +crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX() -> erlang:nif_error(nif_not_loaded). + crypto_auth_BYTES() -> erlang:nif_error(nif_not_loaded). crypto_auth_KEYBYTES() -> erlang:nif_error(nif_not_loaded). crypto_auth(_Msg, _Key) -> erlang:nif_error(nif_not_loaded). From 7181600cb4a53c661ee50f07384840aef8829f58 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Mon, 5 Mar 2018 09:53:08 +0100 Subject: [PATCH 3/4] Simple QuickCheck properties for ChaCha20Poly1305 --- eqc_test/enacl_eqc.erl | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/eqc_test/enacl_eqc.erl b/eqc_test/enacl_eqc.erl index 3bc2987..c7b4bf2 100644 --- a/eqc_test/enacl_eqc.erl +++ b/eqc_test/enacl_eqc.erl @@ -219,7 +219,7 @@ prop_box_failure_integrity() -> end end end). - + prop_seal_box_failure_integrity() -> ?FORALL({Msg, {PK1, SK1}}, {?FAULT_RATE(1,40,g_iodata()), ?FAULT_RATE(1,40,keypair())}, begin @@ -783,6 +783,26 @@ badargs(Thunk) -> error:badarg -> true end. +%% AEAD ChaCha20Poly1305 +prop_aead_chacha20poly1305() -> + ?FORALL({Key, Msg, AD, Nonce}, + {binary(32), binary(), ?LET(ADBytes, choose(0,16), binary(ADBytes)), largeint()}, + begin + EncryptMsg = enacl:aead_chacha20poly1305_encrypt(Key, Nonce, AD, Msg), + equals(enacl:aead_chacha20poly1305_decrypt(Key, Nonce, AD, EncryptMsg), Msg) + end). + +prop_aead_chacha20poly1305_fail() -> + ?FORALL({Key, Msg, AD, Nonce}, + {binary(32), binary(), ?LET(ADBytes, choose(0,16), binary(ADBytes)), largeint()}, + begin + EncryptMsg = enacl:aead_chacha20poly1305_encrypt(Key, Nonce, AD, Msg), + case enacl:aead_chacha20poly1305_decrypt(Key, Nonce, AD, <<0:8, EncryptMsg/binary>>) of + {error, _} -> true; + _ -> false + end + end). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Joel Test Blobs From 2f50ba6289f2f2d9fef05d22a396c0bab4f64149 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Mon, 5 Mar 2018 15:19:00 +0100 Subject: [PATCH 4/4] Also expose crypto_curve25519_scalarmult_base --- c_src/enacl_nif.c | 29 +++++++++++++++++++++++++++++ src/enacl.erl | 10 +++++++++- src/enacl_nif.erl | 4 +++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/c_src/enacl_nif.c b/c_src/enacl_nif.c index a6d422b..e657bfb 100644 --- a/c_src/enacl_nif.c +++ b/c_src/enacl_nif.c @@ -153,6 +153,34 @@ ERL_NIF_TERM enif_crypto_curve25519_scalarmult(ErlNifEnv *env, int argc, ERL_NIF return result; } +static +ERL_NIF_TERM enif_crypto_curve25519_scalarmult_base(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + ERL_NIF_TERM result; + ErlNifBinary secret, output; + + if ((argc != 1) || (!enif_inspect_binary(env, argv[0], &secret)) + || (secret.size != crypto_scalarmult_curve25519_BYTES)) { + return enif_make_badarg(env); + } + + do + { + if (!enif_alloc_binary(crypto_scalarmult_curve25519_BYTES, &output)) { + result = nacl_error_tuple(env, "alloc_failed"); + continue; + } + + if (crypto_scalarmult_curve25519_base(output.data, secret.data) < 0) { + result = nacl_error_tuple(env, "scalarmult_curve25519_base_failed"); + continue; + } + + result = enif_make_binary(env, &output); + } while (0); + + return result; +} + /* Ed 25519 */ static ERL_NIF_TERM enif_crypto_sign_ed25519_keypair(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { @@ -1698,6 +1726,7 @@ static ErlNifFunc nif_funcs[] = { {"crypto_pwhash_str_verify", 2, enif_crypto_pwhash_str_verify}, erl_nif_dirty_job_cpu_bound_macro("crypto_curve25519_scalarmult", 2, enif_crypto_curve25519_scalarmult), + erl_nif_dirty_job_cpu_bound_macro("crypto_curve25519_scalarmult_base", 1, enif_crypto_curve25519_scalarmult_base), erl_nif_dirty_job_cpu_bound_macro("crypto_sign_ed25519_keypair", 0, enif_crypto_sign_ed25519_keypair), {"crypto_sign_ed25519_public_to_curve25519", 1, enif_crypto_sign_ed25519_public_to_curve25519}, diff --git a/src/enacl.erl b/src/enacl.erl index 10ecc1c..02c4dcd 100644 --- a/src/enacl.erl +++ b/src/enacl.erl @@ -83,7 +83,8 @@ %% Curve 25519. -export([ - curve25519_scalarmult/1, curve25519_scalarmult/2 + curve25519_scalarmult/1, curve25519_scalarmult/2, + curve25519_scalarmult_base/1 ]). %% Ed 25519. @@ -905,6 +906,13 @@ curve25519_scalarmult(Secret, BasePoint) -> curve25519_scalarmult(#{ secret := Secret, base_point := BasePoint }) -> curve25519_scalarmult(Secret, BasePoint). +%% @doc curve25519_scalarmult_base/1 compute the corresponding public key for a +%% given secret key. +%% @end. +-spec curve25519_scalarmult_base(Secret :: binary()) -> binary(). +curve25519_scalarmult_base(Secret) -> + enacl_nif:crypto_curve25519_scalarmult_base(Secret). + %% Ed 25519 Crypto %% --------------- %% @doc crypto_sign_ed25519_keypair/0 creates a new Ed 25519 Public/Secret keypair. diff --git a/src/enacl_nif.erl b/src/enacl_nif.erl index 8e807b1..798ef53 100644 --- a/src/enacl_nif.erl +++ b/src/enacl_nif.erl @@ -98,7 +98,8 @@ %% Curve25519 -export([ - crypto_curve25519_scalarmult/2 + crypto_curve25519_scalarmult/2, + crypto_curve25519_scalarmult_base/1 ]). %% Ed 25519 @@ -273,6 +274,7 @@ crypto_onetimeauth_verify(_Authenticator, _Msg, _Key) -> erlang:nif_error(nif_no crypto_onetimeauth_verify_b(_Authenticator, _Msg, _Key) -> erlang:nif_error(nif_not_loaded). crypto_curve25519_scalarmult(_Secret, _BasePoint) -> erlang:nif_error(nif_not_loaded). +crypto_curve25519_scalarmult_base(_Secret) -> erlang:nif_error(nif_not_loaded). crypto_sign_ed25519_keypair() -> erlang:nif_error(nif_not_loaded). crypto_sign_ed25519_public_to_curve25519(_PublicKey) -> erlang:nif_error(nif_not_loaded).