diff --git a/nucypher/blockchain/economics.py b/nucypher/blockchain/economics.py index 34db922c1..5dd20cb8f 100644 --- a/nucypher/blockchain/economics.py +++ b/nucypher/blockchain/economics.py @@ -177,7 +177,8 @@ class SlashingEconomics: reward_coefficient = 2 @property - def deployment_parameters(self): + def deployment_parameters(self) -> Tuple[int, ...]: + """Cast coefficient attributes to uint256 compatible type for solidity+EVM""" deployment_parameters = [ self.algorithm_sha256, @@ -187,4 +188,4 @@ class SlashingEconomics: self.reward_coefficient ] - return deployment_parameters + return tuple(map(int, deployment_parameters)) diff --git a/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py b/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py index b6d675215..1a8ca37cb 100644 --- a/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py +++ b/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py @@ -18,21 +18,21 @@ along with nucypher. If not, see . import os -import coincurve import pytest -from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.backends.openssl import backend +from cryptography.hazmat.primitives import hashes from eth_tester.exceptions import TransactionFailed from eth_utils import to_canonical_address from web3.contract import Contract -from nucypher.blockchain.eth.token import NU -from nucypher.policy.models import IndisputableEvidence from umbral import pre from umbral.curvebn import CurveBN from umbral.keys import UmbralPrivateKey -from umbral.signing import Signer, Signature -from cryptography.hazmat.backends.openssl import backend -from cryptography.hazmat.primitives import hashes +from umbral.signing import Signer + +from nucypher.blockchain.eth.token import NU +from nucypher.crypto.utils import get_signature_recovery_value +from nucypher.policy.models import IndisputableEvidence VALUE_FIELD = 0 @@ -46,12 +46,6 @@ policy_manager_secret = os.urandom(SECRET_LENGTH) user_escrow_secret = os.urandom(SECRET_LENGTH) adjudicator_secret = os.urandom(SECRET_LENGTH) -ALGORITHM_SHA256 = 1 -BASE_PENALTY = 300 -PENALTY_HISTORY_COEFFICIENT = 10 -PERCENTAGE_PENALTY_COEFFICIENT = 2 -REWARD_COEFFICIENT = 2 - @pytest.fixture() def token(testerchain): @@ -109,7 +103,7 @@ def policy_manager(testerchain, escrow): @pytest.fixture() -def adjudicator(testerchain, escrow): +def adjudicator(testerchain, escrow, slashing_economics): escrow, _ = escrow creator = testerchain.interface.w3.eth.accounts[0] @@ -119,11 +113,8 @@ def adjudicator(testerchain, escrow): contract, _ = testerchain.interface.deploy_contract( 'MiningAdjudicator', escrow.address, - ALGORITHM_SHA256, - BASE_PENALTY, - PENALTY_HISTORY_COEFFICIENT, - PERCENTAGE_PENALTY_COEFFICIENT, - REWARD_COEFFICIENT) + *slashing_economics.deployment_parameters) + dispatcher, _ = testerchain.interface.deploy_contract('Dispatcher', contract.address, secret_hash) # Wrap dispatcher contract @@ -140,26 +131,8 @@ def adjudicator(testerchain, escrow): # TODO: Obtain real re-encryption metadata. Maybe constructing a WorkOrder and obtaining a response. # TODO organize support functions +# TODO: Repeated method in estimate_gas def generate_args_for_slashing(testerchain, miner): - def sign_data(data, umbral_privkey): - umbral_pubkey_bytes = umbral_privkey.get_pubkey().to_bytes(is_compressed=False) - - # Prepare hash of the data - hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) - hash_ctx.update(data) - data_hash = hash_ctx.finalize() - - # Sign data and calculate recoverable signature - cryptography_priv_key = umbral_privkey.to_cryptography_privkey() - signature_der_bytes = cryptography_priv_key.sign(data, ec.ECDSA(hashes.SHA256())) - signature = Signature.from_bytes(signature_der_bytes, der_encoded=True) - recoverable_signature = bytes(signature) + bytes([0]) - pubkey_bytes = coincurve.PublicKey.from_signature_and_message(recoverable_signature, data_hash, hasher=None) \ - .format(compressed=False) - if pubkey_bytes != umbral_pubkey_bytes: - recoverable_signature = bytes(signature) + bytes([1]) - return recoverable_signature - delegating_privkey = UmbralPrivateKey.gen_key() _symmetric_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey()) signing_privkey = UmbralPrivateKey.gen_key() @@ -174,19 +147,34 @@ def generate_args_for_slashing(testerchain, miner): sign_delegating_key=False, sign_receiving_key=False) capsule.set_correctness_keys(delegating_privkey.get_pubkey(), pub_key_bob, signing_privkey.get_pubkey()) - cfrag = pre.reencrypt(kfrags[0], capsule, metadata=os.urandom(34)) - capsule_bytes = capsule.to_bytes() - # Corrupt proof - cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve) - cfrag_bytes = cfrag.to_bytes() - hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) - hash_ctx.update(capsule_bytes + cfrag_bytes) - data_hash = hash_ctx.finalize() - requester_umbral_private_key = UmbralPrivateKey.gen_key() - requester_umbral_public_key_bytes = requester_umbral_private_key.get_pubkey().to_bytes(is_compressed=False) - capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key) + miner_umbral_private_key = UmbralPrivateKey.gen_key() - miner_umbral_public_key_bytes = miner_umbral_private_key.get_pubkey().to_bytes(is_compressed=False) + ursula_pubkey = miner_umbral_private_key.get_pubkey() + ursula_pubkey_bytes = ursula_pubkey.to_bytes(is_compressed=False)[1:] + specification = bytes(capsule) + ursula_pubkey_bytes + bytes(20) + bytes(32) + + ursulas_signer = Signer(miner_umbral_private_key) + bobs_signer = Signer(priv_key_bob) + task_signature = bytes(bobs_signer(specification)) + + metadata = bytes(ursulas_signer(task_signature)) + + cfrag = pre.reencrypt(kfrags[0], capsule, metadata=metadata) + cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve) + + cfrag_signature = bytes(ursulas_signer(bytes(cfrag))) + + bob_pubkey_bytes = pub_key_bob.to_bytes(is_compressed=False)[1:] + + cfrag_signature_v = get_signature_recovery_value(message=bytes(cfrag), + signature=cfrag_signature, + public_key=ursula_pubkey) + task_signature_v = get_signature_recovery_value(message=task_signature, + signature=metadata, + public_key=ursula_pubkey) + recovery_values = cfrag_signature_v + task_signature_v + + miner_umbral_public_key_bytes = miner_umbral_private_key.get_pubkey().to_bytes(is_compressed=False)[1:] # Sign Umbral public key using eth-key hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(miner_umbral_public_key_bytes) @@ -195,16 +183,19 @@ def generate_args_for_slashing(testerchain, miner): sig_key = testerchain.interface.provider.ethereum_tester.backend._key_lookup[address] signed_miner_umbral_public_key = bytes(sig_key.sign_msg_hash(miner_umbral_public_key_hash)) - capsule_signature_by_requester_and_miner = sign_data(capsule_signature_by_requester, miner_umbral_private_key) - cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key) evidence = IndisputableEvidence(capsule, cfrag, ursula=None) evidence_data = evidence.precompute_values() - return data_hash, (capsule_bytes, - capsule_signature_by_requester, - capsule_signature_by_requester_and_miner, - cfrag_bytes, - cfrag_signature_by_miner, - requester_umbral_public_key_bytes, + + hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) + hash_ctx.update(bytes(capsule) + bytes(cfrag)) + data_hash = hash_ctx.finalize() + + return data_hash, (bytes(capsule), + bytes(cfrag), + cfrag_signature, + task_signature, + recovery_values, + bob_pubkey_bytes, miner_umbral_public_key_bytes, signed_miner_umbral_public_key, evidence_data) @@ -272,7 +263,7 @@ def execute_multisig_transaction(testerchain, multisig, accounts, tx): @pytest.mark.slow -def test_all(testerchain, token, escrow, policy_manager, adjudicator, user_escrow_proxy, multisig): +def test_all(testerchain, token, escrow, policy_manager, adjudicator, user_escrow_proxy, multisig, slashing_economics): # Travel to the start of the next period to prevent problems with unexpected overflow first period testerchain.time_travel(hours=1) @@ -780,19 +771,22 @@ def test_all(testerchain, token, escrow, policy_manager, adjudicator, user_escro total_lock = escrow.functions.lockedPerPeriod(period).call() alice1_balance = token.functions.balanceOf(alice1).call() + algorithm_sha256, base_penalty, *coefficients = slashing_economics.deployment_parameters + penalty_history_coefficient, percentage_penalty_coefficient, reward_coefficient = coefficients + data_hash, slashing_args = generate_args_for_slashing(testerchain, ursula1) assert not adjudicator.functions.evaluatedCFrags(data_hash).call() tx = adjudicator.functions.evaluateCFrag(*slashing_args).transact({'from': alice1}) testerchain.wait_for_receipt(tx) assert adjudicator.functions.evaluatedCFrags(data_hash).call() - assert tokens_amount - BASE_PENALTY == escrow.functions.minerInfo(ursula1).call()[VALUE_FIELD] + assert tokens_amount - base_penalty == escrow.functions.minerInfo(ursula1).call()[VALUE_FIELD] assert previous_lock == escrow.functions.getLockedTokensInPast(ursula1, 1).call() assert lock == escrow.functions.getLockedTokens(ursula1).call() assert next_lock == escrow.functions.getLockedTokens(ursula1, 1).call() assert total_previous_lock == escrow.functions.lockedPerPeriod(period - 1).call() assert total_lock == escrow.functions.lockedPerPeriod(period).call() assert 0 == escrow.functions.lockedPerPeriod(period + 1).call() - assert alice1_balance + BASE_PENALTY / REWARD_COEFFICIENT == token.functions.balanceOf(alice1).call() + assert alice1_balance + base_penalty / reward_coefficient == token.functions.balanceOf(alice1).call() # Slash part of the one sub stake tokens_amount = escrow.functions.minerInfo(ursula2).call()[VALUE_FIELD] @@ -807,14 +801,14 @@ def test_all(testerchain, token, escrow, policy_manager, adjudicator, user_escro tx = adjudicator.functions.evaluateCFrag(*slashing_args).transact({'from': alice1}) testerchain.wait_for_receipt(tx) assert adjudicator.functions.evaluatedCFrags(data_hash).call() - assert lock - BASE_PENALTY == escrow.functions.minerInfo(ursula2).call()[VALUE_FIELD] + assert lock - base_penalty == escrow.functions.minerInfo(ursula2).call()[VALUE_FIELD] assert previous_lock == escrow.functions.getLockedTokensInPast(ursula2, 1).call() - assert lock - BASE_PENALTY == escrow.functions.getLockedTokens(ursula2).call() - assert next_lock - BASE_PENALTY == escrow.functions.getLockedTokens(ursula2, 1).call() + assert lock - base_penalty == escrow.functions.getLockedTokens(ursula2).call() + assert next_lock - base_penalty == escrow.functions.getLockedTokens(ursula2, 1).call() assert total_previous_lock == escrow.functions.lockedPerPeriod(period - 1).call() - assert total_lock - BASE_PENALTY == escrow.functions.lockedPerPeriod(period).call() + assert total_lock - base_penalty == escrow.functions.lockedPerPeriod(period).call() assert 0 == escrow.functions.lockedPerPeriod(period + 1).call() - assert alice1_balance + BASE_PENALTY == token.functions.balanceOf(alice1).call() + assert alice1_balance + base_penalty == token.functions.balanceOf(alice1).call() # Upgrade the adjudicator # Deploy the same contract as the second version @@ -822,11 +816,7 @@ def test_all(testerchain, token, escrow, policy_manager, adjudicator, user_escro adjudicator_v2, _ = testerchain.interface.deploy_contract( 'MiningAdjudicator', escrow.address, - ALGORITHM_SHA256, - BASE_PENALTY, - PENALTY_HISTORY_COEFFICIENT, - PERCENTAGE_PENALTY_COEFFICIENT, - REWARD_COEFFICIENT) + *slashing_economics.deployment_parameters) adjudicator_secret2 = os.urandom(SECRET_LENGTH) adjudicator_secret2_hash = testerchain.interface.w3.keccak(adjudicator_secret2) # Ursula and Alice can't upgrade library, only owner can @@ -900,7 +890,7 @@ def test_all(testerchain, token, escrow, policy_manager, adjudicator, user_escro tx = adjudicator.functions.evaluateCFrag(*slashing_args).transact({'from': alice2}) testerchain.wait_for_receipt(tx) assert adjudicator.functions.evaluatedCFrags(data_hash).call() - penalty = (2 * BASE_PENALTY + 3 * PENALTY_HISTORY_COEFFICIENT) + penalty = (2 * base_penalty + 3 * penalty_history_coefficient) assert lock - penalty == escrow.functions.minerInfo(ursula1).call()[VALUE_FIELD] assert previous_lock == escrow.functions.getLockedTokensInPast(ursula1, 1).call() assert lock - penalty == escrow.functions.getLockedTokens(ursula1).call() @@ -908,7 +898,7 @@ def test_all(testerchain, token, escrow, policy_manager, adjudicator, user_escro assert total_previous_lock == escrow.functions.lockedPerPeriod(period - 1).call() assert total_lock - penalty == escrow.functions.lockedPerPeriod(period).call() assert 0 == escrow.functions.lockedPerPeriod(period + 1).call() - assert alice2_balance + penalty / REWARD_COEFFICIENT == token.functions.balanceOf(alice2).call() + assert alice2_balance + penalty / reward_coefficient == token.functions.balanceOf(alice2).call() # Unlock and withdraw all tokens in MinersEscrow for index in range(9): diff --git a/tests/blockchain/eth/contracts/main/mining_adjudicator/conftest.py b/tests/blockchain/eth/contracts/main/mining_adjudicator/conftest.py index 9418c209d..09611e4df 100644 --- a/tests/blockchain/eth/contracts/main/mining_adjudicator/conftest.py +++ b/tests/blockchain/eth/contracts/main/mining_adjudicator/conftest.py @@ -34,11 +34,7 @@ def adjudicator_contract(testerchain, escrow, request, slashing_economics): contract, _ = testerchain.interface.deploy_contract( 'MiningAdjudicator', escrow.address, - slashing_economics.algorithm_sha256, - slashing_economics.base_penalty, - slashing_economics.penalty_history_coefficient, - slashing_economics.percentage_penalty_coefficient, - slashing_economics.reward_coefficient) + *slashing_economics.deployment_parameters) if request.param: secret = os.urandom(DispatcherDeployer.DISPATCHER_SECRET_LENGTH) diff --git a/tests/blockchain/eth/contracts/main/mining_adjudicator/test_mining_adjudicator.py b/tests/blockchain/eth/contracts/main/mining_adjudicator/test_mining_adjudicator.py index 8babf1671..5fd277fc9 100644 --- a/tests/blockchain/eth/contracts/main/mining_adjudicator/test_mining_adjudicator.py +++ b/tests/blockchain/eth/contracts/main/mining_adjudicator/test_mining_adjudicator.py @@ -18,24 +18,21 @@ along with nucypher. If not, see . import os -import coincurve import pytest from cryptography.hazmat.backends.openssl import backend from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ec from eth_tester.exceptions import TransactionFailed -from eth_utils import to_canonical_address, to_checksum_address +from eth_utils import to_canonical_address from typing import Tuple from web3.contract import Contract from umbral import pre -from umbral.config import default_params from umbral.curvebn import CurveBN from umbral.keys import UmbralPrivateKey from umbral.point import Point -from umbral.random_oracles import hash_to_curvebn, ExtendedKeccak -from umbral.signing import Signature, Signer +from umbral.signing import Signer +from nucypher.crypto.utils import get_signature_recovery_value from nucypher.policy.models import IndisputableEvidence @@ -45,33 +42,8 @@ secret = (123456).to_bytes(32, byteorder='big') secret2 = (654321).to_bytes(32, byteorder='big') -def sign_data(data, umbral_privkey): - umbral_pubkey_bytes = umbral_privkey.get_pubkey().to_bytes(is_compressed=False) - - # Prepare hash of the data - hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) - hash_ctx.update(data) - data_hash = hash_ctx.finalize() - - # Sign data and calculate recoverable signature - cryptography_priv_key = umbral_privkey.to_cryptography_privkey() - signature_der_bytes = cryptography_priv_key.sign(data, ec.ECDSA(hashes.SHA256())) - signature = Signature.from_bytes(signature_der_bytes, der_encoded=True) - recoverable_signature = make_recoverable_signature(data_hash, signature, umbral_pubkey_bytes) - return recoverable_signature - - -def make_recoverable_signature(data_hash, signature, umbral_pubkey_bytes): - recoverable_signature = bytes(signature) + bytes([0]) - pubkey_bytes = coincurve.PublicKey.from_signature_and_message(recoverable_signature, data_hash, hasher=None) \ - .format(compressed=False) - if pubkey_bytes != umbral_pubkey_bytes: - recoverable_signature = bytes(signature) + bytes([1]) - return recoverable_signature - - # TODO: Obtain real re-encryption metadata. Maybe constructing a WorkOrder and obtaining a response. -def fragments(metadata): +def mock_ursula_reencrypts(ursula_privkey, corrupt_cfrag: bool = False): delegating_privkey = UmbralPrivateKey.gen_key() _symmetric_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey()) signing_privkey = UmbralPrivateKey.gen_key() @@ -86,8 +58,42 @@ def fragments(metadata): sign_delegating_key=False, sign_receiving_key=False) capsule.set_correctness_keys(delegating_privkey.get_pubkey(), pub_key_bob, signing_privkey.get_pubkey()) + + ursula_pubkey = ursula_privkey.get_pubkey() + ursula_pubkey_bytes = ursula_pubkey.to_bytes(is_compressed=False)[1:] + specification = bytes(capsule) + ursula_pubkey_bytes + bytes(20) + bytes(32) + + ursulas_signer = Signer(ursula_privkey) + bobs_signer = Signer(priv_key_bob) + task_signature = bytes(bobs_signer(specification)) + + metadata = bytes(ursulas_signer(task_signature)) + cfrag = pre.reencrypt(kfrags[0], capsule, metadata=metadata) - return capsule, cfrag + + if corrupt_cfrag: + cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve) + + cfrag_signature = bytes(ursulas_signer(bytes(cfrag))) + + bob_pubkey_bytes = pub_key_bob.to_bytes(is_compressed=False)[1:] + + cfrag_signature_v = get_signature_recovery_value(message=bytes(cfrag), + signature=cfrag_signature, + public_key=ursula_pubkey) + task_signature_v = get_signature_recovery_value(message=task_signature, + signature=metadata, + public_key=ursula_pubkey) + recovery_values = cfrag_signature_v + task_signature_v + + return capsule, cfrag, cfrag_signature, task_signature, recovery_values, bob_pubkey_bytes + + +def evaluation_hash(capsule, cfrag): + hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) + hash_ctx.update(bytes(capsule) + bytes(cfrag)) + data_hash = hash_ctx.finalize() + return data_hash @pytest.mark.slow @@ -113,7 +119,7 @@ def test_evaluate_cfrag(testerchain, escrow, adjudicator_contract, slashing_econ # Generate miner's Umbral key miner_umbral_private_key = UmbralPrivateKey.gen_key() - miner_umbral_public_key_bytes = miner_umbral_private_key.get_pubkey().to_bytes(is_compressed=False) + miner_umbral_public_key_bytes = miner_umbral_private_key.get_pubkey().to_bytes(is_compressed=False)[1:] # Sign Umbral public key using eth-key hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) @@ -124,43 +130,28 @@ def test_evaluate_cfrag(testerchain, escrow, adjudicator_contract, slashing_econ sig_key = provider.ethereum_tester.backend._key_lookup[address] signed_miner_umbral_public_key = bytes(sig_key.sign_msg_hash(miner_umbral_public_key_hash)) - # Prepare hash of the data - metadata = os.urandom(33) - capsule, cfrag = fragments(metadata) - + # Prepare evaluation data + capsule, cfrag, *other_stuff = mock_ursula_reencrypts(miner_umbral_private_key) + cfrag_signature, task_signature, recovery_values, bob_pubkey_bytes = other_stuff assert cfrag.verify_correctness(capsule) - capsule_bytes = capsule.to_bytes() - cfrag_bytes = cfrag.to_bytes() - - # Bob prepares supporting Evidence + # Investigator prepares supporting Evidence evidence = IndisputableEvidence(capsule, cfrag, ursula=None) evidence_data = evidence.precompute_values() assert len(evidence_data) == 20 * 32 + 32 + 20 + 1 - hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) - hash_ctx.update(capsule_bytes + cfrag_bytes) - data_hash = hash_ctx.finalize() + data_hash = evaluation_hash(capsule, cfrag) # This capsule and cFrag are not yet evaluated assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call() - # Generate requester's Umbral key - requester_umbral_private_key = UmbralPrivateKey.gen_key() - requester_umbral_public_key_bytes = requester_umbral_private_key.get_pubkey().to_bytes(is_compressed=False) - - # Sign capsule and cFrag - capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key) - capsule_signature_by_requester_and_miner = sign_data(capsule_signature_by_requester, miner_umbral_private_key) - cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key) - # Challenge using good data - args = (capsule_bytes, - capsule_signature_by_requester, - capsule_signature_by_requester_and_miner, - cfrag_bytes, - cfrag_signature_by_miner, - requester_umbral_public_key_bytes, + args = (bytes(capsule), + bytes(cfrag), + cfrag_signature, + task_signature, + recovery_values, + bob_pubkey_bytes, miner_umbral_public_key_bytes, signed_miner_umbral_public_key, evidence_data) @@ -183,39 +174,36 @@ def test_evaluate_cfrag(testerchain, escrow, adjudicator_contract, slashing_econ assert investigator == event_args['investigator'] assert event_args['correctness'] + ############################### # Test: Don't evaluate miner with data that already was checked + ############################### + # TODO: be more specific on the expected exception with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag(*args).transact() testerchain.wait_for_receipt(tx) + ############################### # Test: Ursula produces incorrect proof: - metadata = os.urandom(34) - capsule, cfrag = fragments(metadata) - capsule_bytes = capsule.to_bytes() - # Corrupt proof - cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve) - cfrag_bytes = cfrag.to_bytes() + ############################### + capsule, cfrag, *other_stuff = mock_ursula_reencrypts(miner_umbral_private_key, corrupt_cfrag=True) + cfrag_signature, task_signature, recovery_values, bob_pubkey_bytes = other_stuff assert not cfrag.verify_correctness(capsule) - hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) - hash_ctx.update(capsule_bytes + cfrag_bytes) - data_hash = hash_ctx.finalize() - capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key) - capsule_signature_by_requester_and_miner = sign_data(capsule_signature_by_requester, miner_umbral_private_key) - cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key) evidence = IndisputableEvidence(capsule, cfrag, ursula=None) evidence_data = evidence.precompute_values() - args = (capsule_bytes, - capsule_signature_by_requester, - capsule_signature_by_requester_and_miner, - cfrag_bytes, - cfrag_signature_by_miner, - requester_umbral_public_key_bytes, + args = (bytes(capsule), + bytes(cfrag), + cfrag_signature, + task_signature, + recovery_values, + bob_pubkey_bytes, miner_umbral_public_key_bytes, signed_miner_umbral_public_key, evidence_data) + data_hash = evaluation_hash(capsule, cfrag) assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call() + tx = adjudicator_contract.functions.evaluateCFrag(*args).transact({'from': investigator}) testerchain.wait_for_receipt(tx) number_of_evaluations += 1 @@ -243,18 +231,10 @@ def test_evaluate_cfrag(testerchain, escrow, adjudicator_contract, slashing_econ # Test: Bob produces wrong precomputed data ############################### - metadata = os.urandom(34) - capsule, cfrag = fragments(metadata) - capsule_bytes = capsule.to_bytes() - cfrag_bytes = cfrag.to_bytes() + capsule, cfrag, *other_stuff = mock_ursula_reencrypts(miner_umbral_private_key) + cfrag_signature, task_signature, recovery_values, bob_pubkey_bytes = other_stuff assert cfrag.verify_correctness(capsule) - hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) - hash_ctx.update(capsule_bytes + cfrag_bytes) - data_hash = hash_ctx.finalize() - capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key) - capsule_signature_by_requester_and_miner = sign_data(capsule_signature_by_requester, miner_umbral_private_key) - cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key) evidence = IndisputableEvidence(capsule, cfrag, ursula=None) evidence_data = evidence.precompute_values() @@ -265,16 +245,17 @@ def test_evaluate_cfrag(testerchain, escrow, adjudicator_contract, slashing_econ evidence_data[32:32+64] = random_point_bytes evidence_data = bytes(evidence_data) - args = (capsule_bytes, - capsule_signature_by_requester, - capsule_signature_by_requester_and_miner, - cfrag_bytes, - cfrag_signature_by_miner, - requester_umbral_public_key_bytes, + args = (bytes(capsule), + bytes(cfrag), + cfrag_signature, + task_signature, + recovery_values, + bob_pubkey_bytes, miner_umbral_public_key_bytes, signed_miner_umbral_public_key, evidence_data) + data_hash = evaluation_hash(capsule, cfrag) assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call() # Evaluation must fail since Bob precomputed wrong values @@ -292,34 +273,27 @@ def test_evaluate_cfrag(testerchain, escrow, adjudicator_contract, slashing_econ # Test: Second violation. Penalty is increased ############################### - # Prepare hash of the data - metadata = os.urandom(34) - capsule, cfrag = fragments(metadata) - capsule_bytes = capsule.to_bytes() - # Corrupt proof - cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve) - cfrag_bytes = cfrag.to_bytes() - hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) - hash_ctx.update(capsule_bytes + cfrag_bytes) - data_hash = hash_ctx.finalize() - capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key) - capsule_signature_by_requester_and_miner = sign_data(capsule_signature_by_requester, miner_umbral_private_key) - cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key) + capsule, cfrag, *other_stuff = mock_ursula_reencrypts(miner_umbral_private_key, corrupt_cfrag=True) + cfrag_signature, task_signature, recovery_values, bob_pubkey_bytes = other_stuff + evidence = IndisputableEvidence(capsule, cfrag, ursula=None) evidence_data = evidence.precompute_values() - args = [capsule_bytes, - capsule_signature_by_requester, - capsule_signature_by_requester_and_miner, - cfrag_bytes, - cfrag_signature_by_miner, - requester_umbral_public_key_bytes, + args = (bytes(capsule), + bytes(cfrag), + cfrag_signature, + task_signature, + recovery_values, + bob_pubkey_bytes, miner_umbral_public_key_bytes, signed_miner_umbral_public_key, - evidence_data] + evidence_data) + + data_hash = evaluation_hash(capsule, cfrag) assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call() worker_stake = escrow.functions.minerInfo(miner).call()[0] investigator_balance = escrow.functions.rewardInfo(investigator).call() + assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call() tx = adjudicator_contract.functions.evaluateCFrag(*args).transact({'from': investigator}) testerchain.wait_for_receipt(tx) @@ -350,33 +324,27 @@ def test_evaluate_cfrag(testerchain, escrow, adjudicator_contract, slashing_econ # Test: Third violation. Penalty reaches the maximum allowed ############################### - # Prepare corrupted data again - capsule, cfrag = fragments(metadata) - capsule_bytes = capsule.to_bytes() - # Corrupt proof - cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve) - cfrag_bytes = cfrag.to_bytes() - hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) - hash_ctx.update(capsule_bytes + cfrag_bytes) - data_hash = hash_ctx.finalize() - capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key) - capsule_signature_by_requester_and_miner = sign_data(capsule_signature_by_requester, miner_umbral_private_key) - cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key) + capsule, cfrag, *other_stuff = mock_ursula_reencrypts(miner_umbral_private_key, corrupt_cfrag=True) + cfrag_signature, task_signature, recovery_values, bob_pubkey_bytes = other_stuff + evidence = IndisputableEvidence(capsule, cfrag, ursula=None) evidence_data = evidence.precompute_values() - args = [capsule_bytes, - capsule_signature_by_requester, - capsule_signature_by_requester_and_miner, - cfrag_bytes, - cfrag_signature_by_miner, - requester_umbral_public_key_bytes, + args = (bytes(capsule), + bytes(cfrag), + cfrag_signature, + task_signature, + recovery_values, + bob_pubkey_bytes, miner_umbral_public_key_bytes, signed_miner_umbral_public_key, - evidence_data] + evidence_data) + + data_hash = evaluation_hash(capsule, cfrag) + assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call() worker_stake = escrow.functions.minerInfo(miner).call()[0] investigator_balance = escrow.functions.rewardInfo(investigator).call() - assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call() + tx = adjudicator_contract.functions.evaluateCFrag(*args).transact({'from': investigator}) testerchain.wait_for_receipt(tx) number_of_evaluations += 1 @@ -406,47 +374,40 @@ def test_evaluate_cfrag(testerchain, escrow, adjudicator_contract, slashing_econ ############## # Can't evaluate miner using broken signatures - wrong_args = args[:] - wrong_args[1] = capsule_signature_by_requester[1:] + wrong_args = list(args) + wrong_args[2] = cfrag_signature[1:] with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact() testerchain.wait_for_receipt(tx) - wrong_args = args[:] - wrong_args[2] = capsule_signature_by_requester_and_miner[1:] + + wrong_args = list(args) + wrong_args[3] = task_signature[1:] with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact() testerchain.wait_for_receipt(tx) - wrong_args = args[:] - wrong_args[4] = cfrag_signature_by_miner[1:] - with pytest.raises((TransactionFailed, ValueError)): - tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact() - testerchain.wait_for_receipt(tx) - wrong_args = args[:] + + wrong_args = list(args) wrong_args[7] = signed_miner_umbral_public_key[1:] with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact() testerchain.wait_for_receipt(tx) # Can't evaluate miner using wrong keys - wrong_args = args[:] - wrong_args[5] = UmbralPrivateKey.gen_key().get_pubkey().to_bytes(is_compressed=False) + wrong_args = list(args) + wrong_args[5] = UmbralPrivateKey.gen_key().get_pubkey().to_bytes(is_compressed=False)[1:] with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact() testerchain.wait_for_receipt(tx) - wrong_args = args[:] - wrong_args[6] = UmbralPrivateKey.gen_key().get_pubkey().to_bytes(is_compressed=False) + + wrong_args = list(args) + wrong_args[6] = UmbralPrivateKey.gen_key().get_pubkey().to_bytes(is_compressed=False)[1:] with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact() testerchain.wait_for_receipt(tx) # Can't use signature for another data - wrong_args = args[:] - wrong_args[0] = bytes(args[0][0] + 1) + args[0][1:] - with pytest.raises((TransactionFailed, ValueError)): - tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact() - testerchain.wait_for_receipt(tx) - wrong_args = args[:] - wrong_args[3] = bytes(args[3][0] + 1) + args[3][1:] + wrong_args = list(args) + wrong_args[1] = os.urandom(len(bytes(cfrag))) with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact() testerchain.wait_for_receipt(tx) @@ -455,7 +416,8 @@ def test_evaluate_cfrag(testerchain, escrow, adjudicator_contract, slashing_econ address = to_canonical_address(wrong_miner) sig_key = provider.ethereum_tester.backend._key_lookup[address] signed_wrong_miner_umbral_public_key = bytes(sig_key.sign_msg_hash(miner_umbral_public_key_hash)) - wrong_args = args[:] + + wrong_args = list(args) wrong_args[7] = signed_wrong_miner_umbral_public_key with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact() diff --git a/tests/metrics/estimate_gas.py b/tests/metrics/estimate_gas.py index bc73d6fa8..61930db2d 100755 --- a/tests/metrics/estimate_gas.py +++ b/tests/metrics/estimate_gas.py @@ -18,34 +18,29 @@ You should have received a copy of the GNU Affero General Public License along with nucypher. If not, see . """ -import coincurve +import io import json import os - -from cryptography.hazmat.primitives.asymmetric import ec -from eth_utils import to_canonical_address - -from nucypher.blockchain.economics import TokenEconomics -from nucypher.policy.models import IndisputableEvidence, Policy -from umbral import pre -from umbral.curvebn import CurveBN -from umbral.keys import UmbralPrivateKey -from umbral.signing import Signer, Signature - +import re import time from os.path import abspath, dirname -import io -import re - +from eth_utils import to_canonical_address from cryptography.hazmat.backends.openssl import backend from cryptography.hazmat.primitives import hashes from twisted.logger import globalLogPublisher, Logger, jsonFileLogObserver, ILogObserver from zope.interface import provider -from nucypher.blockchain.eth.agents import NucypherTokenAgent, MinerAgent, PolicyAgent, MiningAdjudicatorAgent -from nucypher.utilities.sandbox.blockchain import TesterBlockchain +from umbral import pre +from umbral.curvebn import CurveBN +from umbral.keys import UmbralPrivateKey +from umbral.signing import Signer +from nucypher.blockchain.economics import TokenEconomics +from nucypher.blockchain.eth.agents import NucypherTokenAgent, MinerAgent, PolicyAgent, MiningAdjudicatorAgent +from nucypher.crypto.utils import get_signature_recovery_value +from nucypher.policy.models import IndisputableEvidence, Policy +from nucypher.utilities.sandbox.blockchain import TesterBlockchain ALGORITHM_SHA256 = 1 TOKEN_ECONOMICS = TokenEconomics() @@ -121,26 +116,8 @@ class AnalyzeGas: # TODO organize support functions +# TODO: Repeated method in test_intercontract_integration def generate_args_for_slashing(testerchain, miner, corrupt: bool = True): - def sign_data(data, umbral_privkey): - umbral_pubkey_bytes = umbral_privkey.get_pubkey().to_bytes(is_compressed=False) - - # Prepare hash of the data - hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) - hash_ctx.update(data) - data_hash = hash_ctx.finalize() - - # Sign data and calculate recoverable signature - cryptography_priv_key = umbral_privkey.to_cryptography_privkey() - signature_der_bytes = cryptography_priv_key.sign(data, ec.ECDSA(hashes.SHA256())) - signature = Signature.from_bytes(signature_der_bytes, der_encoded=True) - recoverable_signature = bytes(signature) + bytes([0]) - pubkey_bytes = coincurve.PublicKey.from_signature_and_message(recoverable_signature, data_hash, hasher=None) \ - .format(compressed=False) - if pubkey_bytes != umbral_pubkey_bytes: - recoverable_signature = bytes(signature) + bytes([1]) - return recoverable_signature - delegating_privkey = UmbralPrivateKey.gen_key() _symmetric_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey()) signing_privkey = UmbralPrivateKey.gen_key() @@ -155,18 +132,35 @@ def generate_args_for_slashing(testerchain, miner, corrupt: bool = True): sign_delegating_key=False, sign_receiving_key=False) capsule.set_correctness_keys(delegating_privkey.get_pubkey(), pub_key_bob, signing_privkey.get_pubkey()) - cfrag = pre.reencrypt(kfrags[0], capsule, metadata=os.urandom(34)) - capsule_bytes = capsule.to_bytes() + + miner_umbral_private_key = UmbralPrivateKey.gen_key() + ursula_pubkey = miner_umbral_private_key.get_pubkey() + ursula_pubkey_bytes = ursula_pubkey.to_bytes(is_compressed=False)[1:] + specification = bytes(capsule) + ursula_pubkey_bytes + bytes(20) + bytes(32) + + ursulas_signer = Signer(miner_umbral_private_key) + bobs_signer = Signer(priv_key_bob) + task_signature = bytes(bobs_signer(specification)) + + metadata = bytes(ursulas_signer(task_signature)) + + cfrag = pre.reencrypt(kfrags[0], capsule, metadata=metadata) if corrupt: cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve) - cfrag_bytes = cfrag.to_bytes() - hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) - hash_ctx.update(capsule_bytes + cfrag_bytes) - requester_umbral_private_key = UmbralPrivateKey.gen_key() - requester_umbral_public_key_bytes = requester_umbral_private_key.get_pubkey().to_bytes(is_compressed=False) - capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key) - miner_umbral_private_key = UmbralPrivateKey.gen_key() - miner_umbral_public_key_bytes = miner_umbral_private_key.get_pubkey().to_bytes(is_compressed=False) + + cfrag_signature = bytes(ursulas_signer(bytes(cfrag))) + + bob_pubkey_bytes = pub_key_bob.to_bytes(is_compressed=False)[1:] + + cfrag_signature_v = get_signature_recovery_value(message=bytes(cfrag), + signature=cfrag_signature, + public_key=ursula_pubkey) + task_signature_v = get_signature_recovery_value(message=task_signature, + signature=metadata, + public_key=ursula_pubkey) + recovery_values = cfrag_signature_v + task_signature_v + + miner_umbral_public_key_bytes = miner_umbral_private_key.get_pubkey().to_bytes(is_compressed=False)[1:] # Sign Umbral public key using eth-key hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(miner_umbral_public_key_bytes) @@ -175,16 +169,15 @@ def generate_args_for_slashing(testerchain, miner, corrupt: bool = True): sig_key = testerchain.interface.provider.ethereum_tester.backend._key_lookup[address] signed_miner_umbral_public_key = bytes(sig_key.sign_msg_hash(miner_umbral_public_key_hash)) - capsule_signature_by_requester_and_miner = sign_data(capsule_signature_by_requester, miner_umbral_private_key) - cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key) evidence = IndisputableEvidence(capsule, cfrag, ursula=None) evidence_data = evidence.precompute_values() - return (capsule_bytes, - capsule_signature_by_requester, - capsule_signature_by_requester_and_miner, - cfrag_bytes, - cfrag_signature_by_miner, - requester_umbral_public_key_bytes, + + return (bytes(capsule), + bytes(cfrag), + cfrag_signature, + task_signature, + recovery_values, + bob_pubkey_bytes, miner_umbral_public_key_bytes, signed_miner_umbral_public_key, evidence_data)