diff --git a/apps/arweave/c_src/secp256k1/Makefile b/apps/arweave/c_src/secp256k1/Makefile new file mode 100644 index 000000000..f5396c3ef --- /dev/null +++ b/apps/arweave/c_src/secp256k1/Makefile @@ -0,0 +1,23 @@ +ERTS_INCLUDE_DIR ?= $(shell erl -noshell -eval 'io:format("~ts/erts-~ts/include/", [code:root_dir(), erlang:system_info(version)]).' -s init stop) +ERL_INTERFACE_INCLUDE_DIR ?= $(shell erl -noshell -eval 'io:format("~ts", [code:lib_dir(erl_interface, include)]).' -s init stop) +ERL_INTERFACE_LIB_DIR ?= $(shell erl -noshell -eval 'io:format("~ts", [code:lib_dir(erl_interface, lib)]).' -s init stop) + +CC = cc +ifeq ($(MODE), debug) + CFLAGS ?= -O0 -g +else + CFLAGS ?= -O3 +endif +CFLAGS += -fPIC -shared -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR) -I /usr/local/include -I ../../lib/secp256k1/src -I ../../lib/secp256k1/include +LDFLAGS = -L../../lib/secp256k1/build/lib +LDLIBS = -L $(ERL_INTERFACE_LIB_DIR) -L /usr/local/lib -lsecp256k1 +NIF_SRC = secp256k1_nif.c +NIF_SO = $(shell pwd)/../../priv/secp256k1_nif.so + +all: $(NIF_SO) + +$(NIF_SO): $(NIF_SRC) + $(CC) $(CFLAGS) $(NIF_SRC) -o $(NIF_SO) $(LDFLAGS) $(LDLIBS) + +clean: + rm -f $(NIF_SO) diff --git a/apps/arweave/c_src/secp256k1/fill_random.h b/apps/arweave/c_src/secp256k1/fill_random.h new file mode 100644 index 000000000..dfc53addd --- /dev/null +++ b/apps/arweave/c_src/secp256k1/fill_random.h @@ -0,0 +1,108 @@ +/************************************************************************* + * Copyright (c) 2020-2021 Elichai Turkel * + * Distributed under the CC0 software license, see the accompanying file * + * EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 * + *************************************************************************/ + +/* + * This file is an attempt at collecting best practice methods for obtaining randomness with different operating systems. + * It may be out-of-date. Consult the documentation of the operating system before considering to use the methods below. + * + * Platform randomness sources: + * Linux -> `getrandom(2)`(`sys/random.h`), if not available `/dev/urandom` should be used. http://man7.org/linux/man-pages/man2/getrandom.2.html, https://linux.die.net/man/4/urandom + * macOS -> `getentropy(2)`(`sys/random.h`), if not available `/dev/urandom` should be used. https://www.unix.com/man-page/mojave/2/getentropy, https://opensource.apple.com/source/xnu/xnu-517.12.7/bsd/man/man4/random.4.auto.html + * FreeBSD -> `getrandom(2)`(`sys/random.h`), if not available `kern.arandom` should be used. https://www.freebsd.org/cgi/man.cgi?query=getrandom, https://www.freebsd.org/cgi/man.cgi?query=random&sektion=4 + * OpenBSD -> `getentropy(2)`(`unistd.h`), if not available `/dev/urandom` should be used. https://man.openbsd.org/getentropy, https://man.openbsd.org/urandom + * Windows -> `BCryptGenRandom`(`bcrypt.h`). https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom + */ + +#if defined(_WIN32) +/* + * The defined WIN32_NO_STATUS macro disables return code definitions in + * windows.h, which avoids "macro redefinition" MSVC warnings in ntstatus.h. + */ +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS +#include +#include +#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) +#include +#elif defined(__OpenBSD__) +#include +#else +#error "Couldn't identify the OS" +#endif + +#include +#include +#include +#include + +/* Returns 1 on success, and 0 on failure. */ +static int fill_random(unsigned char* data, size_t size) { +#if defined(_WIN32) + NTSTATUS res = BCryptGenRandom(NULL, data, size, BCRYPT_USE_SYSTEM_PREFERRED_RNG); + if (res != STATUS_SUCCESS || size > ULONG_MAX) { + return 0; + } else { + return 1; + } +#elif defined(__linux__) || defined(__FreeBSD__) + /* If `getrandom(2)` is not available you should fallback to /dev/urandom */ + ssize_t res = getrandom(data, size, 0); + if (res < 0 || (size_t)res != size ) { + return 0; + } else { + return 1; + } +#elif defined(__APPLE__) || defined(__OpenBSD__) + /* If `getentropy(2)` is not available you should fallback to either + * `SecRandomCopyBytes` or /dev/urandom */ + int res = getentropy(data, size); + if (res == 0) { + return 1; + } else { + return 0; + } +#endif + return 0; +} + +static void print_hex(unsigned char* data, size_t size) { + size_t i; + printf("0x"); + for (i = 0; i < size; i++) { + printf("%02x", data[i]); + } + printf("\n"); +} + +#if defined(_MSC_VER) +// For SecureZeroMemory +#include +#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 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 +} diff --git a/apps/arweave/c_src/secp256k1/secp256k1_nif.c b/apps/arweave/c_src/secp256k1/secp256k1_nif.c new file mode 100644 index 000000000..584f9f5ea --- /dev/null +++ b/apps/arweave/c_src/secp256k1/secp256k1_nif.c @@ -0,0 +1,47 @@ +#include + +#include "../ar_nif.h" +#include "./fill_random.h" + +#define SECP256K1_PUBKEY_UNCOMPRESSED_SIZE 65 +#define SECP256K1_PUBKEY_COMPRESSED_SIZE 33 +#define SECP256K1_PRIVKEY_SIZE 32 +#define SECP256K1_CONTEXT_SEED_SIZE 32 + +static int secp256k1_load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info) { + return 0; +} + +static ERL_NIF_TERM generate_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { + unsigned char seed[SECP256K1_CONTEXT_SEED_SIZE]; + if (!fill_random(seed, sizeof(seed))) { + return error_tuple(env, "Failed to generate random seed for context."); + } + secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + if (!secp256k1_context_randomize(ctx, seed)) { + return error_tuple(env, "Failed to randomize context."); + } + + unsigned char privbytes[SECP256K1_PRIVKEY_SIZE]; + if (!fill_random(privbytes, sizeof(privbytes))) { + return error_tuple(env, "Failed to generate random key."); + } + if (!secp256k1_ec_seckey_verify(ctx, privbytes)) { + return error_tuple(env, "Generated secret secp256k1 key is invalid."); + } + + secp256k1_pubkey pubkey; + if(!secp256k1_ec_pubkey_create(ctx, &pubkey, privbytes)) { + return error_tuple(env, "Failed to populate public key."); + } + + unsigned char pubbytes[SECP256K1_PUBKEY_UNCOMPRESSED_SIZE]; + size_t l = sizeof(pubbytes); + if (!secp256k1_ec_pubkey_serialize(ctx, pubbytes, &l, &pubkey, SECP256K1_EC_UNCOMPRESSED)) { + return error_tuple(env, "Failed to serialize public key."); + } + + ERL_NIF_TERM privkey_bin = make_output_binary(env, privbytes, SECP256K1_PRIVKEY_SIZE); + ERL_NIF_TERM pubkey_bin = make_output_binary(env, pubbytes, SECP256K1_PUBKEY_UNCOMPRESSED_SIZE); + return ok_tuple2(env, privkey_bin, pubkey_bin); +} diff --git a/apps/arweave/lib/secp256k1 b/apps/arweave/lib/secp256k1 index f79f46c70..0cdc758a5 160000 --- a/apps/arweave/lib/secp256k1 +++ b/apps/arweave/lib/secp256k1 @@ -1 +1 @@ -Subproject commit f79f46c70386c693ff4e7aef0b9e7923ba284e56 +Subproject commit 0cdc758a56360bf58a851fe91085a327ec97685a diff --git a/rebar.config b/rebar.config index b54cbea1a..454c001b0 100644 --- a/rebar.config +++ b/rebar.config @@ -397,6 +397,8 @@ {"(linux)", compile, "make -C apps/arweave/lib/RandomX/buildsquared"}, {"(freebsd|netbsd|openbsd)", compile, "gmake -C apps/arweave/lib/RandomX/buildsquared"}, {"(darwin|linux|freebsd|netbsd|openbsd)", compile, "bash -c \"cd apps/arweave/lib/RandomX/buildsquared && mv librandomx.a librandomxsquared.a\""}, + % Build secp256k1 + {"(darwin|linux|freebsd|netbsd|openbsd)", compile, "./scripts/build_secp256k1.sh > /dev/null"}, % Compile NIFs {"(linux)", compile, "env AR=gcc-ar make all -C apps/arweave/c_src"}, {"(darwin)", compile, "make all -C apps/arweave/c_src"}, @@ -412,7 +414,9 @@ % Clean randomxsquared {"(linux|darwin)", clean, "bash -c \"if [ -d apps/arweave/lib/RandomX/buildsquared ]; then make -C apps/arweave/lib/RandomX/buildsquared clean; fi\""}, {"(freebsd|netbsd|openbsd)", clean, "bash -c \"if [ -d apps/arweave/lib/RandomX/buildsquared ]; then gmake -C apps/arweave/lib/RandomX/buildsquared clean; fi\""}, - % Clan NIFs + % Clean secp256k1 + {"(linux|darwin|freebsd|netbsd|openbsd)", clean, "bash -c \"if [ -d apps/arweave/lib/secp256k1/build ]; then rm -rf apps/arweave/lib/secp256k1/build; fi\""}, + % Clean NIFs {"(linux|darwin)", clean, "make -C apps/arweave/c_src clean"}, {"(freebsd|netbsd|openbsd)", clean, "gmake -C apps/arweave/c_src clean"} ]}. diff --git a/scripts/build_secp256k1.sh b/scripts/build_secp256k1.sh new file mode 100755 index 000000000..1976c101f --- /dev/null +++ b/scripts/build_secp256k1.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +BUILD_DIR="apps/arweave/lib/secp256k1/build" +CMAKE_OPTIONS="-DBUILD_SHARED_LIBS=OFF \ + -DSECP256K1_BUILD_BENCHMARK=OFF \ + -DSECP256K1_BUILD_EXHAUSTIVE_TESTS=OFF \ + -DSECP256K1_BUILD_TESTS=OFF \ + -DSECP256K1_DISABLE_SHARED=ON \ + -DSECP256K1_ENABLE_MODULE_MUSIG=OFF \ + -DSECP256K1_ENABLE_MODULE_EXTRAKEYS=OFF \ + -DSECP256K1_ENABLE_MODULE_ELLSWIFT=OFF \ + -DSECP256K1_ENABLE_MODULE_SCHNORRSIG=OFF" +mkdir -p $BUILD_DIR +cd $BUILD_DIR +cmake $CMAKE_OPTIONS .. +cmake --build .