Switched to C based nif

This commit is contained in:
Sean Hinde 2023-01-12 14:52:32 +01:00
parent 39ab0b2caa
commit 53b4d36a29
24 changed files with 218 additions and 2231 deletions

View File

@ -1,5 +0,0 @@
[target.'cfg(target_os = "macos")']
rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]

View File

@ -23,8 +23,6 @@ commands:
&& sudo apt-get \
install \
build-essential \
cargo \
cmake \
autoconf \
libncurses5-dev
- run:
@ -33,10 +31,6 @@ commands:
curl -fsSL -o otp-src.tar.gz https://github.com/erlang/otp/archive/OTP-<< parameters.OTP_VERSION >>.tar.gz
tar -zxf otp-src.tar.gz --strip-components=1
./otp_build autoconf && ./configure && make -j$(nproc) && sudo make install
- run:
name: Install rest
command: |
curl https://sh.rustup.rs -sSf | sh -s -- -yq && source $HOME/.cargo/env
setup_macos:
description: "Setup macos environment"
@ -49,7 +43,7 @@ commands:
name: Setup environment
command: |
brew update
brew install rust cmake erlang@<< parameters.OTP_VERSION >>
brew install erlang@<< parameters.OTP_VERSION >>
brew link --force erlang@<< parameters.OTP_VERSION >>
run_build:

5
.gitignore vendored
View File

@ -1,8 +1,7 @@
/target
**/*.rs.bk
**/*~
/parity-ethereum
/_build
/ebin
/priv/ecrecover.dll
/priv/ecrecover.so
/c_src/ecrecover.d
/c_src/ecrecover.o

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "c_src/secp256k1"]
path = c_src/secp256k1
url = https://github.com/bitcoin-core/secp256k1.git

1996
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +0,0 @@
[package]
name = "ecrecover"
version = "0.1.0"
authors = ["John Newby <john@newby.org>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
base64 = "0.10.1"
c_vec = "1.3.3"
ethcore-builtin = { git = "https://github.com/paritytech/parity-ethereum.git", rev = "acf7c48" }
lazy_static = "1.3.0"
libc = "0.2.60"
parity-bytes = "0.1.0"
rustler = "0.21.0"
[lib]
crate-type = ["cdylib"]

View File

@ -1,30 +0,0 @@
UNAME := $(shell uname)
ifeq ($(OS),Windows_NT)
nif_lib_src = ecrecover.dll
nif_lib = ecrecover.dll
else
ifeq ($(UNAME), Linux)
nif_lib_src = libecrecover.so
nif_lib = ecrecover.so
endif
ifeq ($(UNAME), Darwin)
nif_lib_src = libecrecover.dylib
nif_lib = ecrecover.so
endif
endif
all: priv/$(nif_lib) compile
compile:
./rebar3 compile
priv/$(nif_lib): src/lib.rs
ifneq ($(ECRECOVER_DISABLE_NIF_BUILD), true)
cargo build --release
cp target/release/$(nif_lib_src) $@
endif
clean:
rm -f priv/$(nif_lib) target/release/$(nif_lib_src)
./rebar3 clean

25
Makefile.win32 Normal file
View File

@ -0,0 +1,25 @@
secp256k1_dir := c_src/secp256k1
secp256k1_lib_dir := c_src/secp256k1/.libs
CFLAGS := -MT -DNOMINMAX -Ox -DNDEBUG -openmp -W4 -Zi -EHsc -nologo -I$(secp256k1_dir)/include
LDFLAGS := -LIBPATH:$(secp256k1_lib_dir) -nologo
.PHONY: win32_ecrecover
win32_ecrecover: $(secp256k1_lib_dir)/libsecp256k1.lib
$(secp256k1_lib_dir)/libsecp256k1.lib: $(secp256k1_lib_dir)/secp256k1.lib
lib ${LDFLAGS} -OUT:$@ -nodefaultlib $^
$(secp256k1_lib_dir)/secp256k1.lib: $(secp256k1_obj_dir)/fp.obj
lib ${LDFLAGS} -OUT:$@ -nodefaultlib $<
$(secp256k1_obj_dir)/fp.obj: $(secp256k1_src_dir)/fp.cpp
cl -c ${CFLAGS} $< -Fo$@
$(secp256k1_obj_dir)/bn_c384_256.obj: $(secp256k1_src_dir)/bn_c384_256.cpp
cl -c ${CFLAGS} $< -Fo$@
clean_win32_secp256k1:
make -C $(secp256k1_dir) clean
rm -f $(secp256k1_lib_dir)/*.lib $(secp256k1_lib_dir)/*.dll $(secp256k1_lib_dir)/*.exp

102
c_src/ecrecover.c Normal file
View File

@ -0,0 +1,102 @@
#include "erl_nif.h"
#include "secp256k1_recovery.h"
static secp256k1_context *ctx = NULL;
static ERL_NIF_TERM error_result(ErlNifEnv* env, char* error_msg)
{
return enif_make_tuple2(env, enif_make_atom(env, "error"), enif_make_string(env, error_msg, ERL_NIF_LATIN1));
}
static ERL_NIF_TERM ok_result(ErlNifEnv* env, ERL_NIF_TERM *r)
{
return enif_make_tuple2(env, enif_make_atom(env, "ok"), *r);
}
static ERL_NIF_TERM
recover(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ERL_NIF_TERM r;
ErlNifBinary message, csignature;
int result;
int compressed = SECP256K1_EC_UNCOMPRESSED;
size_t pubkeylen = 65;
int recid;
unsigned char* finished_recpubkey_buf;
secp256k1_ecdsa_recoverable_signature signature;
secp256k1_pubkey recpubkey;
if (!enif_inspect_binary(env, argv[0], &message)) {
return enif_make_badarg(env);
}
if (!enif_inspect_binary(env, argv[1], &csignature)) {
return enif_make_badarg(env);
}
if (!enif_get_int(env, argv[2], &recid)) {
return error_result(env, "Recovery id invalid 0-3");
}
if (recid < 0 || recid > 3) {
error_result(env, "Recovery id invalid 0-3x");
}
printf("xx1\r\n");
result = secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &signature, csignature.data, recid);
printf("xx2\r\n");
if (!result) {
return error_result(env, "ecdsa_signature_parse_compact returned 0");
}
// Now do ECDSA recovery
result = secp256k1_ecdsa_recover(ctx, &recpubkey, &signature, message.data);
if (!result) {
return error_result(env, "ecdsa recovery problem");
}
// Now serialize recpubkey based on the compression flag
finished_recpubkey_buf = enif_make_new_binary(env, pubkeylen, &r);
printf("pubker[0] = %d\r\n", recpubkey.data[0]);
result = secp256k1_ec_pubkey_serialize(ctx, finished_recpubkey_buf,
&pubkeylen, &recpubkey, compressed);
if (!result) {
return error_result(env, "ecdsa pubkey serialize error");
}
return ok_result(env, &r);
}
static int
load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info)
{
ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
return 0;
}
static int
upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM load_info)
{
return 0;
}
static void
unload(ErlNifEnv* env, void* priv)
{
secp256k1_context_destroy(ctx);
return;
}
static ErlNifFunc nif_funcs[] =
{
{"recover", 3, recover}
};
ERL_NIF_INIT(ecrecover, nif_funcs, &load, NULL, NULL, &unload);

1
c_src/secp256k1 Submodule

@ -0,0 +1 @@
Subproject commit cbe41ac138bc0773d60ab1942b7ad6fc5eccfc19

View File

@ -1 +0,0 @@
4717328000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b650acf9d3f5f0a2c799776a1254355d5f4061762a237396a99a0e0e3fc2bcd6729514a0dacb2e623ac4abd157cb18000000000000000000000000f941ba12e03

View File

@ -1 +0,0 @@
47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad000000000000000000000000000000000000000000000000000000000000001b650acf9d3f5f0a2c799776a1254355d5f4061762a237396a99a0e0e3fc2bcd6729514a0dacb2e623ac4abd157cb18163ff942280db4d5caad66ddf941ba12e03

View File

@ -1,7 +0,0 @@
#if !defined __AE__BASE64_H
#define __AE__BASE64_H
char *bin2hex(unsigned char*, int);
unsigned char *hex2bin(const char*);
#endif

View File

@ -1,6 +0,0 @@
#if !defined __AE_ECRECOVER_H
#define __AE_ECRECOVER_H
int ecrecover(const unsigned char *input, unsigned char *output);
#endif

View File

@ -1,10 +0,0 @@
#include <cstdarg>
#include <cstdint>
#include <cstdlib>
#include <new>
extern "C" {
void ecrecover(const unsigned char *input, unsigned char *output);
} // extern "C"

View File

@ -1,20 +0,0 @@
#if !defined __AE__ERL_COMM_H
#define __AE__ERL_COMM_H
#ifdef DEBUG
#include <stdio.h>
#define DEBUG_PRINTF printf
#else
#define DEBUG_PRINTF
#endif
typedef unsigned char byte;
#define ECRECOVER_BUFFER_MAX 1024
int read_cmd(byte *buf);
int write_cmd(byte *buf, int len);
int read_exact(byte *buf, int len);
int write_exact(byte *buf, int len);
#endif

View File

@ -1,17 +1,39 @@
{deps, [
%% ecrecover prebuilt libraries
{ecrecoverprebuilt,
{ecrecoverprebuilt_app_with_priv_from_git,
{git, "https://github.com/aeternity/ecrecover-prebuilt.git",
{ref, "4027374"}}}}
]}.
{deps,
[{sha3, {git, "https://github.com/aeternity/erlang-sha3", {ref, "b5f27a2"}}}]}.
{plugins, [{rebar_ecrecoverprebuilt_dep,
{git, "https://github.com/aeternity/rebar3-ecrecover-prebuilt-plugin.git",
{ref, "7286efd"}}}
]}.
{pre_hooks, [ {compile, "git submodule update --init"}
, {"(linux|darwin)", compile, "sh -c \"cd c_src/secp256k1 && ./autogen.sh && ./configure --enable-module-recovery && make\""}
, {"win32", compile, "make -f Makefile.win32 win32_ecrecover"}
]}.
{post_hooks, [ {"(linux|darwin)", clean, "make -C \"c_src/secp256k1\" clean"}
, {"win32", clean, "make -f Makefile.win32 clean_win32_ecrecover"}
]}.
{plugins, [pc]}.
{provider_hooks, [
{post, [ {compile, {pc, compile}}
, {clean, {pc, clean}}
]}
]}.
{port_specs, [{"priv/ecrecover.so", ["c_src/*.c"]}]}.
{port_env, [ {"darwin", "CFLAGS", "$CFLAGS -fPIC -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes -I c_src/secp256k1/include"}
, {"darwin", "CXXFLAGS", "$CXXFLAGS -fPIC -O3 -finline-functions -Wall"}
, {"darwin", "LDFLAGS", "$LDFLAGS c_src/secp256k1/.libs/libsecp256k1.a"}
, {"linux", "CFLAGS", "$CFLAGS -fPIC -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes -I c_src/secp256k1/include"}
, {"linux", "CXXFLAGS", "$CXXFLAGS -fPIC -O3 -finline-functions -Wall"}
, {"linux", "LDFLAGS", "$LDFLAGS c_src/secp256k1/.libs/libsecp256k1.a -lstdc++"}
, {"win32", "CFLAGS", "$CFLAGS /LD /MD /Fe /Ox /DNDEBUG /DMCLBN_DONT_EXPORT /Ic_src/secp256k1/include"}
, {"win32", "LDFLAGS", "$LDFLAGS /LIBPATH:c_src/cybozulib_ext/lib c_src/secp256k1/.libs/secp256k1.lib"}
]}.
{dialyzer, [{warnings, [unknown]},
{plt_apps, all_deps},
{base_plt_apps, [erts, kernel, stdlib, crypto]}
{base_plt_apps, [erts, kernel, stdlib]}
]}.

View File

@ -1,5 +1 @@
[{<<"ecrecoverprebuilt">>,
{ecrecoverprebuilt_app_with_priv_from_git,
{git,"https://github.com/aeternity/ecrecover-prebuilt.git",
{ref,"402737411420fc4c500e1f42c921ef185a35cc56"}}},
0}].
[].

BIN
rebar3

Binary file not shown.

View File

@ -4,8 +4,7 @@
{registered, []},
{applications,
[kernel,
stdlib,
ecrecoverprebuilt
stdlib
]},
{env,[]},
{modules, []},

View File

@ -1,7 +1,9 @@
-module(ecrecover).
%% https://github.com/mbrix/libsecp256k1
%% API
-export([recover/2]).
-export([recover/2, recover/3]).
%% NIF
-export([load/0]).
@ -9,16 +11,17 @@
%%=============================================================================
%% NIF API
%% GoodMsg1 = <<71,23,50,133,168,215,52,30,94,151,47,198,119,40,99,132,248,2,248,239,66,165,236,95,3,187,250,37,76,176,31,173>>.
%% GoodSig1_v = <<27,101,10,207,157,63,95,10,44,121,151,118,161,37,67,85,213,244,6,23,98,162,55,57,106,153,160,224,227,252,43,205,103,41,81,74,13,172,178,230,35,172,74,189,21,124,177,129,99,255,148,34,128,219,77,92,170,214,109,223,148,27,161,46,3>>.
%% ecrecover:recover(<<71,23,50,133,168,215,52,30,94,151,47,198,119,40,99,132,248,2,248,239,66,165,236,95,3,187,250,37,76,176,31,173>>, <<27,101,10,207,157,63,95,10,44,121,151,118,161,37,67,85,213,244,6,23,98,162,55,57,106,153,160,224,227,252,43,205,103,41,81,74,13,172,178,230,35,172,74,189,21,124,177,129,99,255,148,34,128,219,77,92,170,214,109,223,148,27,161,46,3>>).
%%
load() ->
% Prefer locally built NIF (good for testing and exotic platforms) over
% prebuilt binaries.
case load_local_nif() of
ok ->
ok;
{error, _} ->
load_prebuilt_nif()
end.
EbinDir = filename:dirname(code:which(?MODULE)),
AppDir = filename:dirname(EbinDir),
PrivDir = filename:join(AppDir, "priv"),
SoName = filename:join(PrivDir, atom_to_list(?MODULE)),
erlang:load_nif(SoName, 0).
not_loaded(Line) ->
erlang:nif_error({error, {not_loaded, [{module, ?MODULE}, {line, Line}]}}).
@ -26,36 +29,22 @@ not_loaded(Line) ->
%%=============================================================================
%% External API
-spec recover(<<_:(32*8)>>, <<_:(65*8)>>) -> <<_:(32*8)>>.
recover(<<_:32/binary>> = Hash, <<_:65/binary>> = Sig) ->
Input = <<Hash/binary, 0:(8*31), Sig/binary>>,
case recover_(Input) of
{ok, []} ->
<<0:256>>;
{ok, Res} ->
erlang:list_to_binary(Res);
_Err ->
recover(Hash, <<V, Sig:64/binary>>) when V == 27; V == 28 ->
RecId = V - 27,
case recover(Hash, Sig, RecId) of
{ok, <<4, XY:64/binary>>} ->
<<_:12/bytes, ShortPub:20/bytes>> = keccak256(XY),
<<0:96, ShortPub/binary>>;
{error, _} ->
<<0:256>>
end.
end.
-spec recover(<<_:(32*8)>>, <<_:(65*8)>>, integer()) -> <<_:(32*8)>>.
recover(_Hash, _Sig, _RecId) ->
not_loaded(?LINE).
%%=============================================================================
%% Internal Functions
load_local_nif() ->
EbinDir = filename:dirname(code:which(?MODULE)),
AppDir = filename:dirname(EbinDir),
PrivDir = filename:join(AppDir, "priv"),
SoName = filename:join(PrivDir, atom_to_list(?MODULE)),
erlang:load_nif(SoName, 0).
load_prebuilt_nif() ->
case code:priv_dir(ecrecoverprebuilt) of
{error, _} ->
{error, prebuilt_priv_dir_not_found};
PrivDir ->
SoName = filename:join(PrivDir, atom_to_list(?MODULE)),
erlang:load_nif(SoName, 0)
end.
recover_(_Input) ->
not_loaded(?LINE).
keccak256(Bin) ->
sha3:hash(256, Bin).

View File

@ -1,28 +0,0 @@
-module(ecrecover_util).
-export([ recover_from_hex/1
, bin_to_hex/1
, hex_to_bin/1
]).
%%=============================================================================
%% External API
recover_from_hex(Input) ->
<<Hash:32/binary, _:31/binary, Sig:65/binary>> = hex_to_bin(Input),
PubKey = ecrecover:recover(Hash, Sig),
bin_to_hex(PubKey).
bin_to_hex(Bin) ->
lists:flatten([io_lib:format("~2.16.0B", [X]) || X <- binary_to_list(Bin)]).
hex_to_bin(S) ->
hex_to_bin(S, []).
hex_to_bin([], Acc) ->
list_to_binary(lists:reverse(Acc));
hex_to_bin([X,Y|T], Acc) ->
{ok, [V], []} = io_lib:fread("~16u", [X,Y]),
hex_to_bin(T, [V | Acc]);
hex_to_bin([X|T], Acc) ->
{ok, [V], []} = io_lib:fread("~16u", lists:flatten([X,"0"])),
hex_to_bin(T, [V | Acc]).

View File

@ -1,42 +0,0 @@
extern crate c_vec;
extern crate ethcore_builtin;
#[macro_use]
extern crate lazy_static;
extern crate libc;
extern crate parity_bytes;
#[macro_use]
extern crate rustler;
use crate::ethcore_builtin::Implementation;
use ethcore_builtin::EcRecover;
use parity_bytes::BytesRef;
use rustler::*;
mod atoms {
rustler_atoms! {
atom ok;
}
}
rustler_export_nifs!(
"ecrecover",
[("recover_", 1, nif_ecrecover),],
Some(on_load)
);
#[no_mangle]
fn on_load(_env: Env, _load_info: Term) -> bool {
true
}
pub fn nif_ecrecover<'a>(env: Env<'a>, args: &[Term<'a>]) -> Result<Term<'a>, Error> {
let input: Binary = args[0].decode()?;
let mut byte_ref = Vec::new();
let ecrecover = EcRecover {};
let _result = match ecrecover.execute(input.as_slice(), &mut BytesRef::Flexible(&mut byte_ref))
{
Ok(_) => (),
Err(_e) => return Err(rustler::Error::Atom("ecrecover_failed")),
};
Ok((atoms::ok(), byte_ref.as_slice()).encode(env))
}

22
test/ecrecover_tests.erl Normal file
View File

@ -0,0 +1,22 @@
%%%=============================================================================
%%% @copyright (C) 2023, Aeternity Foundation
%%% @doc
%%% Unit tests for ecrecover
%%% @end
%%%=============================================================================
-module(ecrecover_tests).
-include_lib("eunit/include/eunit.hrl").
%% MSG and SIG from aecontract_SUITE, in turn taken from
%% https://github.com/aeternity/parity-ethereum/blob/master/ethcore/builtin/src/lib.rs#L656
%% GoodHexSig1 = "47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad000000000000000000000000000000000000000000000000000000000000001b650acf9d3f5f0a2c799776a1254355d5f4061762a237396a99a0e0e3fc2bcd6729514a0dacb2e623ac4abd157cb18163ff942280db4d5caad66ddf941ba12e03",
%% <<GoodMsg1:32/binary, _:31/binary, GoodSig1_v:65/binary>> = aeu_hex:hex_to_bin(GoodHexSig1)
-define(MSG, <<71,23,50,133,168,215,52,30,94,151,47,198,119,40,99,132,248,2,248,239,66,165,236,95,3,187,250,37,76,176,31,173>>).
-define(SIG, <<27,101,10,207,157,63,95,10,44,121,151,118,161,37,67,85,213,244,6,23,98,162,55,57,106,153,160,224,227,252,43,205,103,41,81,74,13,172,178,230,35,172,74,189,21,124,177,129,99,255,148,34,128,219,77,92,170,214,109,223,148,27,161,46,3>>).
-define(EXPECTED_PUBKEY, <<0,0,0,0,0,0,0,0,0,0,0,0,192,139,85,66,209,119,172,102,134,148,105,32,64,151,65,70,58,21,221,219>>).
recover_test() ->
PubKey = ecrecover:recover(?MSG, ?SIG),
?assertEqual(?EXPECTED_PUBKEY, PubKey).