ofek 7 years ago
parent
commit
b3da2c9a1c
  1. 10
      .travis/install.sh
  2. 18
      README.md
  3. 1
      _cffi_build/build.py
  4. 48
      _cffi_build/secp256k1_schnorr.h
  5. 120
      coincurve/__init__.py
  6. 3
      setup.py

10
.travis/install.sh

@ -80,16 +80,6 @@ if [[ $TRAVIS_OS_NAME == "osx" ]]; then
source ~/virtualenv/python${TRAVIS_PYTHON_VERSION}/bin/activate
fi
# Build lib-secp256k1 to test non bundled installation
if [[ $BUNDLED -eq 0 ]]; then
git clone git://github.com/bitcoin/secp256k1.git libsecp256k1_ext
builtin pushd libsecp256k1_ext
./autogen.sh
./configure --enable-module-recovery --enable-experimental --enable-module-ecdh --enable-module-schnorr
make
builtin popd
fi
# Install necessary packages
python -m pip install -U setuptools cffi pytest coverage coveralls

18
README.md

@ -1,12 +1,13 @@
# Coincurve
Python CFFI bindings for [libsecp256k1](https://github.com/bitcoin/secp256k1)
Python CFFI bindings for [libsecp256k1](https://github.com/bitcoin-core/secp256k1)
(an experimental and optimized C library for EC operations on curve secp256k1).
This is a fork of [https://github.com/ludbb/secp256k1-py](https://github.com/ludbb/secp256k1-py).
New features:
- Newer version of [libsecp256k1](https://github.com/bitcoin-core/secp256k1)
- Implements a fix for [https://bugs.python.org/issue28150](https://bugs.python.org/issue28150)
to support Python 3.6
- Supports Windows (soon)
@ -156,21 +157,6 @@ convert a recoverable signature to a normal signature, i.e. one that can be used
> NOTE: `ecdsa_recover*` can only be used if the `secp256k1` C library is compiled with support for it. If there is no support, an Exception will be raised when calling any of them.
#### class `secp256k1.Schnorr`
The `Schnorr` class is intended to be used as a mix in. Its methods can be accessed from any `secp256k1.PrivateKey` or `secp256k1.PublicKey` instances.
##### Methods
- `schnorr_recover(msg, schnorr_sig, raw=False, digest=hashlib.sha256)` -> internal object<br/>
recover and return a public key from a Schnorr signature. `schnorr_sig` is expected to be the result from `schnorr_partial_combine` or `schnorr_sign`. `msg`, `raw`, and `digest` are used as described in `ecdsa_sign`.
- `schnorr_partial_combine(schnorr_sigs)` -> bytes<br/>
combine multiple Schnorr partial signatures. `raw_sigs` is expected to be a list (or similar iterable) of signatures resulting from `PrivateKey.schnorr_partial_sign`. If the signatures cannot be combined, an Exception is raised.
> NOTE: `schnorr_*` can only be used if the `secp256k1` C library is compiled with support for it. If there is no support, an Exception will be raised when calling any of them.
#### Constants
##### `secp256k1.FLAG_SIGN`

1
_cffi_build/build.py

@ -27,7 +27,6 @@ modules = [
Source('secp256k1.h', '#include <secp256k1.h>'),
Source('secp256k1_ecdh.h', '#include <secp256k1_ecdh.h>'),
Source('secp256k1_recovery.h', '#include <secp256k1_recovery.h>'),
Source('secp256k1_schnorr.h', '#include <secp256k1_schnorr.h>'),
]
ffi = _mk_ffi(modules, libraries=['secp256k1'])

48
_cffi_build/secp256k1_schnorr.h

@ -1,48 +0,0 @@
int secp256k1_schnorr_sign(
const secp256k1_context* ctx,
unsigned char *sig64,
const unsigned char *msg32,
const unsigned char *seckey,
secp256k1_nonce_function noncefp,
const void *ndata
);
int secp256k1_schnorr_verify(
const secp256k1_context* ctx,
const unsigned char *sig64,
const unsigned char *msg32,
const secp256k1_pubkey *pubkey
);
int secp256k1_schnorr_recover(
const secp256k1_context* ctx,
secp256k1_pubkey *pubkey,
const unsigned char *sig64,
const unsigned char *msg32
);
int secp256k1_schnorr_generate_nonce_pair(
const secp256k1_context* ctx,
secp256k1_pubkey *pubnonce,
unsigned char *privnonce32,
const unsigned char *msg32,
const unsigned char *sec32,
secp256k1_nonce_function noncefp,
const void* noncedata
);
int secp256k1_schnorr_partial_sign(
const secp256k1_context* ctx,
unsigned char *sig64,
const unsigned char *msg32,
const unsigned char *sec32,
const secp256k1_pubkey *pubnonce_others,
const unsigned char *secnonce32
);
int secp256k1_schnorr_partial_combine(
const secp256k1_context* ctx,
unsigned char *sig64,
const unsigned char * const * sig64sin,
size_t n
);

120
coincurve/__init__.py

@ -14,7 +14,6 @@ ALL_FLAGS = FLAG_SIGN | FLAG_VERIFY
NO_FLAGS = lib.SECP256K1_CONTEXT_NONE
HAS_RECOVERABLE = hasattr(lib, 'secp256k1_ecdsa_sign_recoverable')
HAS_SCHNORR = hasattr(lib, 'secp256k1_schnorr_sign')
HAS_ECDH = hasattr(lib, 'secp256k1_ecdh')
@ -158,48 +157,7 @@ class ECDSA: # Use as a mixin; instance.ctx is assumed to exist.
return normal_sig
class Schnorr: # Use as a mixin; instance.ctx is assumed to exist.
def schnorr_recover(self, msg, schnorr_sig, raw=False,
digest=hashlib.sha256):
if not HAS_SCHNORR:
raise Exception("secp256k1_schnorr not enabled")
if self.flags & FLAG_VERIFY != FLAG_VERIFY:
raise Exception("instance not configured for sig verification")
msg32 = _hash32(msg, raw, digest)
pubkey = ffi.new('secp256k1_pubkey *')
recovered = lib.secp256k1_schnorr_recover(
self.ctx, pubkey, schnorr_sig, msg32)
if recovered:
return pubkey
raise Exception('failed to recover public key')
def schnorr_partial_combine(self, schnorr_sigs):
"""Combine multiple Schnorr partial signatures."""
if not HAS_SCHNORR:
raise Exception("secp256k1_schnorr not enabled")
assert len(schnorr_sigs) > 0
sig64 = ffi.new('char [64]')
sig64sin = []
for sig in schnorr_sigs:
if not isinstance(sig, bytes):
raise TypeError('expected bytes, got {}'.format(type(sig)))
if len(sig) != 64:
raise Exception('invalid signature length')
sig64sin.append(ffi.new('char []', sig))
res = lib.secp256k1_schnorr_partial_combine(
self.ctx, sig64, sig64sin, len(sig64sin))
if res <= 0:
raise Exception('failed to combine signatures ({})'.format(res))
return bytes(ffi.buffer(sig64, 64))
class PublicKey(Base, ECDSA, Schnorr):
class PublicKey(Base, ECDSA):
def __init__(self, pubkey=None, raw=False, flags=FLAG_VERIFY, ctx=None):
Base.__init__(self, ctx, flags)
@ -286,21 +244,6 @@ class PublicKey(Base, ECDSA, Schnorr):
return bool(verified)
def schnorr_verify(self, msg, schnorr_sig, raw=False,
digest=hashlib.sha256):
assert self.public_key, "No public key defined"
if not HAS_SCHNORR:
raise Exception("secp256k1_schnorr not enabled")
if self.flags & FLAG_VERIFY != FLAG_VERIFY:
raise Exception("instance not configured for sig verification")
msg32 = _hash32(msg, raw, digest)
verified = lib.secp256k1_schnorr_verify(
self.ctx, schnorr_sig, msg32, self.public_key)
return bool(verified)
def ecdh(self, scalar):
assert self.public_key, "No public key defined"
if not HAS_ECDH:
@ -317,7 +260,7 @@ class PublicKey(Base, ECDSA, Schnorr):
return bytes(ffi.buffer(result, 32))
class PrivateKey(Base, ECDSA, Schnorr):
class PrivateKey(Base, ECDSA):
def __init__(self, privkey=None, raw=True, flags=ALL_FLAGS, ctx=None):
assert flags in (ALL_FLAGS, FLAG_SIGN)
@ -406,65 +349,6 @@ class PrivateKey(Base, ECDSA, Schnorr):
return raw_sig
def schnorr_sign(self, msg, raw=False, digest=hashlib.sha256):
if not HAS_SCHNORR:
raise Exception("secp256k1_schnorr not enabled")
msg32 = _hash32(msg, raw, digest)
sig64 = ffi.new('char [64]')
signed = lib.secp256k1_schnorr_sign(
self.ctx, sig64, msg32, self.private_key, ffi.NULL, ffi.NULL)
assert signed == 1
return bytes(ffi.buffer(sig64, 64))
def schnorr_generate_nonce_pair(self, msg, raw=False,
digest=hashlib.sha256):
"""
Generate a nonce pair deterministically for use with
schnorr_partial_sign.
"""
if not HAS_SCHNORR:
raise Exception("secp256k1_schnorr not enabled")
msg32 = _hash32(msg, raw, digest)
pubnonce = ffi.new('secp256k1_pubkey *')
privnonce = ffi.new('char [32]')
valid = lib.secp256k1_schnorr_generate_nonce_pair(
self.ctx, pubnonce, privnonce, msg32, self.private_key,
ffi.NULL, ffi.NULL)
assert valid == 1
return pubnonce, privnonce
def schnorr_partial_sign(self, msg, privnonce, pubnonce_others,
raw=False, digest=hashlib.sha256):
"""
Produce a partial Schnorr signature, which can be combined using
schnorr_partial_combine to end up with a full signature that is
verifiable using PublicKey.schnorr_verify.
To combine pubnonces, use PublicKey.combine.
Do not pass the pubnonce produced for the respective privnonce;
combine the pubnonces from other signers and pass that instead.
"""
if not HAS_SCHNORR:
raise Exception("secp256k1_schnorr not enabled")
msg32 = _hash32(msg, raw, digest)
sig64 = ffi.new('char [64]')
res = lib.secp256k1_schnorr_partial_sign(
self.ctx, sig64, msg32, self.private_key,
pubnonce_others, privnonce)
if res <= 0:
raise Exception('failed to partially sign ({})'.format(res))
return bytes(ffi.buffer(sig64, 64))
def _hash32(msg, raw, digest):
if not raw:

3
setup.py

@ -34,7 +34,7 @@ from setup_support import absolute, build_flags, has_system_lib
# Version of libsecp256k1 to download if none exists in the `libsecp256k1`
# directory
LIB_TARBALL_URL = "https://github.com/bitcoin-core/secp256k1/archive/c5b32e16c4d2560ce829caf88a413fc06fd83d09.tar.gz"
LIB_TARBALL_URL = "https://github.com/bitcoin-core/secp256k1/archive/master.tar.gz"
# We require setuptools >= 3.3
@ -196,7 +196,6 @@ class build_clib(_build_clib):
"--with-bignum=gmp",
"--enable-experimental",
"--enable-module-ecdh",
"--enable-module-schnorr",
]
log.debug("Running configure: {}".format(" ".join(cmd)))

Loading…
Cancel
Save