-
Notifications
You must be signed in to change notification settings - Fork 559
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
342 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
"""Sign data with Ethereum private key.""" | ||
import binascii | ||
|
||
import bitcoin | ||
from ethereum import utils | ||
from ethereum.utils import big_endian_to_int, sha3 | ||
from secp256k1 import PrivateKey | ||
|
||
|
||
def get_ethereum_address_from_private_key(private_key_seed_ascii: str) -> str: | ||
"""Generate Ethereum address from a private key. | ||
https://github.com/ethereum/pyethsaletool/blob/master/pyethsaletool.py#L111 | ||
:param private_key: Any string as a seed | ||
:return: 0x prefixed hex string | ||
""" | ||
priv = utils.sha3(private_key_seed_ascii) | ||
pub = bitcoin.encode_pubkey(bitcoin.privtopub(priv), 'bin_electrum') | ||
return "0x" + binascii.hexlify(sha3(pub)[12:]).decode("ascii") | ||
|
||
|
||
def get_address_as_bytes(address: str) -> bytes: | ||
"""Convert Ethereum address to byte data payload for signing.""" | ||
assert address.startswith("0x") | ||
address_bytes = binascii.unhexlify(address[2:]) | ||
return address_bytes | ||
|
||
|
||
def sign(data: bytes, private_key_seed_ascii: str, hash_function=bitcoin.bin_sha256): | ||
"""Sign data using Ethereum private key. | ||
:param private_key_seed_ascii: Private key seed as ASCII string | ||
""" | ||
|
||
msghash = hash_function(data) | ||
|
||
priv = utils.sha3(private_key_seed_ascii) | ||
pub = bitcoin.privtopub(priv) | ||
|
||
# Based on ethereum/tesrt_contracts.py test_ecrecover | ||
pk = PrivateKey(priv, raw=True) | ||
|
||
signature = pk.ecdsa_recoverable_serialize(pk.ecdsa_sign_recoverable(msghash, raw=True)) | ||
signature = signature[0] + utils.bytearray_to_bytestr([signature[1]]) | ||
|
||
v = utils.safe_ord(signature[64]) + 27 | ||
r_bytes = signature[0:32] | ||
r = big_endian_to_int(r_bytes) | ||
s_bytes = signature[32:64] | ||
s = big_endian_to_int(s_bytes) | ||
|
||
# Make sure we use bytes data and zero padding stays | ||
# good across different systems | ||
r_hex = binascii.hexlify(r_bytes).decode("ascii") | ||
s_hex = binascii.hexlify(s_bytes).decode("ascii") | ||
|
||
# Convert to Etheruem address format | ||
addr = utils.big_endian_to_int(utils.sha3(bitcoin.encode_pubkey(pub, 'bin')[1:])[12:]) | ||
|
||
# Return various bits about signing so it's easier to debug | ||
return { | ||
"signature": signature, | ||
"v": v, | ||
"r": r, | ||
"s": s, | ||
"r_bytes": r_bytes, | ||
"s_bytes": s_bytes, | ||
"r_hex": "0x" + r_hex, | ||
"s_hex": "0x" + s_hex, | ||
"address_bitcoin": addr, | ||
"address_ethereum": get_ethereum_address_from_private_key(priv), | ||
"public_key": pub, | ||
"hash": msghash, | ||
"payload": binascii.hexlify(bytes([v] + list(r_bytes)+ list(s_bytes,))) | ||
} | ||
|
||
|
||
def verify(msghash: bytes, signature, public_key): | ||
"""Verify that data has been signed with Etheruem private key. | ||
:param signature: | ||
:return: | ||
""" | ||
|
||
V = utils.safe_ord(signature[64]) + 27 | ||
R = big_endian_to_int(signature[0:32]) | ||
S = big_endian_to_int(signature[32:64]) | ||
|
||
return bitcoin.ecdsa_raw_verify(msghash, (V, R, S), public_key) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
"""Customer id tracking.""" | ||
import uuid | ||
|
||
import pytest | ||
from ethereum.tester import TransactionFailed | ||
from eth_utils import to_wei | ||
|
||
from ico.tests.utils import time_travel | ||
from ico.state import CrowdsaleState | ||
|
||
|
||
@pytest.fixture | ||
def crowdsale(uncapped_flatprice, uncapped_flatprice_finalizer, team_multisig): | ||
"""Set up a crowdsale with customer id require policy.""" | ||
uncapped_flatprice.transact({"from": team_multisig}).setRequireCustomerId(True) | ||
return uncapped_flatprice | ||
|
||
|
||
@pytest.fixture | ||
def token(uncapped_token): | ||
"""Token contract we are buying.""" | ||
return uncapped_token | ||
|
||
|
||
@pytest.fixture | ||
def customer_id(uncapped_flatprice, uncapped_flatprice_finalizer, team_multisig) -> int: | ||
"""Generate UUID v4 customer id as a hex string.""" | ||
customer_id = int(uuid.uuid4().hex, 16) # Customer ids are 128-bit UUID v4 | ||
return customer_id | ||
|
||
|
||
def test_only_owner_change_change_policy(crowdsale, customer): | ||
"""Only owner change change customerId required policy.""" | ||
|
||
with pytest.raises(TransactionFailed): | ||
crowdsale.transact({"from": customer}).setRequireCustomerId(False) | ||
|
||
|
||
def test_participate_with_customer_id(chain, crowdsale, customer, customer_id, token): | ||
"""Buy tokens with a proper customer id.""" | ||
|
||
time_travel(chain, crowdsale.call().startsAt() + 1) | ||
wei_value = to_wei(1, "ether") | ||
assert crowdsale.call().getState() == CrowdsaleState.Funding | ||
crowdsale.transact({"from": customer, "value": wei_value}).buyWithCustomerId(customer_id) | ||
|
||
# We got credited | ||
assert token.call().balanceOf(customer) > 0 | ||
|
||
# We have tracked the investor id | ||
events = crowdsale.pastEvents("Invested").get() | ||
assert len(events) == 1 | ||
e = events[0] | ||
assert e["args"]["investor"] == customer | ||
assert e["args"]["weiAmount"] == wei_value | ||
assert e["args"]["customerId"] == customer_id | ||
|
||
|
||
def test_participate_missing_customer_id(chain, crowdsale, customer, customer_id, token): | ||
"""Cannot bypass customer id process.""" | ||
|
||
time_travel(chain, crowdsale.call().startsAt() + 1) | ||
wei_value = to_wei(1, "ether") | ||
assert crowdsale.call().getState() == CrowdsaleState.Funding | ||
|
||
with pytest.raises(TransactionFailed): | ||
crowdsale.transact({"from": customer, "value": wei_value}).buy() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
"""Signed address investing.""" | ||
|
||
import uuid | ||
|
||
import pytest | ||
from ethereum.tester import TransactionFailed | ||
from eth_utils import to_wei | ||
|
||
from ico.tests.utils import time_travel | ||
from ico.state import CrowdsaleState | ||
from ico.sign import get_ethereum_address_from_private_key | ||
from ico.sign import get_address_as_bytes | ||
from ico.sign import sign | ||
|
||
|
||
@pytest.fixture | ||
def private_key(): | ||
"""Server side private key.""" | ||
return "Toholampi summer festival 2017 has the most harcore rock bands" | ||
|
||
|
||
@pytest.fixture | ||
def signer_address(private_key): | ||
"""Server side signer address.""" | ||
return get_ethereum_address_from_private_key(private_key) | ||
|
||
|
||
@pytest.fixture | ||
def crowdsale(uncapped_flatprice, uncapped_flatprice_finalizer, team_multisig, signer_address): | ||
"""Set up a crowdsale with customer id require policy.""" | ||
uncapped_flatprice.transact({"from": team_multisig}).setRequireSignedAddress(True, signer_address) | ||
return uncapped_flatprice | ||
|
||
|
||
@pytest.fixture | ||
def token(uncapped_token): | ||
"""Token contract we are buying.""" | ||
return uncapped_token | ||
|
||
|
||
@pytest.fixture | ||
def customer_id(uncapped_flatprice, uncapped_flatprice_finalizer, team_multisig) -> int: | ||
"""Generate UUID v4 customer id as a hex string.""" | ||
customer_id = int(uuid.uuid4().hex, 16) # Customer ids are 128-bit UUID v4 | ||
return customer_id | ||
|
||
|
||
def test_only_owner_change_change_policy(crowdsale, customer, signer_address): | ||
"""Only owner change change customerId required policy.""" | ||
|
||
with pytest.raises(TransactionFailed): | ||
crowdsale.transact({"from": customer}).setRequireSignedAddress(True, signer_address) | ||
|
||
|
||
def test_participate_with_signed_address(chain, crowdsale, customer, customer_id, token, private_key): | ||
"""Buy tokens with a proper signed address.""" | ||
|
||
address_bytes = get_address_as_bytes(customer) | ||
sign_data = sign(address_bytes, private_key) | ||
|
||
time_travel(chain, crowdsale.call().startsAt() + 1) | ||
wei_value = to_wei(1, "ether") | ||
assert crowdsale.call().getState() == CrowdsaleState.Funding | ||
crowdsale.transact({"from": customer, "value": wei_value}).buyWithSignedAddress(customer_id, sign_data["v"], sign_data["r_bytes"], sign_data["s_bytes"]) | ||
|
||
# We got credited | ||
assert token.call().balanceOf(customer) > 0 | ||
|
||
# We have tracked the investor id | ||
events = crowdsale.pastEvents("Invested").get() | ||
assert len(events) == 1 | ||
e = events[0] | ||
assert e["args"]["investor"] == customer | ||
assert e["args"]["weiAmount"] == wei_value | ||
assert e["args"]["customerId"] == customer_id | ||
|
||
|
||
def test_participate_bad_signature(chain, crowdsale, customer, customer_id, token): | ||
"""Investment does not happen with a bad signature..""" | ||
|
||
address_bytes = get_address_as_bytes(customer) | ||
sign_data = sign(address_bytes, private_key) | ||
|
||
time_travel(chain, crowdsale.call().startsAt() + 1) | ||
wei_value = to_wei(1, "ether") | ||
assert crowdsale.call().getState() == CrowdsaleState.Funding | ||
|
||
sign_data["s_bytes"] = b'ABC' # Corrupt signature data | ||
|
||
with pytest.raises(TransactionFailed): | ||
crowdsale.transact({"from": customer, "value": wei_value}).buyWithSignedAddress(customer_id, sign_data["v"], sign_data["r_bytes"], sign_data["s_bytes"]) | ||
|
Oops, something went wrong.