From 92cce4287a9da953eb56f547297df32b96bdba93 Mon Sep 17 00:00:00 2001 From: tecnovert Date: Wed, 28 Oct 2020 21:52:21 +0200 Subject: [PATCH] Add dleag and ecdsa otves modules. --- .travis/build_windows_wheels.sh | 9 ++- _cffi_build/build.py | 4 ++ _cffi_build/secp256k1_dleag.h | 25 +++++++++ _cffi_build/secp256k1_ecdsaotves.h | 32 +++++++++++ _cffi_build/secp256k1_ed25519.h | 2 + _cffi_build/secp256k1_generator.h | 7 +++ coincurve/dleag.py | 65 ++++++++++++++++++++++ coincurve/ecdsaotves.py | 89 ++++++++++++++++++++++++++++++ coincurve/ed25519.py | 35 ++++++++++++ setup.py | 31 ++++++++--- tests/test_dleag.py | 12 ++++ tests/test_ecdsaotves.py | 33 +++++++++++ 12 files changed, 334 insertions(+), 10 deletions(-) create mode 100644 _cffi_build/secp256k1_dleag.h create mode 100644 _cffi_build/secp256k1_ecdsaotves.h create mode 100644 _cffi_build/secp256k1_ed25519.h create mode 100644 _cffi_build/secp256k1_generator.h create mode 100644 coincurve/dleag.py create mode 100644 coincurve/ecdsaotves.py create mode 100644 coincurve/ed25519.py create mode 100644 tests/test_dleag.py create mode 100644 tests/test_ecdsaotves.py diff --git a/.travis/build_windows_wheels.sh b/.travis/build_windows_wheels.sh index ce94671..a2b7b17 100644 --- a/.travis/build_windows_wheels.sh +++ b/.travis/build_windows_wheels.sh @@ -5,14 +5,17 @@ set -e -x build_dll() { ./autogen.sh echo "LDFLAGS = -no-undefined" >> Makefile.am - ./configure --host=$1 --enable-module-recovery --enable-experimental --enable-module-ecdh --enable-endomorphism --disable-jni + ./configure --host=$1 --enable-module-recovery --enable-experimental --enable-module-ecdh --enable-endomorphism --disable-jni --disable-openssl-tests --with-bignum=no --enable-module-ed25519 --enable-module-generator --enable-module-dleag --enable-module-ecdsaotves make } cd .. -git clone https://github.com/bitcoin-core/secp256k1.git +#git clone https://github.com/bitcoin-core/secp256k1.git +#mv secp256k1 64bit +wget -O secp256k1_anonswap.zip https://github.com/tecnovert/secp256k1/archive/anonswap.zip +unzip secp256k1_anonswap.zip +mv secp256k1-anonswap 64bit -mv secp256k1 64bit cp 64bit 32bit -R cd 64bit diff --git a/_cffi_build/build.py b/_cffi_build/build.py index ea5c66c..b9e83c5 100644 --- a/_cffi_build/build.py +++ b/_cffi_build/build.py @@ -27,6 +27,10 @@ modules = [ Source('secp256k1.h', '#include '), Source('secp256k1_ecdh.h', '#include '), Source('secp256k1_recovery.h', '#include '), + Source('secp256k1_generator.h', '#include '), + Source('secp256k1_ed25519.h', '#include '), + Source('secp256k1_dleag.h', '#include '), + Source('secp256k1_ecdsaotves.h', '#include '), ] ffi = _mk_ffi(modules, libraries=['secp256k1']) diff --git a/_cffi_build/secp256k1_dleag.h b/_cffi_build/secp256k1_dleag.h new file mode 100644 index 0000000..58aaab6 --- /dev/null +++ b/_cffi_build/secp256k1_dleag.h @@ -0,0 +1,25 @@ + +size_t secp256k1_dleag_size(size_t n_bits); + +int secp256k1_dleag_prove( + const secp256k1_context *ctx, + unsigned char *proof_out, + size_t *proof_len, /* Input length of proof_out buffer, output length of proof. */ + const unsigned char *key, /* 32 bytes */ + size_t n_bits, + const unsigned char *nonce, /* 32 bytes */ + const secp256k1_generator *gen_s_a, + const secp256k1_generator *gen_s_b, + const unsigned char *gen_e_a, + const unsigned char *gen_e_b +); + +int secp256k1_dleag_verify( + const secp256k1_context *ctx, + const unsigned char *proof, + size_t proof_len, + const secp256k1_generator *gen_s_a, + const secp256k1_generator *gen_s_b, + const unsigned char *gen_e_a, + const unsigned char *gen_e_b +); diff --git a/_cffi_build/secp256k1_ecdsaotves.h b/_cffi_build/secp256k1_ecdsaotves.h new file mode 100644 index 0000000..1f9d1b0 --- /dev/null +++ b/_cffi_build/secp256k1_ecdsaotves.h @@ -0,0 +1,32 @@ +int ecdsaotves_enc_sign( + const secp256k1_context *ctx, + unsigned char *ct_out, + const unsigned char *skS, + const unsigned char *pkE, + const unsigned char *msg32 +); + +int ecdsaotves_enc_verify( + const secp256k1_context *ctx, + const unsigned char *pkS, + const unsigned char *pkE, + const unsigned char *msg32, + const unsigned char *ct +); + +int ecdsaotves_dec_sig( + const secp256k1_context *ctx, + unsigned char *sig_out, + size_t *sig_length, + const unsigned char *skE, + const unsigned char *ct +); + +int ecdsaotves_rec_enc_key( + const secp256k1_context *ctx, + unsigned char *key_out, + const unsigned char *pkE, + const unsigned char *ct, + const unsigned char *dersig, + size_t sig_length +); diff --git a/_cffi_build/secp256k1_ed25519.h b/_cffi_build/secp256k1_ed25519.h new file mode 100644 index 0000000..879f27c --- /dev/null +++ b/_cffi_build/secp256k1_ed25519.h @@ -0,0 +1,2 @@ +extern const unsigned char ed25519_gen[32]; +extern const unsigned char ed25519_gen2[32]; diff --git a/_cffi_build/secp256k1_generator.h b/_cffi_build/secp256k1_generator.h new file mode 100644 index 0000000..ab7c110 --- /dev/null +++ b/_cffi_build/secp256k1_generator.h @@ -0,0 +1,7 @@ +typedef struct { + unsigned char data[64]; +} secp256k1_generator; + +extern const secp256k1_generator secp256k1_generator_const_g; +extern const secp256k1_generator secp256k1_generator_const_h; + diff --git a/coincurve/dleag.py b/coincurve/dleag.py new file mode 100644 index 0000000..53d4acd --- /dev/null +++ b/coincurve/dleag.py @@ -0,0 +1,65 @@ +from coincurve.context import GLOBAL_CONTEXT +from coincurve.flags import EC_COMPRESSED, EC_UNCOMPRESSED +from ._libsecp256k1 import ffi, lib + + +def dleag_proof_len(bits=252): + return lib.secp256k1_dleag_size(bits) + + +def get_nonce(): + try: + import secrets + + return secrets.token_bytes(32) + except Exception: + from os import urandom + + return urandom(32) + + +def dleag_prove(private_key, context=GLOBAL_CONTEXT): + proof_length = dleag_proof_len() + proof_output = ffi.new('unsigned char[{}]'.format(proof_length)) + + proof_length_p = ffi.new('size_t *') + proof_length_p[0] = proof_length + + # nonce_bytes = ffi.from_buffer(secrets.token_bytes(32)) + nonce_bytes = get_nonce() + rv = lib.secp256k1_dleag_prove( + context.ctx, + proof_output, + proof_length_p, + private_key.secret, + 252, + nonce_bytes, + ffi.addressof(lib.secp256k1_generator_const_g), + ffi.addressof(lib.secp256k1_generator_const_h), + lib.ed25519_gen, + lib.ed25519_gen2, + ) + + if rv != 1: + raise ValueError('secp256k1_dleag_prove failed') + + # TODO: How to clear memory? Add random module to secp256k1? + # ffi.memmove(nonce_bytes, bytes([0] * 32), 32) + return bytes(ffi.buffer(proof_output, proof_length)) + + +def dleag_verify(proof, context=GLOBAL_CONTEXT): + proof_bytes = ffi.from_buffer(proof) + proof_length = len(proof) + + rv = lib.secp256k1_dleag_verify( + context.ctx, + proof_bytes, + proof_length, + ffi.addressof(lib.secp256k1_generator_const_g), + ffi.addressof(lib.secp256k1_generator_const_h), + lib.ed25519_gen, + lib.ed25519_gen2, + ) + + return True if rv == 1 else False diff --git a/coincurve/ecdsaotves.py b/coincurve/ecdsaotves.py new file mode 100644 index 0000000..ef21d8b --- /dev/null +++ b/coincurve/ecdsaotves.py @@ -0,0 +1,89 @@ +from coincurve.context import GLOBAL_CONTEXT +from coincurve.utils import bytes_to_int, int_to_bytes, sha256 +from ._libsecp256k1 import ffi, lib + + +def ecdsaotves_enc_sign(private_key_sign, public_key_encrypt, msg, context=GLOBAL_CONTEXT): + ct_length = 196 + ct_output = ffi.new('unsigned char[{}]'.format(ct_length)) + + if len(private_key_sign) != 32: + raise ValueError('private_key_sign must be 32 bytes') + if len(public_key_encrypt) != 33: + raise ValueError('public_key_encrypt must be 33 bytes') + if len(msg) != 32: + raise ValueError('msg must be 32 bytes') + + rv = lib.ecdsaotves_enc_sign( + context.ctx, + ct_output, + private_key_sign, + public_key_encrypt, + msg, + ) + + if rv != 1: + raise ValueError('ecdsaotves_enc_sign failed') + + return bytes(ffi.buffer(ct_output, ct_length)) + + +def ecdsaotves_enc_verify(public_key_sign, public_key_encrypt, msg, ct, context=GLOBAL_CONTEXT): + if len(public_key_sign) != 33: + raise ValueError('public_key_sign must be 33 bytes') + if len(public_key_encrypt) != 33: + raise ValueError('public_key_encrypt must be 33 bytes') + if len(msg) != 32: + raise ValueError('msg must be 32 bytes') + if len(ct) != 196: + raise ValueError('ciphertext must be 196 bytes') + + rv = lib.ecdsaotves_enc_verify( + context.ctx, + public_key_sign, + public_key_encrypt, + msg, + ct, + ) + + return True if rv == 1 else False + + +def ecdsaotves_dec_sig(private_key_encrypt, ct, context=GLOBAL_CONTEXT): + if len(private_key_encrypt) != 32: + raise ValueError('private_key_encrypt must be 32 bytes') + if len(ct) != 196: + raise ValueError('ciphertext must be 196 bytes') + + output_length = ffi.new('size_t *') + output_length[0] = 100 + sig_output = ffi.new('unsigned char[{}]'.format(100)) + + rv = lib.ecdsaotves_dec_sig( + context.ctx, + sig_output, + output_length, + private_key_encrypt, + ct, + ) + if rv != 1: + raise ValueError('ecdsaotves_dec_sig failed') + + return bytes(ffi.buffer(sig_output, output_length[0])) + + +def ecdsaotves_rec_enc_key(public_key_encrypt, ct, sig_der, context=GLOBAL_CONTEXT): + + if len(public_key_encrypt) != 33: + raise ValueError('public_key_encrypt must be 33 bytes') + if len(ct) != 196: + raise ValueError('ciphertext must be 196 bytes') + + key_output = ffi.new('unsigned char[{}]'.format(32)) + + sig_length = len(sig_der) + rv = lib.ecdsaotves_rec_enc_key(context.ctx, key_output, public_key_encrypt, ct, sig_der, sig_length) + if rv != 1: + raise ValueError('ecdsaotves_rec_enc_key failed') + + return bytes(ffi.buffer(key_output, 32)) diff --git a/coincurve/ed25519.py b/coincurve/ed25519.py new file mode 100644 index 0000000..b8304b0 --- /dev/null +++ b/coincurve/ed25519.py @@ -0,0 +1,35 @@ +from asn1crypto.keys import ECDomainParameters, ECPointBitString, ECPrivateKey, PrivateKeyAlgorithm, PrivateKeyInfo + +from coincurve.context import GLOBAL_CONTEXT +from coincurve.ecdsa import cdata_to_der, der_to_cdata, deserialize_recoverable, recover, serialize_recoverable +from coincurve.flags import EC_COMPRESSED, EC_UNCOMPRESSED +from coincurve.utils import bytes_to_int, int_to_bytes_padded +from ._libsecp256k1 import ffi, lib + + +DEFAULT_NONCE = (ffi.NULL, ffi.NULL) +GROUP_ORDER_INT = 2 ** 252 + 27742317777372353535851937790883648493 + + +def get_valid_secret(): + try: + import secrets + + return int_to_bytes_padded(9 + secrets.randbelow(GROUP_ORDER_INT - 9)) + except Exception: + from os import urandom + + while True: + secret = urandom(32) + if 9 < bytes_to_int(secret) < GROUP_ORDER_INT: + return secret + + +class Ed25519PrivateKey: + def __init__(self, secret=None, context=GLOBAL_CONTEXT): + self.context = context + + +class Ed25519PublicKey: + def __init__(self, data, context=GLOBAL_CONTEXT): + self.context = context diff --git a/setup.py b/setup.py index 1dd4dae..0a42d15 100644 --- a/setup.py +++ b/setup.py @@ -5,6 +5,7 @@ import platform import shutil import subprocess import tarfile +import zipfile from distutils import log from distutils.command.build_clib import build_clib as _build_clib from distutils.command.build_ext import build_ext as _build_ext @@ -48,6 +49,7 @@ def download_library(command): if command.dry_run: return libdir = absolute('libsecp256k1') + zipdir = absolute('secp256k1-anonswap') if os.path.exists(os.path.join(libdir, 'autogen.sh')): # Library already downloaded return @@ -55,7 +57,16 @@ def download_library(command): command.announce('downloading libsecp256k1 source code', level=log.INFO) try: import requests - + zip_url = 'https://github.com/tecnovert/secp256k1/archive/anonswap.zip' + r = requests.get(zip_url, stream=True) + status_code = r.status_code + if status_code == 200: + content = BytesIO(r.raw.read()) + content.seek(0) + with zipfile.ZipFile(content) as zip_ref: + zip_ref.extractall(absolute()) + shutil.move(zipdir, libdir) + """ r = requests.get(LIB_TARBALL_URL, stream=True) status_code = r.status_code if status_code == 200: @@ -67,6 +78,7 @@ def download_library(command): shutil.move(dirname, libdir) else: raise SystemExit('Unable to download secp256k1 library: HTTP-Status: %d', status_code) + """ except requests.exceptions.RequestException as e: raise SystemExit('Unable to download secp256k1 library: %s', str(e)) @@ -127,9 +139,9 @@ class build_clib(_build_clib): return build_flags('libsecp256k1', 'l', os.path.abspath(self.build_temp)) def run(self): - if has_system_lib(): - log.info('Using system library') - return + #if has_system_lib(): + # log.info('Using system library') + # return build_temp = os.path.abspath(self.build_temp) @@ -175,13 +187,18 @@ class build_clib(_build_clib): '--disable-dependency-tracking', '--with-pic', '--enable-module-recovery', - '--disable-jni', '--prefix', os.path.abspath(self.build_clib), '--enable-experimental', '--enable-module-ecdh', - '--enable-benchmark=no', - # '--enable-endomorphism', + #'--enable-benchmark=no', + '--enable-benchmark=yes', + '--enable-module-ed25519', + '--enable-module-generator', + '--enable-module-dleag', + '--enable-module-ecdsaotves', + '--with-bignum=no', + '--with-valgrind=no' ] log.debug('Running configure: {}'.format(' '.join(cmd))) diff --git a/tests/test_dleag.py b/tests/test_dleag.py new file mode 100644 index 0000000..c998caa --- /dev/null +++ b/tests/test_dleag.py @@ -0,0 +1,12 @@ +from coincurve.dleag import dleag_prove, dleag_verify +from coincurve.ed25519 import get_valid_secret +from coincurve.keys import PrivateKey, PublicKey + + +class TestDLEAG: + def test_dleag(self): + secret = get_valid_secret() + private_key = PrivateKey(secret) + proof = dleag_prove(private_key) + + assert True == dleag_verify(proof) diff --git a/tests/test_ecdsaotves.py b/tests/test_ecdsaotves.py new file mode 100644 index 0000000..a22b323 --- /dev/null +++ b/tests/test_ecdsaotves.py @@ -0,0 +1,33 @@ +import sys +from coincurve.ecdsaotves import ecdsaotves_enc_sign, ecdsaotves_enc_verify, ecdsaotves_dec_sig, ecdsaotves_rec_enc_key +from coincurve.keys import PrivateKey, PublicKey +from coincurve.utils import get_valid_secret, sha256 + + +class TestECDSAOTVES: + def test_ecdsaotves(self): + secret_sign = get_valid_secret() + secret_encrypt = get_valid_secret() + + pk_sign = PublicKey.from_secret(secret_sign) + pk_encrypt = PublicKey.from_secret(secret_encrypt) + pk_sb = pk_sign.format() + pk_eb = pk_encrypt.format() + + message = 'otves message' + if sys.version_info[0] > 2: + message_hash = sha256(bytes(message, 'utf-8')) + else: + message_hash = sha256(message) + + ct = ecdsaotves_enc_sign(secret_sign, pk_eb, message_hash) + + assert ecdsaotves_enc_verify(pk_sb, pk_eb, message_hash, ct) + + sig = ecdsaotves_dec_sig(secret_encrypt, ct) + + assert pk_sign.verify(sig, message_hash, hasher=None) + + secret_rec = ecdsaotves_rec_enc_key(pk_eb, ct, sig) + + assert secret_rec == secret_encrypt