Skip to content

Commit

Permalink
Merge pull request #31 from DavidKnott/pluggable-ecc-backends
Browse files Browse the repository at this point in the history
Create and test coincurve ecc backend
  • Loading branch information
pipermerriam authored Jul 22, 2017
2 parents fad1374 + 221211f commit 65afb30
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 1 deletion.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ env:
- TOX_ENV=py35-transactions
- TOX_ENV=py35-vm-fast
- TOX_ENV=py35-vm-limits
- TOX_ENV=py35-coincurve
#- TOX_ENV=py35-vm-performance
- TOX_ENV=flake8
cache:
Expand Down
79 changes: 79 additions & 0 deletions evm/ecc/backends/coincurve.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from .base import BaseECCBackend

from evm.utils.ecdsa import (
encode_signature,
)

from evm.utils.numeric import (
big_endian_to_int,
safe_ord
)

from evm.constants import (
NULL_BYTE,
)

from evm.utils.keccak import (
keccak,
)

from evm.utils.secp256k1 import (
decode_public_key,
encode_raw_public_key,
)


class CoinCurveECCBackend(BaseECCBackend):
def __init__(self):
try:
import coincurve
except ImportError:
raise ImportError("The CoinCurveECCBackend requires the coincurve \
library which is not available for import.")
self.keys = coincurve.keys
self.ecdsa = coincurve.ecdsa

def ecdsa_sign(self, msg, private_key):
v, r, s = self.ecdsa_raw_sign(keccak(msg), private_key)
signature = encode_signature(v, r, s)
return signature

def ecdsa_raw_sign(self, msg_hash, private_key):
signature = self.keys.PrivateKey(private_key).sign_recoverable(msg_hash, hasher=None)
v = safe_ord(signature[64]) + 27
r = big_endian_to_int(signature[0:32])
s = big_endian_to_int(signature[32:64])
return v, r, s

def ecdsa_verify(self, msg, signature, public_key):
signature = signature[1:] + NULL_BYTE
signature = self.__recoverable_to_normal(signature)
return self.keys.PublicKey(public_key).verify(signature, msg, hasher=keccak)

def ecdsa_raw_verify(self, msg_hash, vrs, raw_public_key):
v, r, s = vrs
signature = encode_signature(v, r, s)[1:] + NULL_BYTE
signature = self.__recoverable_to_normal(signature)
public_key = encode_raw_public_key(raw_public_key)
return self.keys.PublicKey(public_key).verify(signature, msg_hash, hasher=None)

def ecdsa_recover(self, msg, signature):
signature = signature[1:] + NULL_BYTE
return self.keys.PublicKey.from_signature_and_message(signature,
msg,
hasher=keccak
).format(compressed=False)

def ecdsa_raw_recover(self, msg_hash, vrs):
v, r, s = vrs
signature = encode_signature(v, r, s)[1:] + NULL_BYTE
raw_public_key = self.keys.PublicKey.from_signature_and_message(signature,
msg_hash,
hasher=None
).format(compressed=False)
return decode_public_key(raw_public_key)

def __recoverable_to_normal(self, signature):
return self.ecdsa.cdata_to_der(
self.ecdsa.recoverable_convert(
self.ecdsa.deserialize_recoverable(signature)))
7 changes: 7 additions & 0 deletions evm/utils/numeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,10 @@ def signed_to_unsigned(value):
return value + UINT_256_CEILING
else:
return value


def safe_ord(value):
if isinstance(value, int):
return value
else:
return ord(value)
5 changes: 5 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
"ethereum-bloom>=0.4.0",
"pyethash>=0.1.27",
],
extra_require={
'coincurve': [
"coincurve>=4.5.1",
]
},
license=about['__license__'],
zip_safe=False,
keywords='ethereum blockchain evm',
Expand Down
60 changes: 60 additions & 0 deletions tests/coincurve-ecc/test_coincurve_ecc_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from __future__ import absolute_import
import os
import pytest
from evm.ecc.backends.pure_python import PurePythonECCBackend
from evm.ecc import (
get_ecc_backend,
)
from evm.utils.secp256k1 import (
decode_public_key,
)


PRIVATE_KEY = (
b'E\xa9\x15\xe4\xd0`\x14\x9e\xb46Y`\xe6\xa7\xa4_3C\x93\t0a\x11k\x19~2@\x06_\xf2\xd8'
)
PUBLIC_KEY = (
b'\x04:QAvFo\xa8\x15\xedH\x1f\xfa\xd0\x91\x10\xa2\xd3D\xf6\xc9\xb7\x8c\x1d\x14\xaf\xc3Q\xc3\xa5\x1b\xe3=\x80r\xe7y9\xdc\x03\xbaDy\x07y\xb7\xa1\x02[\xaf0\x03\xf6s$0\xe2\x0c\xd9\xb7m\x953\x91\xb3'
)
RAW_PUBLIC_KEY = decode_public_key(PUBLIC_KEY)

MSG = b'my message'
MSG_HASH = b'#tpO\xbbmDaqK\xcb\xab\xebj\x16\x0c"E\x9ex\x1b\x08\\\x83lI\x08JG\x0e\xd6\xa4'

SIGNATURE = (
b'\x1bw\x84\xe4V\x19\x85\xaf\xeaj\xa9q\x9b\xcf\xc2\xbf\x17\x0c\x8c\xd7\xc3\xd5\xc0\x11\xbd\x80A\xc8\xdbJ\x97\x83\x07[\xb1\xdaD\x00\xe1\xd9#W\x83\rF\xa5gx\xc9"\xdem\xbfu\xa2\x19\xfe\xc6\x83IM\xc7o\xfd\x7f'
)

V = 27
R = 54060028713369731575288880898058519584012347418583874062392262086259746767623
S = 41474707565615897636207177895621376369577110960831782659442889110043833138559

pytest.importorskip('coincurve')
purePython = PurePythonECCBackend()


def test_ecdsa_sign():
# Sets ecc backend to coin curve
os.environ['EVM_ECC_BACKEND_CLASS'] = 'evm.ecc.backends.coincurve.CoinCurveECCBackend'
assert get_ecc_backend().ecdsa_sign(MSG, PRIVATE_KEY) == purePython.ecdsa_sign(MSG, PRIVATE_KEY)


def test_ecdsa_raw_sign():
assert get_ecc_backend().ecdsa_raw_sign(MSG_HASH, PRIVATE_KEY) == purePython.ecdsa_raw_sign(MSG_HASH, PRIVATE_KEY)


def test_ecdsa_verify():
assert get_ecc_backend().ecdsa_verify(MSG, SIGNATURE, PUBLIC_KEY) == purePython.ecdsa_verify(MSG, SIGNATURE, PUBLIC_KEY)


def test_ecdsa_raw_verify():
assert get_ecc_backend().ecdsa_raw_verify(MSG_HASH, (V, R, S), RAW_PUBLIC_KEY) == purePython.ecdsa_raw_verify(MSG_HASH, (V, R, S), RAW_PUBLIC_KEY)

def test_ecdsa_recover():
assert get_ecc_backend().ecdsa_recover(MSG, SIGNATURE) == purePython.ecdsa_recover(MSG, SIGNATURE)


def test_ecdsa_raw_recover():
assert get_ecc_backend().ecdsa_raw_recover(MSG_HASH, (V, R, S)) == purePython.ecdsa_raw_recover(MSG_HASH, (V, R, S))
# Switches ecc backend back to pure python for the rest of the tests
os.environ['EVM_ECC_BACKEND_CLASS'] = 'evm.ecc.backends.pure_python.PurePythonECCBackend'
4 changes: 3 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tox]
envlist=
py{27,34,35}-{core,state-all,state-fast,state-slow,blockchain-fast,blockchain-slow,transactions,vm-all,vm-fast,vm-limits,vm-performance}
py{27,34,35}-{core,coincurve,state-all,state-fast,state-slow,blockchain-fast,blockchain-slow,transactions,vm-all,vm-fast,vm-limits,vm-performance}
flake8

[flake8]
Expand All @@ -13,6 +13,7 @@ commands=
blockchain-fast: py.test {posargs:tests/json-fixtures/test_blockchain.py -m "not blockchain_slow"}
blockchain-slow: py.test {posargs:tests/json-fixtures/test_blockchain.py -m blockchain_slow}
core: py.test {posargs:tests/core}
coincurve: py.test {posargs:tests/coincurve-ecc}
state-all: py.test {posargs:tests/json-fixtures/test_state.py}
state-fast: py.test {posargs:tests/json-fixtures/test_state.py -m "not state_slow"}
state-slow: py.test {posargs:tests/json-fixtures/test_state.py -m state_slow}
Expand All @@ -23,6 +24,7 @@ commands=
vm-performance: py.test {posargs:tests/json-fixtures/test_vm.py -m vm_performance}
deps =
-r{toxinidir}/requirements-dev.txt
coincurve: coincurve
basepython =
py27: python2.7
py34: python3.4
Expand Down

0 comments on commit 65afb30

Please sign in to comment.