Skip to content

Commit

Permalink
Feature/ecdsa (#686)
Browse files Browse the repository at this point in the history
* ECDSA support

* RFC 6979 signature + ecrecover

---------

Co-authored-by: Lev Berman <[email protected]>
Co-authored-by: Saam Tehrani <[email protected]>
Co-authored-by: vird <[email protected]>
  • Loading branch information
4 people committed Jan 9, 2025
1 parent 89be3a1 commit 59bbb3c
Show file tree
Hide file tree
Showing 19 changed files with 694 additions and 160 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "lib/RandomX"]
path = apps/arweave/lib/RandomX
url = https://github.com/ArweaveTeam/RandomX.git
[submodule "apps/arweave/lib/secp256k1"]
path = apps/arweave/lib/secp256k1
url = https://github.com/bitcoin-core/secp256k1
19 changes: 16 additions & 3 deletions apps/arweave/c_src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ C_SRC_DIR = $(CURDIR)

CFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR) -I /usr/local/include -I ../lib/RandomX/src -I $(C_SRC_DIR)
CXXFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR) -I ../lib/RandomX/src -std=c++11
LDLIBS += -L $(ERL_INTERFACE_LIB_DIR) -L /usr/local/lib -lei -lssl -lcrypto
LDLIBS += -L $(ERL_INTERFACE_LIB_DIR) -L /usr/local/lib -lei -lssl -lcrypto


RX512_OUTPUT ?= $(CURDIR)/../priv/rx512_arweave.so
Expand Down Expand Up @@ -116,6 +116,19 @@ $(VDF_OUTPUT): $(VDF_OBJECTS)
@mkdir -p $(BASEDIR)/priv/
$(link_verbose) $(CXX) $(VDF_OBJECTS) $(LDFLAGS) $(LDLIBS) -shared -o $(VDF_OUTPUT)

SECP256K1_SOURCES = $(wildcard $(C_SRC_DIR)/*.c $(C_SRC_DIR)/secp256k1/*.c)
SECP256K1_OBJECTS = $(addsuffix .o, $(basename $(SECP256K1_SOURCES)))
SECP256K1_CFLAGS = -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR) -I /usr/local/include -I $(CURDIR)/../lib/secp256k1/src -I $(CURDIR)/../lib/secp256k1/include -I $(C_SRC_DIR)
SECP256K1_LDLIBS = -L $(ERL_INTERFACE_LIB_DIR) -L /usr/local/lib -lcrypto
SECP256K1_OUTPUT ?= $(CURDIR)/../priv/secp256k1_arweave.so

$(SECP256K1_OUTPUT): $(SECP256K1_OBJECTS)
@mkdir -p $(BASEDIR)/priv/
$(link_verbose) $(CXX) $(SECP256K1_OBJECTS) $(SECP256K1_LDLIBS) ../lib/secp256k1/build/lib/libsecp256k1.a -shared -o $(SECP256K1_OUTPUT)

%secp256k1_nif.o: %secp256k1_nif.c
$(c_verbose) $(CC) $(SECP256K1_CFLAGS) -c $(OUTPUT_OPTION) $<

%.o: %.c
$(COMPILE_C) $(OUTPUT_OPTION) $<

Expand All @@ -128,10 +141,10 @@ $(VDF_OUTPUT): $(VDF_OBJECTS)
%.o: %.cpp
$(COMPILE_CPP) $(OUTPUT_OPTION) $<

all: $(RX512_OUTPUT) $(RX4096_OUTPUT) $(RXSQUARED_OUTPUT) $(VDF_OUTPUT)
all: $(RX512_OUTPUT) $(RX4096_OUTPUT) $(RXSQUARED_OUTPUT) $(VDF_OUTPUT) $(SECP256K1_OUTPUT)

clean:
@rm -f $(RX512_OUTPUT) $(RX4096_OUTPUT) $(RXSQUARED_OUTPUT) $(VDF_OUTPUT) $(RX512_OBJECTS) $(RX4096_OBJECTS) $(RXSQUARED_OBJECTS) $(VDF_OBJECTS)
@rm -f $(RX512_OUTPUT) $(RX4096_OUTPUT) $(RXSQUARED_OUTPUT) $(VDF_OUTPUT) $(RX512_OBJECTS) $(RX4096_OBJECTS) $(RXSQUARED_OBJECTS) $(VDF_OBJECTS) $(SECP256K1_OUTPUT) $(SECP256K1_OBJECTS)



Expand Down
209 changes: 209 additions & 0 deletions apps/arweave/c_src/secp256k1/secp256k1_nif.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
#include <string.h>

#include <openssl/rand.h>
#include <secp256k1.h>
#include <secp256k1_recovery.h>

#include <ar_nif.h>

#define SECP256K1_PUBKEY_UNCOMPRESSED_SIZE 65
#define SECP256K1_PUBKEY_COMPRESSED_SIZE 33
#define SECP256K1_SIGNATURE_COMPACT_SIZE 64
#define SECP256K1_SIGNATURE_RECOVERABLE_SIZE 65
#define SECP256K1_PRIVKEY_SIZE 32
#define SECP256K1_CONTEXT_SEED_SIZE 32
#define SECP256K1_DIGEST_SIZE 32

static int secp256k1_load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info) {
return 0;
}

#if defined(_MSC_VER)
// For SecureZeroMemory
#include <Windows.h>
#endif
/* Cleanses memory to prevent leaking sensitive info. Won't be optimized out. */
static void secure_erase(void *ptr, size_t len) {
#if defined(_MSC_VER)
/* SecureZeroMemory is guaranteed not to be optimized out by MSVC. */
SecureZeroMemory(ptr, len);
#elif defined(__GNUC__)
/* We use a memory barrier that scares the compiler away from optimizing out the memset.
*
* Quoting Adam Langley <[email protected]> in commit ad1907fe73334d6c696c8539646c21b11178f20f
* in BoringSSL (ISC License):
* As best as we can tell, this is sufficient to break any optimisations that
* might try to eliminate "superfluous" memsets.
* This method used in memzero_explicit() the Linux kernel, too. Its advantage is that it is
* pretty efficient, because the compiler can still implement the memset() efficiently,
* just not remove it entirely. See "Dead Store Elimination (Still) Considered Harmful" by
* Yang et al. (USENIX Security 2017) for more background.
*/
memset(ptr, 0, len);
__asm__ __volatile__("" : : "r"(ptr) : "memory");
#else
void *(*volatile const volatile_memset)(void *, int, size_t) = memset;
volatile_memset(ptr, 0, len);
#endif
}

static ERL_NIF_TERM sign_recoverable(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
if (argc != 2) {
return enif_make_badarg(env);
}
ErlNifBinary Digest, PrivateBytes;
if (!enif_inspect_binary(env, argv[0], &Digest)) {
return enif_make_badarg(env);
}
if (Digest.size != SECP256K1_DIGEST_SIZE) {
return enif_make_badarg(env);
}

if (!enif_inspect_binary(env, argv[1], &PrivateBytes)) {
return enif_make_badarg(env);
}
if (PrivateBytes.size != SECP256K1_PRIVKEY_SIZE) {
return enif_make_badarg(env);
}

char *error = NULL;
unsigned char seed[SECP256K1_CONTEXT_SEED_SIZE];
unsigned char digest[SECP256K1_DIGEST_SIZE];
unsigned char privbytes[SECP256K1_PRIVKEY_SIZE];
unsigned char signature_compact[SECP256K1_SIGNATURE_COMPACT_SIZE];
unsigned char signature_recoverable[SECP256K1_SIGNATURE_RECOVERABLE_SIZE];
int recid;
secp256k1_ecdsa_recoverable_signature s;
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);

memcpy(digest, Digest.data, SECP256K1_DIGEST_SIZE);
memcpy(privbytes, PrivateBytes.data, SECP256K1_PRIVKEY_SIZE);

if (!secp256k1_ec_seckey_verify(ctx, privbytes)) {
error = "secp256k1 key is invalid.";
goto cleanup;
}

if (!RAND_priv_bytes(seed, sizeof(seed))) {
error = "Failed to generate random seed for context.";
goto cleanup;
}

if (!secp256k1_context_randomize(ctx, seed)) {
error = "Failed to randomize context.";
goto cleanup;
}

if(!secp256k1_ecdsa_sign_recoverable(ctx, &s, digest, privbytes, NULL, NULL)) {
error = "Failed to create signature.";
goto cleanup;
}

if(!secp256k1_ecdsa_recoverable_signature_serialize_compact(ctx, signature_compact, &recid, &s)) {
error = "Failed to serialize signature.";
goto cleanup;
}
memcpy(signature_recoverable, signature_compact, SECP256K1_SIGNATURE_COMPACT_SIZE);
signature_recoverable[64] = (unsigned char)(recid);

ERL_NIF_TERM signature_term = make_output_binary(env, signature_recoverable, SECP256K1_SIGNATURE_RECOVERABLE_SIZE);

cleanup:
secp256k1_context_destroy(ctx);
secure_erase(seed, sizeof(seed));
secure_erase(privbytes, sizeof(privbytes));
memset(signature_compact, 0, SECP256K1_SIGNATURE_COMPACT_SIZE);
memset(signature_recoverable, 0, SECP256K1_SIGNATURE_RECOVERABLE_SIZE);

if (error) {
return error_tuple(env, error);
}
return ok_tuple(env, signature_term);
}

static ERL_NIF_TERM recover_pk_and_verify(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
if (argc != 2) {
return enif_make_badarg(env);
}
ErlNifBinary Digest, Signature;
if (!enif_inspect_binary(env, argv[0], &Digest)) {
return enif_make_badarg(env);
}
if (Digest.size != SECP256K1_DIGEST_SIZE) {
return enif_make_badarg(env);
}

if (!enif_inspect_binary(env, argv[1], &Signature)) {
return enif_make_badarg(env);
}
if (Signature.size != SECP256K1_SIGNATURE_RECOVERABLE_SIZE) {
return enif_make_badarg(env);
}

char *error = NULL;
unsigned char digest[SECP256K1_DIGEST_SIZE];
unsigned char signature_recoverable[SECP256K1_SIGNATURE_RECOVERABLE_SIZE];
unsigned char signature_compact[SECP256K1_SIGNATURE_COMPACT_SIZE];
unsigned char pubbytes[SECP256K1_PUBKEY_COMPRESSED_SIZE];
int recid;
secp256k1_ecdsa_recoverable_signature rs;
secp256k1_ecdsa_signature s;
secp256k1_pubkey pubkey;

memcpy(digest, Digest.data, SECP256K1_DIGEST_SIZE);
memcpy(signature_recoverable, Signature.data, SECP256K1_SIGNATURE_RECOVERABLE_SIZE);

memcpy(signature_compact, signature_recoverable, SECP256K1_SIGNATURE_COMPACT_SIZE);
recid = (int)signature_recoverable[64];

if (recid < 0 || recid > 3) {
error = "Invalid signature recid. recid >= 0 && recid <= 3.";
goto cleanup;
}

if (!secp256k1_ecdsa_recoverable_signature_parse_compact(secp256k1_context_static, &rs, signature_compact, recid)) {
error = "Failed to deserialize/parse recoverable signature.";
goto cleanup;
}

if (!secp256k1_ecdsa_recover(secp256k1_context_static, &pubkey, &rs, digest)) {
error = "Failed to recover public key.";
goto cleanup;
}
size_t l = SECP256K1_PUBKEY_COMPRESSED_SIZE;
if (!secp256k1_ec_pubkey_serialize(secp256k1_context_static, pubbytes, &l, &pubkey, SECP256K1_EC_COMPRESSED)) {
error = "Failed to serialize the recovered public key.";
goto cleanup;
}

if (!secp256k1_ecdsa_recoverable_signature_convert(secp256k1_context_static, &s, &rs)) {
error = "Failed to convert recoverable signature to compact signature.";
goto cleanup;
}

// NOTE. https://github.com/bitcoin-core/secp256k1/blob/f79f46c70386c693ff4e7aef0b9e7923ba284e56/src/secp256k1.c#L461
// Verify performs check for low-s
int is_valid = secp256k1_ecdsa_verify(secp256k1_context_static, &s, digest, &pubkey);
ERL_NIF_TERM pubkey_term = make_output_binary(env, pubbytes, SECP256K1_PUBKEY_COMPRESSED_SIZE);

cleanup:
memset(digest, 0, SECP256K1_DIGEST_SIZE);
memset(pubbytes, 0, SECP256K1_PUBKEY_COMPRESSED_SIZE);
memset(signature_compact, 0, SECP256K1_SIGNATURE_COMPACT_SIZE);
memset(signature_recoverable, 0, SECP256K1_SIGNATURE_RECOVERABLE_SIZE);

if (error) {
return error_tuple(env, error);
}
if (is_valid) {
return ok_tuple2(env, enif_make_atom(env, "true"), pubkey_term);
}
return ok_tuple2(env, enif_make_atom(env, "false"), pubkey_term);
}

static ErlNifFunc nif_funcs[] = {
{"sign_recoverable", 2, sign_recoverable},
{"recover_pk_and_verify", 2, recover_pk_and_verify}
};

ERL_NIF_INIT(secp256k1_nif, nif_funcs, secp256k1_load, NULL, NULL, NULL)
7 changes: 7 additions & 0 deletions apps/arweave/include/ar.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@
%% The default key type used by transactions that do not specify a signature type.
-define(DEFAULT_KEY_TYPE, {?RSA_SIGN_ALG, 65537}).

-define(RSA_KEY_TYPE, {?RSA_SIGN_ALG, 65537}).
-define(ECDSA_KEY_TYPE, {?ECDSA_SIGN_ALG, secp256k1}).

-define(RSA_BLOCK_SIG_SIZE, 512).
-define(ECDSA_PUB_KEY_SIZE, 33).
-define(ECDSA_SIG_SIZE, 65).

%% The difficulty a new weave is started with.
-define(DEFAULT_DIFF, 6).

Expand Down
1 change: 1 addition & 0 deletions apps/arweave/lib/secp256k1
Submodule secp256k1 added at f79f46
35 changes: 25 additions & 10 deletions apps/arweave/src/ar.erl
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@
-behaviour(application).

-export([main/0, main/1, create_wallet/0, create_wallet/1,
create_ecdsa_wallet/0, create_ecdsa_wallet/1,
benchmark_packing/1, benchmark_packing/0, benchmark_2_9/0, benchmark_2_9/1,
benchmark_vdf/0,
benchmark_hash/1, benchmark_hash/0, start/0,
start/1, start/2, stop/1, stop_dependencies/0, start_dependencies/0,
tests/0, tests/1, tests/2, e2e/0, e2e/1, shell/0, stop_shell/0,
docs/0, shutdown/1, console/1, console/2]).

-include_lib("arweave/include/ar.hrl").
-include_lib("arweave/include/ar_consensus.hrl").
-include_lib("arweave/include/ar_config.hrl").
-include("../include/ar.hrl").
-include("../include/ar_consensus.hrl").
-include("../include/ar_config.hrl").

-include_lib("eunit/include/eunit.hrl").

Expand Down Expand Up @@ -794,12 +795,25 @@ set_mining_address(#config{ mining_addr = Addr, cm_exit_peer = CmExitPeer,
end.

create_wallet([DataDir]) ->
create_wallet(DataDir, ?RSA_KEY_TYPE);
create_wallet(_) ->
create_wallet_fail(?RSA_KEY_TYPE).

create_ecdsa_wallet() ->
create_wallet_fail(?ECDSA_KEY_TYPE).

create_ecdsa_wallet([DataDir]) ->
create_wallet(DataDir, ?ECDSA_KEY_TYPE);
create_ecdsa_wallet(_) ->
create_wallet_fail(?ECDSA_KEY_TYPE).

create_wallet(DataDir, KeyType) ->
case filelib:is_dir(DataDir) of
false ->
create_wallet_fail();
create_wallet_fail(KeyType);
true ->
ok = application:set_env(arweave, config, #config{ data_dir = DataDir }),
case ar_wallet:new_keyfile({?RSA_SIGN_ALG, 65537}) of
case ar_wallet:new_keyfile(KeyType) of
{error, Reason} ->
ar:console("Failed to create a wallet, reason: ~p.~n~n",
[io_lib:format("~p", [Reason])]),
Expand All @@ -810,15 +824,16 @@ create_wallet([DataDir]) ->
ar:console("Created a wallet with address ~s.~n", [ar_util:encode(Addr)]),
erlang:halt()
end
end;
create_wallet(_) ->
create_wallet_fail().
end.

create_wallet() ->
create_wallet_fail().
create_wallet_fail(?RSA_KEY_TYPE).

create_wallet_fail() ->
create_wallet_fail(?RSA_KEY_TYPE) ->
io:format("Usage: ./bin/create-wallet [data_dir]~n"),
erlang:halt();
create_wallet_fail(?ECDSA_KEY_TYPE) ->
io:format("Usage: ./bin/create-ecdsa-wallet [data_dir]~n"),
erlang:halt().

benchmark_packing() ->
Expand Down
Loading

0 comments on commit 59bbb3c

Please sign in to comment.