Skip to content

Commit

Permalink
Version 2022.7.20 (#71)
Browse files Browse the repository at this point in the history
* Include btclib_libsecp256k1

* Refactor

* Release
  • Loading branch information
giacomocaironi authored Jul 20, 2022
1 parent 70932af commit 5415608
Show file tree
Hide file tree
Showing 15 changed files with 110 additions and 13 deletions.
5 changes: 4 additions & 1 deletion HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ Notable changes to the codebase are documented here.
Release names follow [*calendar versioning*](https://calver.org/):
full year, short month, short day (YYYY-M-D)

## v2022.6 (current master, in development, not released yet)
## v2022.7.20

Major changes include:

- by default ssa, dsa and point multiplication are now sped up using btclib_libsecp256k1;
this provides an 8 times speed up in benchmarks and 3 times in real world applications.


## v2022.5.3

Expand Down
2 changes: 1 addition & 1 deletion RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

6. Build the package distribution files:

```rm -rf btclib.egg-info/ build/ dist/ && python setup.py sdist bdist_wheel```
```rm -r btclib.egg-info/ build/ dist/ && python setup.py sdist bdist_wheel```

7. Push the package files to PyPi:

Expand Down
2 changes: 1 addition & 1 deletion btclib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"__init__ module for the btclib package."

name = "btclib"
__version__ = "2022.6"
__version__ = "2022.7.20"
__author__ = "The btclib developers"
__author_email__ = "[email protected]"
__copyright__ = "Copyright (C) 2017-2022 The btclib developers"
Expand Down
8 changes: 7 additions & 1 deletion btclib/ecc/curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from typing import Dict, List, Optional, Sequence

from btclib.alias import Integer, JacPoint, Point
from btclib.ecc import libsecp256k1
from btclib.ecc.curve_group import (
HEX_THRESHOLD,
CurveGroup,
Expand Down Expand Up @@ -210,13 +211,18 @@ def __repr__(self) -> str:

def mult(m: Integer, Q: Optional[Point] = None, ec: Curve = secp256k1) -> Point:
"Elliptic curve scalar multiplication."

m = int_from_integer(m) % ec.n

if (Q == ec.G or Q is None) and ec == secp256k1 and libsecp256k1.is_enabled():
return libsecp256k1.mult.mult(m)

if Q is None:
QJ = ec.GJ
else:
ec.require_on_curve(Q)
QJ = jac_from_aff(Q)

m = int_from_integer(m) % ec.n
R = _mult(m, QJ, ec)
return ec.aff_from_jac(R)

Expand Down
19 changes: 18 additions & 1 deletion btclib/ecc/dsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from typing import List, Optional, Tuple, Union

from btclib.alias import HashF, JacPoint, Octets, Point
from btclib.ecc import libsecp256k1
from btclib.ecc.curve import Curve, secp256k1
from btclib.ecc.curve_group import _double_mult, _mult
from btclib.ecc.der import Sig
Expand All @@ -31,7 +32,7 @@
from btclib.exceptions import BTClibRuntimeError, BTClibValueError
from btclib.hashes import challenge_, reduce_to_hlen
from btclib.to_prv_key import PrvKey, int_from_prv_key
from btclib.to_pub_key import Key, point_from_key
from btclib.to_pub_key import Key, point_from_key, pub_keyinfo_from_key
from btclib.utils import bytes_from_octets


Expand Down Expand Up @@ -105,6 +106,15 @@ def sign_(
# the challenge
c = challenge_(msg_hash, ec, hf) # 4, 5

if (
ec == secp256k1
and nonce is None
and lower_s
and hf == sha256
and libsecp256k1.is_enabled()
):
return Sig.parse(libsecp256k1.dsa.sign(msg_hash, q))

# nonce: an integer in the range 1..n-1.
if nonce is None:
nonce = _rfc6979_(c, q, ec, hf) # 1
Expand Down Expand Up @@ -194,6 +204,13 @@ def assert_as_valid_(
Q = point_from_key(key, sig.ec)
QJ = Q[0], Q[1], 1

if libsecp256k1.is_enabled() and sig.ec == secp256k1 and lower_s and hf == sha256:
msg_hash_bytes = bytes_from_octets(msg_hash)
pubkey_bytes = pub_keyinfo_from_key(key)[0]
if not libsecp256k1.dsa.verify(msg_hash_bytes, pubkey_bytes, sig.serialize()):
raise BTClibRuntimeError("signature verification failed")
return

# second part delegated to helper function
_assert_as_valid_(c, QJ, sig.r, sig.s, lower_s, sig.ec)

Expand Down
36 changes: 36 additions & 0 deletions btclib/ecc/libsecp256k1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env python3

# Copyright (C) 2017-2022 The btclib developers
#
# This file is part of btclib. It is subject to the license terms in the
# LICENSE file found in the top-level directory of this distribution.
#
# No part of btclib including this file, may be copied, modified, propagated,
# or distributed except according to the terms contained in the LICENSE file.

"""Helper functions to use the libsecp256k1 python bindings
"""

try:
from btclib_libsecp256k1 import dsa, mult, ssa # pylint: disable=unused-import

LIBSECP256K1_AVAILABLE = True
except ImportError: # pragma: no cover
dsa, ssa, mult = None, None, None # type: ignore
LIBSECP256K1_AVAILABLE = False

LIBSECP256K1_ENABLED = True


def enable():
global LIBSECP256K1_ENABLED # pylint: disable=global-statement
LIBSECP256K1_ENABLED = True


def disable():
global LIBSECP256K1_ENABLED # pylint: disable=global-statement
LIBSECP256K1_ENABLED = False


def is_enabled():
return LIBSECP256K1_ENABLED and LIBSECP256K1_AVAILABLE
11 changes: 11 additions & 0 deletions btclib/ecc/ssa.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@

from btclib.alias import BinaryData, HashF, Integer, JacPoint, Octets, Point
from btclib.bip32.bip32 import BIP32Key
from btclib.ecc import libsecp256k1
from btclib.ecc.curve import Curve, secp256k1
from btclib.ecc.curve_group import _double_mult, _mult, _multi_mult
from btclib.ecc.number_theory import mod_inv
Expand Down Expand Up @@ -307,6 +308,9 @@ def sign_(
# private and public keys
q, x_Q = gen_keys(prv_key, ec)

if ec == secp256k1 and nonce is None and hf == sha256 and libsecp256k1.is_enabled():
return Sig.parse(libsecp256k1.ssa.sign(msg_hash, q))

# nonce: an integer in the range 1..n-1.
if nonce is None:
nonce = _det_nonce_(msg_hash, q, x_Q, secrets.token_bytes(hf_len), ec, hf)
Expand Down Expand Up @@ -381,6 +385,13 @@ def assert_as_valid_(
# Let c = int(hf(bytes(r) || bytes(Q) || msg_hash)) mod n.
c = challenge_(msg_hash, x_Q, sig.r, sig.ec, hf)

if libsecp256k1.is_enabled() and sig.ec == secp256k1 and hf == sha256:
pubkey_bytes = x_Q.to_bytes(32, "big")
msg_hash = bytes_from_octets(msg_hash)
if not libsecp256k1.ssa.verify(msg_hash, pubkey_bytes, sig.serialize()):
raise BTClibRuntimeError("signature verification failed")
return

_assert_as_valid_(c, (x_Q, y_Q, 1), sig.r, sig.s, sig.ec)


Expand Down
2 changes: 1 addition & 1 deletion btclib/script/sig_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def legacy(script_: Octets, tx: Tx, vin_i: int, hash_type: int) -> bytes:
txin.script_sig = b""
# TODO: delete sig from script_ (even if non standard)
new_tx.vin[vin_i].script_sig = script_
if hash_type & 0x1F == NONE:
if hash_type & 0x1F is NONE:
new_tx.vout = []
for i, txin in enumerate(new_tx.vin):
if i != vin_i:
Expand Down
8 changes: 8 additions & 0 deletions docs/source/btclib.ecc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ btclib.ecc.dsa module
:undoc-members:
:show-inheritance:

btclib.ecc.libsecp256k1 module
------------------------------

.. automodule:: btclib.ecc.libsecp256k1
:members:
:undoc-members:
:show-inheritance:

btclib.ecc.number\_theory module
--------------------------------

Expand Down
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
author = "The btclib developers"

# The full version, including alpha/beta/rc tags
release = "2022.6"
release = "2022.7.20"


# -- General configuration ---------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#optional
btclib_libsecp256k1
btclib_libsecp256k1==0.1.1
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
include_package_data=True,
# test_suite="btclib.tests",
# install_requires=[],
extras_require={"secp256k1": ["btclib_libsecp256k1"]},
extras_require={"secp256k1": ["btclib_libsecp256k1==0.1.0"]},
keywords=(
"bitcoin cryptography elliptic-curves ecdsa schnorr RFC-6979 "
"bip32 bip39 electrum base58 bech32 segwit message-signing "
Expand Down
2 changes: 1 addition & 1 deletion tests/ecc/test_curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ def test_assorted_mult() -> None:
K2 = mult(k2, H, ec)

shamir = double_mult(k1, ec.G, k2, ec.G, ec)
assert shamir == mult(k1 + k2, ec.G, ec)
assert shamir == mult(k1 + k2, None, ec)

shamir = double_mult(k1, INF, k2, H, ec)
assert ec.is_on_curve(shamir)
Expand Down
6 changes: 5 additions & 1 deletion tests/ecc/test_dsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import pytest

from btclib.alias import INF
from btclib.ecc import dsa
from btclib.ecc import dsa, libsecp256k1
from btclib.ecc.curve import CURVES, Curve, double_mult, mult
from btclib.ecc.curve_group import _mult
from btclib.ecc.number_theory import mod_inv
Expand Down Expand Up @@ -61,6 +61,8 @@ def test_signature() -> None:
with pytest.raises(BTClibRuntimeError, match=err_msg):
dsa.assert_as_valid(msg_fake, Q, sig)

libsecp256k1.disable()

_, Q_fake = dsa.gen_keys()
assert not dsa.verify(msg, Q_fake, sig)
err_msg = "signature verification failed"
Expand Down Expand Up @@ -94,6 +96,8 @@ def test_signature() -> None:
with pytest.raises(BTClibValueError, match=err_msg):
dsa.sign_(reduce_to_hlen(msg), q, sig.ec.n)

libsecp256k1.enable()


def test_gec() -> None:
"""GEC 2: Test Vectors for SEC 1, section 2
Expand Down
16 changes: 14 additions & 2 deletions tests/ecc/test_ssa.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

from btclib.alias import INF, Point, String
from btclib.bip32.bip32 import BIP32KeyData
from btclib.ecc import ssa
from btclib.ecc import libsecp256k1, ssa
from btclib.ecc.curve import CURVES, double_mult, mult
from btclib.ecc.number_theory import mod_inv
from btclib.ecc.pedersen import second_generator
Expand All @@ -34,6 +34,8 @@
def test_signature() -> None:
msg = "Satoshi Nakamoto".encode()

libsecp256k1.disable()

q, x_Q = ssa.gen_keys(0x01)
sig = ssa.sign(msg, q)
ssa.assert_as_valid(msg, x_Q, sig)
Expand Down Expand Up @@ -94,6 +96,8 @@ def test_signature() -> None:
with pytest.raises(BTClibValueError, match=err_msg):
ssa.sign_(m_bytes, q, sig.ec.n)

libsecp256k1.enable()


def test_bip340_vectors() -> None:
"""BIP340 (Schnorr) test vectors.
Expand All @@ -105,7 +109,7 @@ def test_bip340_vectors() -> None:
with open(filename, newline="", encoding="ascii") as csvfile:
reader = csv.reader(csvfile)
# skip column headers while checking that there are 7 columns
_, _, _, _, _, _, _, _ = reader.__next__()
next(reader)
for row in reader:
(index, seckey, pub_key, aux_rand, m, sig, result, comment) = row
err_msg = f"Test vector #{int(index)}"
Expand Down Expand Up @@ -190,6 +194,14 @@ def test_low_cardinality() -> None:
for ec in test_curves:
for q in range(1, ec.n // 2): # all possible private keys
q, x_Q, QJ = ssa.gen_keys_(q, ec)
while True:
try:
msg = secrets.token_bytes(32)
sig = ssa.sign(msg, q, ec=ec)
break
except BTClibRuntimeError:
continue
ssa.assert_as_valid(msg, x_Q, sig)
for k in range(1, ec.n // 2): # all possible ephemeral keys
k, r = ssa.gen_keys(k, ec)
for e in range(ec.n): # all possible challenges
Expand Down

0 comments on commit 5415608

Please sign in to comment.