Remove IndisputableEvidence and related functions and tests

pull/2699/head
Bogdan Opanchuk 2021-05-17 21:57:41 -07:00
parent 57df58b4a6
commit 26076b1d98
16 changed files with 31 additions and 1501 deletions

View File

@ -68,7 +68,6 @@ from nucypher.types import (
StakerInfo,
PeriodDelta,
StakingEscrowParameters,
Evidence
)
from nucypher.utilities.logging import Logger # type: ignore
@ -915,7 +914,7 @@ class AdjudicatorAgent(EthereumContractAgent):
_proxy_name: str = DISPATCHER_CONTRACT_NAME
@contract_api(TRANSACTION)
def evaluate_cfrag(self, evidence: Evidence, transacting_power: TransactingPower) -> TxReceipt:
def evaluate_cfrag(self, evidence, transacting_power: TransactingPower) -> TxReceipt:
"""Submits proof that a worker created wrong CFrag"""
payload: TxParams = {'gas': Wei(500_000)} # TODO #842: gas needed for use with geth.
contract_function: ContractFunction = self.contract.functions.evaluateCFrag(*evidence.evaluation_arguments())
@ -925,7 +924,7 @@ class AdjudicatorAgent(EthereumContractAgent):
return receipt
@contract_api(CONTRACT_CALL)
def was_this_evidence_evaluated(self, evidence: Evidence) -> bool:
def was_this_evidence_evaluated(self, evidence) -> bool:
data_hash: bytes = sha256_digest(evidence.task.capsule, evidence.task.cfrag)
result: bool = self.contract.functions.evaluatedCFrags(data_hash).call()
return result
@ -1003,7 +1002,7 @@ class WorkLockAgent(EthereumContractAgent):
def cancel_bid(self, transacting_power: TransactingPower) -> TxReceipt:
"""Cancel bid and refund deposited ETH."""
contract_function: ContractFunction = self.contract.functions.cancelBid()
receipt = self.blockchain.send_transaction(contract_function=contract_function,
receipt = self.blockchain.send_transaction(contract_function=contract_function,
transacting_power=transacting_power)
return receipt
@ -1012,7 +1011,7 @@ class WorkLockAgent(EthereumContractAgent):
"""Force refund to bidders who can get tokens more than maximum allowed."""
addresses = sorted(addresses, key=str.casefold)
contract_function: ContractFunction = self.contract.functions.forceRefund(addresses)
receipt = self.blockchain.send_transaction(contract_function=contract_function,
receipt = self.blockchain.send_transaction(contract_function=contract_function,
transacting_power=transacting_power)
return receipt

View File

@ -780,7 +780,7 @@ class Bob(Character):
def _reencrypt(self,
work_order: 'WorkOrder',
retain_cfrags: bool = False
) -> Tuple[bool, Union[List['IndisputableEvidence'], List['CapsuleFrag']]]:
) -> Tuple[bool, Union[List['Ursula'], List['CapsuleFrag']]]:
if work_order.completed:
raise TypeError(
@ -811,10 +811,8 @@ class Bob(Character):
for capsule, pre_task in work_order.tasks.items():
if not pre_task.cfrag.verify_correctness(capsule):
# TODO: WARNING - This block is untested.
from nucypher.policy.collections import IndisputableEvidence
evidence = IndisputableEvidence(task=pre_task, work_order=work_order)
# I got a lot of problems with you people ...
the_airing_of_grievances.append(evidence)
the_airing_of_grievances.append(work_order.ursula)
if the_airing_of_grievances:
return False, the_airing_of_grievances

View File

@ -19,7 +19,6 @@ from coincurve import PublicKey
from eth_keys import KeyAPI as EthKeyAPI
from typing import Any, Union
from umbral.keys import UmbralPublicKey
from umbral.point import Point
from umbral.signing import Signature
from nucypher.crypto.api import keccak_digest
@ -42,49 +41,13 @@ def construct_policy_id(label: bytes, stamp: bytes) -> bytes:
return keccak_digest(label + stamp)
def canonical_address_from_umbral_key(public_key: UmbralPublicKey) -> bytes:
def canonical_address_from_umbral_key(public_key: Union[UmbralPublicKey, SignatureStamp]) -> bytes:
pubkey_raw_bytes = get_coordinates_as_bytes(public_key)
eth_pubkey = EthKeyAPI.PublicKey(pubkey_raw_bytes)
canonical_address = eth_pubkey.to_canonical_address()
return canonical_address
def recover_pubkey_from_signature(message: bytes,
signature: Union[bytes, Signature],
v_value_to_try: int,
is_prehashed: bool = False) -> bytes:
"""
Recovers a serialized, compressed public key from a signature.
It allows to specify a potential v value, in which case it assumes the signature
has the traditional (r,s) raw format. If a v value is not present, it assumes
the signature has the recoverable format (r, s, v).
:param message: Signed message
:param signature: The signature from which the pubkey is recovered
:param v_value_to_try: A potential v value to try
:param is_prehashed: True if the message is already pre-hashed. Default is False, and message will be hashed with SHA256
:return: The compressed byte-serialized representation of the recovered public key
"""
signature = bytes(signature)
expected_signature_size = Signature.expected_bytes_length()
if not len(signature) == expected_signature_size:
raise ValueError(f"The signature size should be {expected_signature_size} B.")
if v_value_to_try in (0, 1, 27, 28):
if v_value_to_try >= 27:
v_value_to_try -= 27
signature = signature + v_value_to_try.to_bytes(1, 'big')
else:
raise ValueError("Wrong v value. It should be 0, 1, 27 or 28.")
kwargs = dict(hasher=None) if is_prehashed else {}
pubkey = PublicKey.from_signature_and_message(serialized_sig=signature,
message=message,
**kwargs)
return pubkey.format(compressed=True)
def get_signature_recovery_value(message: bytes,
signature: Union[bytes, Signature],
public_key: Union[bytes, UmbralPublicKey],
@ -117,7 +80,7 @@ def get_signature_recovery_value(message: bytes,
"Either the message, the signature or the public key is not correct")
def get_coordinates_as_bytes(point: Union[Point, UmbralPublicKey, SignatureStamp],
def get_coordinates_as_bytes(point: UmbralPublicKey,
x_coord=True,
y_coord=True) -> bytes:
if isinstance(point, SignatureStamp):

View File

@ -31,7 +31,6 @@ from cryptography.hazmat.primitives import hashes
from eth_utils import to_canonical_address, to_checksum_address
from typing import Optional, Tuple
from umbral.config import default_params
from umbral.curvebn import CurveBN
from umbral.keys import UmbralPublicKey
from umbral.pre import Capsule
@ -46,8 +45,6 @@ from nucypher.crypto.splitters import capsule_splitter, key_splitter
from nucypher.crypto.splitters import cfrag_splitter
from nucypher.crypto.utils import (
canonical_address_from_umbral_key,
get_coordinates_as_bytes,
get_signature_recovery_value
)
from nucypher.network.middleware import RestMiddleware
@ -569,172 +566,3 @@ class Revocation:
raise InvalidSignature(
"Revocation has an invalid signature: {}".format(self.signature))
return True
# TODO: Change name to EvaluationEvidence
class IndisputableEvidence:
def __init__(self,
task: 'WorkOrder.Task',
work_order: 'WorkOrder',
delegating_pubkey: UmbralPublicKey = None,
receiving_pubkey: UmbralPublicKey = None,
verifying_pubkey: UmbralPublicKey = None,
) -> None:
self.task = task
self.ursula_pubkey = work_order.ursula.stamp.as_umbral_pubkey()
self.ursula_identity_evidence = work_order.ursula.decentralized_identity_evidence
self.bob_verifying_key = work_order.bob.stamp.as_umbral_pubkey()
self.blockhash = work_order.blockhash
self.alice_address = work_order.alice_address
keys = self.task.capsule.get_correctness_keys()
key_types = ("delegating", "receiving", "verifying")
if all(keys[key_type] for key_type in key_types):
self.delegating_pubkey = keys["delegating"]
self.receiving_pubkey = keys["receiving"]
self.verifying_pubkey = keys["verifying"]
elif all((delegating_pubkey, receiving_pubkey, verifying_pubkey)):
self.delegating_pubkey = delegating_pubkey
self.receiving_pubkey = receiving_pubkey
self.verifying_pubkey = verifying_pubkey
else:
raise ValueError("All correctness keys are required to compute evidence. "
"Either pass them as arguments or in the capsule.")
# TODO: check that the metadata is correct.
def get_proof_challenge_scalar(self) -> CurveBN:
capsule = self.task.capsule
cfrag = self.task.cfrag
umbral_params = default_params()
e, v, _ = capsule.components()
e1 = cfrag.point_e1
v1 = cfrag.point_v1
e2 = cfrag.proof.point_e2
v2 = cfrag.proof.point_v2
u = umbral_params.u
u1 = cfrag.proof.point_kfrag_commitment
u2 = cfrag.proof.point_kfrag_pok
metadata = cfrag.proof.metadata
from umbral.random_oracles import hash_to_curvebn, ExtendedKeccak
hash_input = (e, e1, e2, v, v1, v2, u, u1, u2, metadata)
h = hash_to_curvebn(*hash_input, params=umbral_params, hash_class=ExtendedKeccak)
return h
def precompute_values(self) -> bytes:
capsule = self.task.capsule
cfrag = self.task.cfrag
umbral_params = default_params()
e, v, _ = capsule.components()
e1 = cfrag.point_e1
v1 = cfrag.point_v1
e2 = cfrag.proof.point_e2
v2 = cfrag.proof.point_v2
u = umbral_params.u
u1 = cfrag.proof.point_kfrag_commitment
u2 = cfrag.proof.point_kfrag_pok
metadata = cfrag.proof.metadata
h = self.get_proof_challenge_scalar()
e1h = h * e1
v1h = h * v1
u1h = h * u1
z = cfrag.proof.bn_sig
ez = z * e
vz = z * v
uz = z * u
only_y_coord = dict(x_coord=False, y_coord=True)
# E points
e_y = get_coordinates_as_bytes(e, **only_y_coord)
ez_xy = get_coordinates_as_bytes(ez)
e1_y = get_coordinates_as_bytes(e1, **only_y_coord)
e1h_xy = get_coordinates_as_bytes(e1h)
e2_y = get_coordinates_as_bytes(e2, **only_y_coord)
# V points
v_y = get_coordinates_as_bytes(v, **only_y_coord)
vz_xy = get_coordinates_as_bytes(vz)
v1_y = get_coordinates_as_bytes(v1, **only_y_coord)
v1h_xy = get_coordinates_as_bytes(v1h)
v2_y = get_coordinates_as_bytes(v2, **only_y_coord)
# U points
uz_xy = get_coordinates_as_bytes(uz)
u1_y = get_coordinates_as_bytes(u1, **only_y_coord)
u1h_xy = get_coordinates_as_bytes(u1h)
u2_y = get_coordinates_as_bytes(u2, **only_y_coord)
# Get hashed KFrag validity message
hash_function = hashes.Hash(hashes.SHA256(), backend=backend)
kfrag_id = cfrag.kfrag_id
precursor = cfrag.point_precursor
delegating_pubkey = self.delegating_pubkey
receiving_pubkey = self.receiving_pubkey
validity_input = (kfrag_id, delegating_pubkey, receiving_pubkey, u1, precursor)
kfrag_validity_message = bytes().join(bytes(item) for item in validity_input)
hash_function.update(kfrag_validity_message)
hashed_kfrag_validity_message = hash_function.finalize()
# Get KFrag signature's v value
kfrag_signature_v = get_signature_recovery_value(message=hashed_kfrag_validity_message,
signature=cfrag.proof.kfrag_signature,
public_key=self.verifying_pubkey,
is_prehashed=True)
cfrag_signature_v = get_signature_recovery_value(message=bytes(cfrag),
signature=self.task.cfrag_signature,
public_key=self.ursula_pubkey)
metadata_signature_v = get_signature_recovery_value(message=self.task.signature,
signature=metadata,
public_key=self.ursula_pubkey)
specification = self.task.get_specification(ursula_pubkey=self.ursula_pubkey,
alice_address=self.alice_address,
blockhash=self.blockhash,
ursula_identity_evidence=self.ursula_identity_evidence)
specification_signature_v = get_signature_recovery_value(message=specification,
signature=self.task.signature,
public_key=self.bob_verifying_key)
ursula_pubkey_prefix_byte = bytes(self.ursula_pubkey)[0:1]
# Bundle everything together
pieces = (
e_y, ez_xy, e1_y, e1h_xy, e2_y,
v_y, vz_xy, v1_y, v1h_xy, v2_y,
uz_xy, u1_y, u1h_xy, u2_y,
hashed_kfrag_validity_message,
self.alice_address,
# The following single-byte values are interpreted as a single bytes5 variable by the Solidity contract
kfrag_signature_v,
cfrag_signature_v,
metadata_signature_v,
specification_signature_v,
ursula_pubkey_prefix_byte,
)
return b''.join(pieces)
def evaluation_arguments(self) -> Tuple:
return (bytes(self.task.capsule),
bytes(self.task.cfrag),
bytes(self.task.cfrag_signature),
bytes(self.task.signature),
get_coordinates_as_bytes(self.bob_verifying_key),
get_coordinates_as_bytes(self.ursula_pubkey),
bytes(self.ursula_identity_evidence),
self.precompute_values()
)

View File

@ -26,7 +26,6 @@ Work = NewType("Work", int)
Agent = TypeVar('Agent', bound='EthereumContractAgent')
Period = NewType('Period', int)
PeriodDelta = NewType('PeriodDelta', int)
Evidence = TypeVar('Evidence', bound='IndisputableEvidence')
ContractReturnValue = TypeVar('ContractReturnValue', bound=Union[TxReceipt, Wei, int, str, bool])

View File

@ -1,108 +0,0 @@
"""
This file is part of nucypher.
nucypher is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
nucypher is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from umbral.keys import UmbralPrivateKey
from umbral.signing import Signer
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.crypto.powers import TransactingPower
from nucypher.blockchain.eth.actors import Investigator, Staker
from nucypher.blockchain.eth.agents import ContractAgency, NucypherTokenAgent, StakingEscrowAgent
from nucypher.blockchain.eth.constants import NULL_ADDRESS
from nucypher.blockchain.eth.token import NU
from nucypher.crypto.signing import SignatureStamp
from tests.constants import INSECURE_DEVELOPMENT_PASSWORD
def mock_ursula(testerchain, account, mocker):
ursula_privkey = UmbralPrivateKey.gen_key()
ursula_stamp = SignatureStamp(verifying_key=ursula_privkey.pubkey,
signer=Signer(ursula_privkey))
signed_stamp = testerchain.client.sign_message(account=account,
message=bytes(ursula_stamp))
ursula = mocker.Mock(stamp=ursula_stamp, decentralized_identity_evidence=signed_stamp)
return ursula
def test_investigator_requests_slashing(testerchain,
test_registry,
agency,
mock_ursula_reencrypts,
token_economics,
mocker):
staker_account = testerchain.staker_account(0)
worker_account = testerchain.ursula_account(0)
##### STAKING ESCROW STUFF #####
token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=test_registry)
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
locked_tokens = token_economics.minimum_allowed_locked * 5
# The staker receives an initial amount of tokens
tpower = TransactingPower(account=testerchain.etherbase_account, signer=Web3Signer(testerchain.client))
_txhash = token_agent.transfer(amount=locked_tokens,
target_address=staker_account,
transacting_power=tpower)
# Deposit: The staker deposits tokens in the StakingEscrow contract.
staker_tpower = TransactingPower(account=staker_account, signer=Web3Signer(testerchain.client))
staker = Staker(transacting_power=staker_tpower,
domain=TEMPORARY_DOMAIN,
registry=test_registry)
staker.initialize_stake(amount=NU(locked_tokens, 'NuNit'),
lock_periods=token_economics.minimum_locked_periods)
assert staker.locked_tokens(periods=1) == locked_tokens
# The staker hasn't bond a worker yet
assert NULL_ADDRESS == staking_agent.get_worker_from_staker(staker_address=staker_account)
_txhash = staking_agent.bond_worker(transacting_power=staker_tpower, worker_address=worker_account)
assert worker_account == staking_agent.get_worker_from_staker(staker_address=staker_account)
assert staker_account == staking_agent.get_staker_from_worker(worker_address=worker_account)
###### END OF STAKING ESCROW STUFF ####
bob_account = testerchain.bob_account
bob_tpower = TransactingPower(account=bob_account, signer=Web3Signer(testerchain.client))
investigator = Investigator(registry=test_registry,
transacting_power=bob_tpower,
domain=TEMPORARY_DOMAIN)
ursula = mock_ursula(testerchain, worker_account, mocker=mocker)
# Let's create a bad cfrag
evidence = mock_ursula_reencrypts(ursula, corrupt_cfrag=True)
assert not investigator.was_this_evidence_evaluated(evidence)
bobby_old_balance = investigator.token_balance
investigator.request_evaluation(evidence=evidence)
assert investigator.was_this_evidence_evaluated(evidence)
investigator_reward = investigator.token_balance - bobby_old_balance
assert investigator_reward > 0
assert investigator_reward == token_economics.base_penalty / token_economics.reward_coefficient
assert staker.locked_tokens(periods=1) < locked_tokens

View File

@ -1,113 +0,0 @@
"""
This file is part of nucypher.
nucypher is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
nucypher is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from umbral.keys import UmbralPrivateKey
from umbral.signing import Signer
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.blockchain.eth.actors import NucypherTokenActor, Staker
from nucypher.blockchain.eth.agents import (
AdjudicatorAgent,
StakingEscrowAgent,
ContractAgency,
NucypherTokenAgent
)
from nucypher.blockchain.eth.constants import NULL_ADDRESS
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.blockchain.eth.token import NU
from nucypher.crypto.powers import TransactingPower
from nucypher.crypto.signing import SignatureStamp
def mock_ursula(testerchain, account, mocker):
ursula_privkey = UmbralPrivateKey.gen_key()
ursula_stamp = SignatureStamp(verifying_key=ursula_privkey.pubkey,
signer=Signer(ursula_privkey))
signed_stamp = testerchain.client.sign_message(account=account,
message=bytes(ursula_stamp))
ursula = mocker.Mock(stamp=ursula_stamp, decentralized_identity_evidence=signed_stamp)
return ursula
def test_adjudicator_slashes(agency,
testerchain,
mock_ursula_reencrypts,
token_economics,
test_registry,
mocker):
staker_account = testerchain.staker_account(0)
worker_account = testerchain.ursula_account(0)
##### STAKING ESCROW STUFF #####
token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=test_registry)
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=test_registry)
locked_tokens = token_economics.minimum_allowed_locked * 5
# The staker receives an initial amount of tokens
tpower = TransactingPower(account=testerchain.etherbase_account, signer=Web3Signer(testerchain.client))
_txhash = token_agent.transfer(amount=locked_tokens,
target_address=staker_account,
transacting_power=tpower)
# Deposit: The staker deposits tokens in the StakingEscrow contract.
tpower = TransactingPower(account=staker_account, signer=Web3Signer(testerchain.client))
staker = Staker(domain=TEMPORARY_DOMAIN,
registry=test_registry,
transacting_power=tpower)
staker.initialize_stake(amount=NU(locked_tokens, 'NuNit'),
lock_periods=token_economics.minimum_locked_periods)
assert staker.locked_tokens(periods=1) == locked_tokens
# The staker hasn't bond a worker yet
assert NULL_ADDRESS == staking_agent.get_worker_from_staker(staker_address=staker_account)
_txhash = staking_agent.bond_worker(transacting_power=tpower, worker_address=worker_account)
assert worker_account == staking_agent.get_worker_from_staker(staker_address=staker_account)
assert staker_account == staking_agent.get_staker_from_worker(worker_address=worker_account)
###### END OF STAKING ESCROW STUFF ####
adjudicator_agent = AdjudicatorAgent(registry=test_registry)
bob_account = testerchain.bob_account
bobby = NucypherTokenActor(checksum_address=bob_account,
domain=TEMPORARY_DOMAIN,
registry=test_registry)
ursula = mock_ursula(testerchain, worker_account, mocker=mocker)
# Let's create a bad cfrag
evidence = mock_ursula_reencrypts(ursula, corrupt_cfrag=True)
assert not adjudicator_agent.was_this_evidence_evaluated(evidence)
bobby_old_balance = bobby.token_balance
bob_tpower = TransactingPower(account=bob_account, signer=Web3Signer(testerchain.client))
adjudicator_agent.evaluate_cfrag(evidence=evidence, transacting_power=bob_tpower)
assert adjudicator_agent.was_this_evidence_evaluated(evidence)
investigator_reward = bobby.token_balance - bobby_old_balance
assert investigator_reward > 0
assert investigator_reward == token_economics.base_penalty / token_economics.reward_coefficient
assert staker.locked_tokens(periods=1) < locked_tokens

View File

@ -205,14 +205,6 @@ def mock_ursula(testerchain, account, mocker):
return ursula
# TODO organize support functions
def generate_args_for_slashing(mock_ursula_reencrypts, ursula):
evidence = mock_ursula_reencrypts(ursula, corrupt_cfrag=True)
args = list(evidence.evaluation_arguments())
data_hash = sha256_digest(evidence.task.capsule, evidence.task.cfrag)
return data_hash, args
@pytest.fixture(scope='module')
def staking_interface(testerchain, token, escrow, policy_manager, worklock, deploy_contract):
# Creator deploys the staking interface
@ -1023,115 +1015,6 @@ def test_upgrading_and_rollback(testerchain,
assert staking_interface_v2.address == staking_interface_router.functions.target().call()
def test_slashing(testerchain,
token_economics,
token,
escrow,
adjudicator,
preallocation_escrow_1,
mock_ursula_reencrypts,
mocker):
creator, staker1, staker2, staker3, staker4, alice1, alice2, *contracts_owners =\
testerchain.client.accounts
ursula1_with_stamp = mock_ursula(testerchain, staker1, mocker=mocker)
ursula2_with_stamp = mock_ursula(testerchain, staker2, mocker=mocker)
ursula3_with_stamp = mock_ursula(testerchain, staker3, mocker=mocker)
# Slash stakers
# Make a commitment to two periods
tx = escrow.functions.commitToNextPeriod().transact({'from': staker1})
testerchain.wait_for_receipt(tx)
tx = escrow.functions.commitToNextPeriod().transact({'from': staker2})
testerchain.wait_for_receipt(tx)
tx = escrow.functions.commitToNextPeriod().transact({'from': staker3})
testerchain.wait_for_receipt(tx)
testerchain.time_travel(hours=1)
tx = escrow.functions.commitToNextPeriod().transact({'from': staker1})
testerchain.wait_for_receipt(tx)
tx = escrow.functions.commitToNextPeriod().transact({'from': staker2})
testerchain.wait_for_receipt(tx)
tx = escrow.functions.commitToNextPeriod().transact({'from': staker3})
testerchain.wait_for_receipt(tx)
testerchain.time_travel(hours=1)
# Can't slash directly using the escrow contract
with pytest.raises((TransactionFailed, ValueError)):
tx = escrow.functions.slashStaker(staker1, 100, alice1, 10).transact()
testerchain.wait_for_receipt(tx)
# Slash part of the free amount of tokens
current_period = escrow.functions.getCurrentPeriod().call()
tokens_amount = escrow.functions.getAllTokens(staker1).call()
previous_lock = escrow.functions.getLockedTokensInPast(staker1, 1).call()
lock = escrow.functions.getLockedTokens(staker1, 0).call()
next_lock = escrow.functions.getLockedTokens(staker1, 1).call()
total_previous_lock = escrow.functions.lockedPerPeriod(current_period - 1).call()
total_lock = escrow.functions.lockedPerPeriod(current_period).call()
alice1_balance = token.functions.balanceOf(alice1).call()
algorithm_sha256, base_penalty, *coefficients = token_economics.slashing_deployment_parameters
penalty_history_coefficient, percentage_penalty_coefficient, reward_coefficient = coefficients
data_hash, slashing_args = generate_args_for_slashing(mock_ursula_reencrypts, ursula1_with_stamp)
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.getAllTokens(staker1).call()
assert previous_lock == escrow.functions.getLockedTokensInPast(staker1, 1).call()
assert lock == escrow.functions.getLockedTokens(staker1, 0).call()
assert next_lock == escrow.functions.getLockedTokens(staker1, 1).call()
assert total_previous_lock == escrow.functions.lockedPerPeriod(current_period - 1).call()
assert total_lock == escrow.functions.lockedPerPeriod(current_period).call()
assert 0 == escrow.functions.lockedPerPeriod(current_period + 1).call()
assert alice1_balance + base_penalty / reward_coefficient == token.functions.balanceOf(alice1).call()
# Slash part of the one sub stake
tokens_amount = escrow.functions.getAllTokens(staker2).call()
unlocked_amount = tokens_amount - escrow.functions.getLockedTokens(staker2, 0).call()
tx = escrow.functions.withdraw(unlocked_amount).transact({'from': staker2})
testerchain.wait_for_receipt(tx)
previous_lock = escrow.functions.getLockedTokensInPast(staker2, 1).call()
lock = escrow.functions.getLockedTokens(staker2, 0).call()
next_lock = escrow.functions.getLockedTokens(staker2, 1).call()
data_hash, slashing_args = generate_args_for_slashing(mock_ursula_reencrypts, ursula2_with_stamp)
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 lock - base_penalty == escrow.functions.getAllTokens(staker2).call()
assert previous_lock == escrow.functions.getLockedTokensInPast(staker2, 1).call()
assert lock - base_penalty == escrow.functions.getLockedTokens(staker2, 0).call()
assert next_lock - base_penalty == escrow.functions.getLockedTokens(staker2, 1).call()
assert total_previous_lock == escrow.functions.lockedPerPeriod(current_period - 1).call()
assert total_lock - base_penalty == escrow.functions.lockedPerPeriod(current_period).call()
assert 0 == escrow.functions.lockedPerPeriod(current_period + 1).call()
assert alice1_balance + base_penalty == token.functions.balanceOf(alice1).call()
# Slash preallocation escrow
tokens_amount = escrow.functions.getAllTokens(preallocation_escrow_1.address).call()
previous_lock = escrow.functions.getLockedTokensInPast(preallocation_escrow_1.address, 1).call()
lock = escrow.functions.getLockedTokens(preallocation_escrow_1.address, 0).call()
next_lock = escrow.functions.getLockedTokens(preallocation_escrow_1.address, 1).call()
total_previous_lock = escrow.functions.lockedPerPeriod(current_period - 1).call()
total_lock = escrow.functions.lockedPerPeriod(current_period).call()
alice1_balance = token.functions.balanceOf(alice1).call()
data_hash, slashing_args = generate_args_for_slashing(mock_ursula_reencrypts, ursula3_with_stamp)
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.getAllTokens(preallocation_escrow_1.address).call()
assert previous_lock == escrow.functions.getLockedTokensInPast(preallocation_escrow_1.address, 1).call()
assert lock - base_penalty == escrow.functions.getLockedTokens(preallocation_escrow_1.address, 0).call()
assert next_lock - base_penalty == escrow.functions.getLockedTokens(preallocation_escrow_1.address, 1).call()
assert total_previous_lock == escrow.functions.lockedPerPeriod(current_period - 1).call()
assert total_lock - base_penalty == escrow.functions.lockedPerPeriod(current_period).call()
assert 0 == escrow.functions.lockedPerPeriod(current_period + 1).call()
assert alice1_balance + base_penalty / reward_coefficient == token.functions.balanceOf(alice1).call()
def test_upgrading_adjudicator(testerchain,
token_economics,
escrow,
@ -1201,6 +1084,22 @@ def test_withdraw(testerchain,
preallocation_escrow_2):
staker1, staker2, staker3, staker4 = testerchain.client.accounts[1:5]
# Make a commitment to two periods
tx = escrow.functions.commitToNextPeriod().transact({'from': staker1})
testerchain.wait_for_receipt(tx)
tx = escrow.functions.commitToNextPeriod().transact({'from': staker2})
testerchain.wait_for_receipt(tx)
tx = escrow.functions.commitToNextPeriod().transact({'from': staker3})
testerchain.wait_for_receipt(tx)
testerchain.time_travel(hours=1)
tx = escrow.functions.commitToNextPeriod().transact({'from': staker1})
testerchain.wait_for_receipt(tx)
tx = escrow.functions.commitToNextPeriod().transact({'from': staker2})
testerchain.wait_for_receipt(tx)
tx = escrow.functions.commitToNextPeriod().transact({'from': staker3})
testerchain.wait_for_receipt(tx)
testerchain.time_travel(hours=1)
# Can't prolong stake by too low duration
with pytest.raises((TransactionFailed, ValueError)):
tx = escrow.functions.prolongStake(0, 1).transact({'from': staker2, 'gas_price': 0})

View File

@ -1,197 +0,0 @@
"""
This file is part of nucypher.
nucypher is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
nucypher is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import pytest
from eth_tester.exceptions import TransactionFailed
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 ExtendedKeccak, hash_to_curvebn
from umbral.signing import Signer
from nucypher.crypto.signing import SignatureStamp
@pytest.fixture(scope='module')
def reencryption_validator(testerchain, deploy_contract):
contract, _ = deploy_contract('ReEncryptionValidatorMock')
return contract
def test_extended_keccak_to_bn(testerchain, reencryption_validator):
test_data = os.urandom(40)
h = hash_to_curvebn(test_data, params=default_params(), hash_class=ExtendedKeccak)
assert int(h) == reencryption_validator.functions.extendedKeccakToBN(test_data).call()
def test_ec_point_operations(testerchain, reencryption_validator):
valid_point = Point.gen_rand()
x, y = valid_point.to_affine()
# Test isOnCurve
assert reencryption_validator.functions.isOnCurve(x, y).call()
bad_y = y - 1
assert not reencryption_validator.functions.isOnCurve(x, bad_y).call()
# Test checkCompressedPoint
sign = 2 + (y % 2)
assert reencryption_validator.functions.checkCompressedPoint(sign, x, y).call()
bad_sign = 3 - (y % 2)
assert not reencryption_validator.functions.checkCompressedPoint(bad_sign, x, y).call()
# Test checkSerializedCoordinates
coords = valid_point.to_bytes(is_compressed=False)[1:]
assert reencryption_validator.functions.checkSerializedCoordinates(coords).call()
coords = coords[:-1] + ((coords[-1] + 42) % 256).to_bytes(1, 'big')
assert not reencryption_validator.functions.checkSerializedCoordinates(coords).call()
# Test ecmulVerify
P = valid_point
scalar = CurveBN.gen_rand()
Q = scalar * P
qx, qy = Q.to_affine()
assert reencryption_validator.functions.ecmulVerify(x, y, int(scalar), qx, qy).call()
assert not reencryption_validator.functions.ecmulVerify(x, y, int(scalar), x, y).call()
# Test eqAffineJacobian
Q_affine = [qx, qy]
Q_jacobian = [qx, qy, 1]
assert reencryption_validator.functions.eqAffineJacobian(Q_affine, Q_jacobian).call()
P_jacobian = [x, y, 1]
assert not reencryption_validator.functions.eqAffineJacobian(Q_affine, P_jacobian).call()
point_at_infinity = [x, y, 0]
random_point = Point.gen_rand()
assert not reencryption_validator.functions.eqAffineJacobian(random_point.to_affine(), point_at_infinity).call()
# Test doubleJacobian
doubleP = reencryption_validator.functions.doubleJacobian(P_jacobian).call()
assert reencryption_validator.functions.eqAffineJacobian((P + P).to_affine(), doubleP).call()
# Test addAffineJacobian
scalar1 = CurveBN.gen_rand()
scalar2 = CurveBN.gen_rand()
R1 = scalar1 * P
R2 = scalar2 * P
assert R1 + R2 == (scalar1 + scalar2) * P
R = reencryption_validator.functions.addAffineJacobian(R1.to_affine(), R2.to_affine()).call()
assert reencryption_validator.functions.eqAffineJacobian((R1 + R2).to_affine(), R).call()
P_plus_P = reencryption_validator.functions.addAffineJacobian(P.to_affine(), P.to_affine()).call()
assert reencryption_validator.functions.eqAffineJacobian((P + P).to_affine(), P_plus_P).call()
def test_umbral_constants(testerchain, reencryption_validator):
umbral_params = default_params()
u_xcoord, u_ycoord = umbral_params.u.to_affine()
u_sign = 2 + (u_ycoord % 2)
assert u_sign == reencryption_validator.functions.UMBRAL_PARAMETER_U_SIGN().call()
assert u_xcoord == reencryption_validator.functions.UMBRAL_PARAMETER_U_XCOORD().call()
assert u_ycoord == reencryption_validator.functions.UMBRAL_PARAMETER_U_YCOORD().call()
secp256k1_field_order = 2**256 - 0x1000003D1
assert secp256k1_field_order == reencryption_validator.functions.FIELD_ORDER().call()
assert secp256k1_field_order - 2 == reencryption_validator.functions.MINUS_2().call()
assert (secp256k1_field_order - 1) // 2 == reencryption_validator.functions.MINUS_ONE_HALF().call()
def test_compute_proof_challenge_scalar(testerchain, reencryption_validator, mock_ursula_reencrypts, mocker):
ursula_privkey = UmbralPrivateKey.gen_key()
ursula_stamp = SignatureStamp(verifying_key=ursula_privkey.pubkey,
signer=Signer(ursula_privkey))
ursula = mocker.Mock(stamp=ursula_stamp, decentralized_identity_evidence=b'')
# Bob prepares supporting Evidence
evidence = mock_ursula_reencrypts(ursula)
capsule = evidence.task.capsule
cfrag = evidence.task.cfrag
capsule_bytes = capsule.to_bytes()
cfrag_bytes = cfrag.to_bytes()
proof_challenge_scalar = int(evidence.get_proof_challenge_scalar())
computeProofChallengeScalar = reencryption_validator.functions.computeProofChallengeScalar
assert proof_challenge_scalar == computeProofChallengeScalar(capsule_bytes, cfrag_bytes).call()
def test_validate_cfrag(testerchain, reencryption_validator, mock_ursula_reencrypts, mocker):
ursula_privkey = UmbralPrivateKey.gen_key()
ursula_stamp = SignatureStamp(verifying_key=ursula_privkey.pubkey,
signer=Signer(ursula_privkey))
ursula = mocker.Mock(stamp=ursula_stamp, decentralized_identity_evidence=b'')
###############################
# Test: Ursula produces correct proof:
###############################
# Bob prepares supporting Evidence
evidence = mock_ursula_reencrypts(ursula)
evidence_data = evidence.precompute_values()
assert len(evidence_data) == 20 * 32 + 32 + 20 + 5
# Challenge using good data
capsule = evidence.task.capsule
cfrag = evidence.task.cfrag
capsule_bytes = capsule.to_bytes()
cfrag_bytes = cfrag.to_bytes()
args = (capsule_bytes, cfrag_bytes, evidence_data)
assert reencryption_validator.functions.validateCFrag(*args).call()
###############################
# Test: Ursula produces incorrect proof:
###############################
evidence = mock_ursula_reencrypts(ursula, corrupt_cfrag=True)
capsule = evidence.task.capsule
cfrag = evidence.task.cfrag
capsule_bytes = capsule.to_bytes()
cfrag_bytes = cfrag.to_bytes()
assert not cfrag.verify_correctness(capsule)
evidence_data = evidence.precompute_values()
args = (capsule_bytes, cfrag_bytes, evidence_data)
assert not reencryption_validator.functions.validateCFrag(*args).call()
###############################
# Test: Bob produces wrong precomputed data
###############################
evidence = mock_ursula_reencrypts(ursula)
capsule = evidence.task.capsule
cfrag = evidence.task.cfrag
capsule_bytes = capsule.to_bytes()
cfrag_bytes = cfrag.to_bytes()
assert cfrag.verify_correctness(capsule)
evidence_data = evidence.precompute_values()
# Bob produces a random point and gets the bytes of coords x and y
random_point_bytes = Point.gen_rand().to_bytes(is_compressed=False)[1:]
# He uses this garbage instead of correct precomputation of z*E
evidence_data = bytearray(evidence_data)
evidence_data[32:32 + 64] = random_point_bytes
evidence_data = bytes(evidence_data)
args = (capsule_bytes, cfrag_bytes, evidence_data)
# Evaluation must fail since Bob precomputed wrong values
with pytest.raises(TransactionFailed):
_ = reencryption_validator.functions.validateCFrag(*args).call()

View File

@ -1,46 +0,0 @@
"""
This file is part of nucypher.
nucypher is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
nucypher is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
from web3.contract import Contract
from tests.constants import INSECURE_DEVELOPMENT_PASSWORD
@pytest.fixture()
def escrow(testerchain, deploy_contract):
escrow, _ = deploy_contract('StakingEscrowForAdjudicatorMock')
return escrow
@pytest.fixture(params=[False, True])
def adjudicator(testerchain, escrow, request, token_economics, deploy_contract):
contract, _ = deploy_contract(
'Adjudicator',
escrow.address,
*token_economics.slashing_deployment_parameters)
if request.param:
dispatcher, _ = deploy_contract('Dispatcher', contract.address)
# Deploy second version of the government contract
contract = testerchain.client.get_contract(
abi=contract.abi,
address=dispatcher.address,
ContractFactoryClass=Contract)
return contract

View File

@ -1,487 +0,0 @@
"""
This file is part of nucypher.
nucypher is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
nucypher is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import pytest
from eth_tester.exceptions import TransactionFailed
from typing import Tuple
from umbral.keys import UmbralPrivateKey
from umbral.point import Point
from web3.contract import Contract
from nucypher.crypto.api import sha256_digest
ALGORITHM_KECCAK256 = 0
ALGORITHM_SHA256 = 1
def test_evaluate_cfrag(testerchain,
escrow,
adjudicator,
token_economics,
blockchain_ursulas,
mock_ursula_reencrypts
):
ursula = list(blockchain_ursulas)[0]
creator, non_staker, investigator, *everyone_else = testerchain.client.accounts
evaluation_log = adjudicator.events.CFragEvaluated.createFilter(fromBlock='latest')
verdict_log = adjudicator.events.IncorrectCFragVerdict.createFilter(fromBlock='latest')
worker_stake = 1000
worker_penalty_history = 0
investigator_balance = 0
number_of_evaluations = 0
def compute_penalty_and_reward(stake: int, penalty_history: int) -> Tuple[int, int]:
penalty_ = token_economics.base_penalty
penalty_ += token_economics.penalty_history_coefficient * penalty_history
penalty_ = min(penalty_, stake // token_economics.percentage_penalty_coefficient)
reward_ = penalty_ // token_economics.reward_coefficient
return penalty_, reward_
# Prepare one staker
staker = ursula.checksum_address
tx = escrow.functions.setStakerInfo(staker, worker_stake, ursula.worker_address).transact()
testerchain.wait_for_receipt(tx)
assert ursula._stamp_has_valid_signature_by_worker()
# Prepare evaluation data
evidence = mock_ursula_reencrypts(ursula)
capsule = evidence.task.capsule
cfrag = evidence.task.cfrag
assert cfrag.verify_correctness(capsule)
evidence_data = evidence.precompute_values()
assert len(evidence_data) == 20 * 32 + 32 + 20 + 5
data_hash = sha256_digest(capsule, cfrag)
# This capsule and cFrag are not yet evaluated
assert not adjudicator.functions.evaluatedCFrags(data_hash).call()
args = list(evidence.evaluation_arguments())
# Challenge using good data
assert worker_stake == escrow.functions.getAllTokens(staker).call()
tx = adjudicator.functions.evaluateCFrag(*args).transact({'from': investigator})
testerchain.wait_for_receipt(tx)
number_of_evaluations += 1
# Hash of the data is saved and staker was not slashed
assert adjudicator.functions.evaluatedCFrags(data_hash).call()
assert worker_stake == escrow.functions.getAllTokens(staker).call()
assert investigator_balance == escrow.functions.rewardInfo(investigator).call()
events = evaluation_log.get_all_entries()
assert number_of_evaluations == len(events)
event_args = events[-1]['args']
assert data_hash == event_args['evaluationHash']
assert investigator == event_args['investigator']
assert event_args['correctness']
assert 0 == len(verdict_log.get_all_entries())
###############################
# Test: Don't evaluate staker with data that already was checked
###############################
with pytest.raises(TransactionFailed):
tx = adjudicator.functions.evaluateCFrag(*args).transact()
testerchain.wait_for_receipt(tx)
###############################
# Test: Ursula produces incorrect proof:
###############################
evidence = mock_ursula_reencrypts(ursula, corrupt_cfrag=True)
capsule = evidence.task.capsule
cfrag = evidence.task.cfrag
assert not cfrag.verify_correctness(capsule)
args = list(evidence.evaluation_arguments())
data_hash = sha256_digest(capsule, cfrag)
assert not adjudicator.functions.evaluatedCFrags(data_hash).call()
tx = adjudicator.functions.evaluateCFrag(*args).transact({'from': investigator})
testerchain.wait_for_receipt(tx)
number_of_evaluations += 1
# Hash of the data is saved and staker was slashed
assert adjudicator.functions.evaluatedCFrags(data_hash).call()
penalty, reward = compute_penalty_and_reward(worker_stake, worker_penalty_history)
worker_stake -= penalty
investigator_balance += reward
worker_penalty_history += 1
assert worker_stake == escrow.functions.getAllTokens(staker).call()
assert investigator_balance == escrow.functions.rewardInfo(investigator).call()
events = evaluation_log.get_all_entries()
assert number_of_evaluations == len(events)
event_args = events[-1]['args']
assert data_hash == event_args['evaluationHash']
assert investigator == event_args['investigator']
assert not event_args['correctness']
events = verdict_log.get_all_entries()
assert number_of_evaluations - 1 == len(events)
event_args = events[-1]['args']
assert data_hash == event_args['evaluationHash']
assert ursula.worker_address == event_args['worker']
assert staker == event_args['staker']
###############################
# Test: Bob produces wrong precomputed data
###############################
evidence = mock_ursula_reencrypts(ursula)
capsule = evidence.task.capsule
cfrag = evidence.task.cfrag
assert cfrag.verify_correctness(capsule)
# Bob produces a random point and gets the bytes of coords x and y
random_point_bytes = Point.gen_rand().to_bytes(is_compressed=False)[1:]
# He uses this garbage instead of correct precomputation of z*E
evidence_data = bytearray(evidence_data)
evidence_data[32:32+64] = random_point_bytes
evidence_data = bytes(evidence_data)
args = list(evidence.evaluation_arguments())
args[-1] = evidence_data
data_hash = sha256_digest(capsule, cfrag)
assert not adjudicator.functions.evaluatedCFrags(data_hash).call()
# Evaluation must fail since Bob precomputed wrong values
with pytest.raises(TransactionFailed):
tx = adjudicator.functions.evaluateCFrag(*args).transact({'from': investigator})
testerchain.wait_for_receipt(tx)
###############################
# Test: Second violation. Penalty is increased
###############################
evidence = mock_ursula_reencrypts(ursula, corrupt_cfrag=True)
capsule = evidence.task.capsule
cfrag = evidence.task.cfrag
assert not cfrag.verify_correctness(capsule)
args = list(evidence.evaluation_arguments())
data_hash = sha256_digest(capsule, cfrag)
assert not adjudicator.functions.evaluatedCFrags(data_hash).call()
worker_stake = escrow.functions.getAllTokens(staker).call()
investigator_balance = escrow.functions.rewardInfo(investigator).call()
assert not adjudicator.functions.evaluatedCFrags(data_hash).call()
tx = adjudicator.functions.evaluateCFrag(*args).transact({'from': investigator})
testerchain.wait_for_receipt(tx)
number_of_evaluations += 1
assert adjudicator.functions.evaluatedCFrags(data_hash).call()
previous_penalty = penalty
penalty, reward = compute_penalty_and_reward(worker_stake, worker_penalty_history)
# Penalty was increased because it's the second violation
assert penalty == previous_penalty + token_economics.penalty_history_coefficient
worker_stake -= penalty
investigator_balance += reward
worker_penalty_history += 1
assert worker_stake == escrow.functions.getAllTokens(staker).call()
assert investigator_balance == escrow.functions.rewardInfo(investigator).call()
events = evaluation_log.get_all_entries()
assert number_of_evaluations == len(events)
event_args = events[-1]['args']
assert data_hash == event_args['evaluationHash']
assert investigator == event_args['investigator']
assert not event_args['correctness']
events = verdict_log.get_all_entries()
assert number_of_evaluations - 1 == len(events)
event_args = events[-1]['args']
assert data_hash == event_args['evaluationHash']
assert ursula.worker_address == event_args['worker']
assert staker == event_args['staker']
###############################
# Test: Third violation. Penalty reaches the maximum allowed
###############################
evidence = mock_ursula_reencrypts(ursula, corrupt_cfrag=True)
capsule = evidence.task.capsule
cfrag = evidence.task.cfrag
assert not cfrag.verify_correctness(capsule)
args = list(evidence.evaluation_arguments())
data_hash = sha256_digest(capsule, cfrag)
assert not adjudicator.functions.evaluatedCFrags(data_hash).call()
worker_stake = escrow.functions.getAllTokens(staker).call()
investigator_balance = escrow.functions.rewardInfo(investigator).call()
tx = adjudicator.functions.evaluateCFrag(*args).transact({'from': investigator})
testerchain.wait_for_receipt(tx)
number_of_evaluations += 1
assert adjudicator.functions.evaluatedCFrags(data_hash).call()
penalty, reward = compute_penalty_and_reward(worker_stake, worker_penalty_history)
# Penalty has reached maximum available percentage of value
assert penalty == worker_stake // token_economics.percentage_penalty_coefficient
worker_stake -= penalty
investigator_balance += reward
worker_penalty_history += 1
assert worker_stake == escrow.functions.getAllTokens(staker).call()
assert investigator_balance == escrow.functions.rewardInfo(investigator).call()
events = evaluation_log.get_all_entries()
assert number_of_evaluations == len(events)
event_args = events[-1]['args']
assert data_hash == event_args['evaluationHash']
assert investigator == event_args['investigator']
assert not event_args['correctness']
events = verdict_log.get_all_entries()
assert number_of_evaluations - 1 == len(events)
event_args = events[-1]['args']
assert data_hash == event_args['evaluationHash']
assert ursula.worker_address == event_args['worker']
assert staker == event_args['staker']
#################
# Test: Invalid evaluations
##############
evidence = mock_ursula_reencrypts(ursula, corrupt_cfrag=True)
capsule = evidence.task.capsule
cfrag = evidence.task.cfrag
assert not cfrag.verify_correctness(capsule)
args = list(evidence.evaluation_arguments())
data_hash = sha256_digest(capsule, cfrag)
assert not adjudicator.functions.evaluatedCFrags(data_hash).call()
# Can't evaluate staker using broken signatures
wrong_args = list(args)
wrong_args[2] = bytes(evidence.task.cfrag_signature)[1:]
with pytest.raises(TransactionFailed):
tx = adjudicator.functions.evaluateCFrag(*wrong_args).transact()
testerchain.wait_for_receipt(tx)
wrong_args = list(args)
wrong_args[3] = evidence.task.signature[1:]
with pytest.raises(TransactionFailed):
tx = adjudicator.functions.evaluateCFrag(*wrong_args).transact()
testerchain.wait_for_receipt(tx)
wrong_args = list(args)
wrong_args[7] = wrong_args[7][1:]
with pytest.raises(TransactionFailed):
tx = adjudicator.functions.evaluateCFrag(*wrong_args).transact()
testerchain.wait_for_receipt(tx)
# Can't evaluate staker using wrong keys
wrong_args = list(args)
wrong_args[5] = UmbralPrivateKey.gen_key().get_pubkey().to_bytes(is_compressed=False)[1:]
with pytest.raises(TransactionFailed):
tx = adjudicator.functions.evaluateCFrag(*wrong_args).transact()
testerchain.wait_for_receipt(tx)
wrong_args = list(args)
wrong_args[6] = UmbralPrivateKey.gen_key().get_pubkey().to_bytes(is_compressed=False)[1:]
with pytest.raises(TransactionFailed):
tx = adjudicator.functions.evaluateCFrag(*wrong_args).transact()
testerchain.wait_for_receipt(tx)
# Can't use signature for another data
wrong_args = list(args)
wrong_args[1] = os.urandom(len(bytes(cfrag)))
with pytest.raises(TransactionFailed):
tx = adjudicator.functions.evaluateCFrag(*wrong_args).transact()
testerchain.wait_for_receipt(tx)
# Can't evaluate nonexistent staker
signed_stamp = testerchain.client.sign_message(account=non_staker,
message=bytes(ursula.stamp))
wrong_args = list(args)
wrong_args[7] = signed_stamp
with pytest.raises(TransactionFailed):
tx = adjudicator.functions.evaluateCFrag(*wrong_args).transact()
testerchain.wait_for_receipt(tx)
# Can't evaluate staker without tokens
tx = escrow.functions.setStakerInfo(non_staker, 0, ursula.worker_address).transact()
testerchain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = adjudicator.functions.evaluateCFrag(*wrong_args).transact()
testerchain.wait_for_receipt(tx)
# If the non-staker starts to stake, then she can be slashed
new_staker = non_staker
tx = escrow.functions.setStakerInfo(new_staker, worker_stake, ursula.worker_address).transact()
testerchain.wait_for_receipt(tx)
evidence = mock_ursula_reencrypts(ursula, corrupt_cfrag=True)
capsule = evidence.task.capsule
cfrag = evidence.task.cfrag
assert not cfrag.verify_correctness(capsule)
args = list(evidence.evaluation_arguments())
data_hash = sha256_digest(capsule, cfrag)
assert not adjudicator.functions.evaluatedCFrags(data_hash).call()
tx = adjudicator.functions.evaluateCFrag(*args).transact({'from': investigator})
testerchain.wait_for_receipt(tx)
number_of_evaluations += 1
assert adjudicator.functions.evaluatedCFrags(data_hash).call()
penalty, reward = compute_penalty_and_reward(worker_stake, worker_penalty_history)
worker_stake -= penalty
investigator_balance += reward
worker_penalty_history += 1
assert worker_stake == escrow.functions.getAllTokens(new_staker).call()
assert investigator_balance == escrow.functions.rewardInfo(investigator).call()
events = evaluation_log.get_all_entries()
assert number_of_evaluations == len(events)
event_args = events[-1]['args']
assert data_hash == event_args['evaluationHash']
assert investigator == event_args['investigator']
assert not event_args['correctness']
events = verdict_log.get_all_entries()
assert number_of_evaluations - 1 == len(events)
event_args = events[-1]['args']
assert data_hash == event_args['evaluationHash']
assert ursula.worker_address == event_args['worker']
assert new_staker == event_args['staker']
def test_upgrading(testerchain, deploy_contract):
creator = testerchain.client.accounts[0]
# Only escrow contract is allowed in Adjudicator constructor
with pytest.raises((TransactionFailed, ValueError)):
deploy_contract('Adjudicator', creator, ALGORITHM_KECCAK256, 1, 2, 3, 4)
# Deploy contracts
escrow1, _ = deploy_contract('StakingEscrowForAdjudicatorMock')
escrow2, _ = deploy_contract('StakingEscrowForAdjudicatorMock')
address1 = escrow1.address
address2 = escrow2.address
contract_library_v1, _ = deploy_contract(
'Adjudicator', address1, ALGORITHM_KECCAK256, 1, 2, 3, 4)
dispatcher, _ = deploy_contract('Dispatcher', contract_library_v1.address)
# Deploy second version of the contract
contract_library_v2, _ = deploy_contract(
'AdjudicatorV2Mock', address2, ALGORITHM_SHA256, 5, 6, 7, 8)
contract = testerchain.client.get_contract(
abi=contract_library_v2.abi,
address=dispatcher.address,
ContractFactoryClass=Contract)
# Can't call `finishUpgrade` and `verifyState` methods outside upgrade lifecycle
with pytest.raises((TransactionFailed, ValueError)):
tx = contract_library_v1.functions.finishUpgrade(contract.address).transact({'from': creator})
testerchain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = contract_library_v1.functions.verifyState(contract.address).transact({'from': creator})
testerchain.wait_for_receipt(tx)
# Upgrade to the second version
assert address1 == contract.functions.escrow().call()
assert ALGORITHM_KECCAK256 == contract.functions.hashAlgorithm().call()
assert 1 == contract.functions.basePenalty().call()
assert 2 == contract.functions.penaltyHistoryCoefficient().call()
assert 3 == contract.functions.percentagePenaltyCoefficient().call()
assert 4 == contract.functions.rewardCoefficient().call()
tx = dispatcher.functions.upgrade(contract_library_v2.address).transact({'from': creator})
testerchain.wait_for_receipt(tx)
# Check constructor and storage values
assert contract_library_v2.address == dispatcher.functions.target().call()
assert address2 == contract.functions.escrow().call()
assert ALGORITHM_SHA256 == contract.functions.hashAlgorithm().call()
assert 5 == contract.functions.basePenalty().call()
assert 6 == contract.functions.penaltyHistoryCoefficient().call()
assert 7 == contract.functions.percentagePenaltyCoefficient().call()
assert 8 == contract.functions.rewardCoefficient().call()
# Check new ABI
tx = contract.functions.setValueToCheck(3).transact({'from': creator})
testerchain.wait_for_receipt(tx)
assert 3 == contract.functions.valueToCheck().call()
# Can't upgrade to the previous version or to the bad version
contract_library_bad, _ = deploy_contract('AdjudicatorBad')
with pytest.raises(TransactionFailed):
tx = dispatcher.functions.upgrade(contract_library_v1.address).transact({'from': creator})
testerchain.wait_for_receipt(tx)
with pytest.raises(TransactionFailed):
tx = dispatcher.functions.upgrade(contract_library_bad.address).transact({'from': creator})
testerchain.wait_for_receipt(tx)
# But can rollback
tx = dispatcher.functions.rollback().transact({'from': creator})
testerchain.wait_for_receipt(tx)
assert contract_library_v1.address == dispatcher.functions.target().call()
assert address1 == contract.functions.escrow().call()
assert ALGORITHM_KECCAK256 == contract.functions.hashAlgorithm().call()
assert 1 == contract.functions.basePenalty().call()
assert 2 == contract.functions.penaltyHistoryCoefficient().call()
assert 3 == contract.functions.percentagePenaltyCoefficient().call()
assert 4 == contract.functions.rewardCoefficient().call()
# After rollback new ABI is unavailable
with pytest.raises(TransactionFailed):
tx = contract.functions.setValueToCheck(2).transact({'from': creator})
testerchain.wait_for_receipt(tx)
# Try to upgrade to the bad version
with pytest.raises(TransactionFailed):
tx = dispatcher.functions.upgrade(contract_library_bad.address).transact({'from': creator})
testerchain.wait_for_receipt(tx)
events = dispatcher.events.StateVerified.createFilter(fromBlock=0).get_all_entries()
assert 4 == len(events)
event_args = events[0]['args']
assert contract_library_v1.address == event_args['testTarget']
assert creator == event_args['sender']
event_args = events[1]['args']
assert contract_library_v2.address == event_args['testTarget']
assert creator == event_args['sender']
assert event_args == events[2]['args']
event_args = events[3]['args']
assert contract_library_v2.address == event_args['testTarget']
assert creator == event_args['sender']
events = dispatcher.events.UpgradeFinished.createFilter(fromBlock=0).get_all_entries()
assert 3 == len(events)
event_args = events[0]['args']
assert contract_library_v1.address == event_args['target']
assert creator == event_args['sender']
event_args = events[1]['args']
assert contract_library_v2.address == event_args['target']
assert creator == event_args['sender']
event_args = events[2]['args']
assert contract_library_v1.address == event_args['target']
assert creator == event_args['sender']

View File

@ -52,11 +52,6 @@ from nucypher.policy.policies import Policy
from nucypher.utilities.logging import Logger
from tests.utils.blockchain import TesterBlockchain
try:
from tests.utils.ursula import _mock_ursula_reencrypts as mock_ursula_reencrypts
except ImportError:
raise DevelopmentInstallationRequired(importable_name='tests.utils.ursula')
ALGORITHM_SHA256 = 1
TOKEN_ECONOMICS = StandardTokenEconomics()
@ -146,12 +141,6 @@ def mock_ursula(testerchain, account):
return ursula
def generate_args_for_slashing(ursula, corrupt_cfrag: bool = True):
evidence = mock_ursula_reencrypts(ursula, corrupt_cfrag=corrupt_cfrag)
args = list(evidence.evaluation_arguments())
return args
def estimate_gas(analyzer: AnalyzeGas = None) -> None:
"""
Execute a linear sequence of NyCypher transactions mimicking
@ -520,45 +509,8 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
testerchain.time_travel(periods=1)
transact(staker_functions.commitToNextPeriod(), {'from': staker1})
#
# Slashing tests
#
testerchain.time_travel(periods=1)
#
# Slashing
#
slashing_args = generate_args_for_slashing(ursula_with_stamp)
transact_and_log("Slash just value", adjudicator_functions.evaluateCFrag(*slashing_args), {'from': alice1})
deposit = staker_functions.stakerInfo(staker1).call()[0]
unlocked = deposit - staker_functions.getLockedTokens(staker1, 0).call()
transact(staker_functions.withdraw(unlocked), {'from': staker1})
sub_stakes_length = str(staker_functions.getSubStakesLength(staker1).call())
slashing_args = generate_args_for_slashing(ursula_with_stamp)
transact_and_log("Slashing one sub stake and saving old one (" + sub_stakes_length + " sub stakes), 1st",
adjudicator_functions.evaluateCFrag(*slashing_args),
{'from': alice1})
sub_stakes_length = str(staker_functions.getSubStakesLength(staker1).call())
slashing_args = generate_args_for_slashing(ursula_with_stamp)
transact_and_log("Slashing one sub stake and saving old one (" + sub_stakes_length + " sub stakes), 2nd",
adjudicator_functions.evaluateCFrag(*slashing_args),
{'from': alice1})
sub_stakes_length = str(staker_functions.getSubStakesLength(staker1).call())
slashing_args = generate_args_for_slashing(ursula_with_stamp)
transact_and_log("Slashing one sub stake and saving old one (" + sub_stakes_length + " sub stakes), 3rd",
adjudicator_functions.evaluateCFrag(*slashing_args),
{'from': alice1})
sub_stakes_length = str(staker_functions.getSubStakesLength(staker1).call())
slashing_args = generate_args_for_slashing(ursula_with_stamp)
transact_and_log("Slashing two sub stakes and saving old one (" + sub_stakes_length + " sub stakes)",
adjudicator_functions.evaluateCFrag(*slashing_args),
{'from': alice1})
for index in range(18):
transact(staker_functions.commitToNextPeriod(), {'from': staker1})
testerchain.time_travel(periods=1)
@ -568,21 +520,6 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
unlocked = deposit - staker_functions.getLockedTokens(staker1, 1).call()
transact(staker_functions.withdraw(unlocked), {'from': staker1})
sub_stakes_length = str(staker_functions.getSubStakesLength(staker1).call())
slashing_args = generate_args_for_slashing(ursula_with_stamp)
transact_and_log("Slashing two sub stakes, shortest and new one (" + sub_stakes_length + " sub stakes)",
adjudicator_functions.evaluateCFrag(*slashing_args),
{'from': alice1})
sub_stakes_length = str(staker_functions.getSubStakesLength(staker1).call())
slashing_args = generate_args_for_slashing(ursula_with_stamp)
transact_and_log("Slashing three sub stakes, two shortest and new one (" + sub_stakes_length + " sub stakes)",
adjudicator_functions.evaluateCFrag(*slashing_args),
{'from': alice1})
slashing_args = generate_args_for_slashing(ursula_with_stamp, corrupt_cfrag=False)
transact_and_log("Evaluating correct CFrag", adjudicator_functions.evaluateCFrag(*slashing_args), {'from': alice1})
transact_and_log("Prolong stake", staker_functions.prolongStake(0, 20), {'from': staker1})
transact_and_log("Merge sub-stakes", staker_functions.mergeStake(2, 3), {'from': staker1})

View File

@ -1,39 +0,0 @@
"""
This file is part of nucypher.
nucypher is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
nucypher is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
from umbral.keys import UmbralPrivateKey
from nucypher.crypto.signing import SignatureStamp
from nucypher.crypto.utils import get_coordinates_as_bytes
def test_coordinates_as_bytes():
pubkey = UmbralPrivateKey.gen_key().pubkey
point = pubkey.point_key
stamp = SignatureStamp(verifying_key=pubkey)
x, y = point.to_affine()
x = x.to_bytes(32, 'big')
y = y.to_bytes(32, 'big')
for p in (point, pubkey, stamp):
assert get_coordinates_as_bytes(p) == x + y
assert get_coordinates_as_bytes(p, x_coord=False) == y
assert get_coordinates_as_bytes(p, y_coord=False) == x
with pytest.raises(ValueError):
_ = get_coordinates_as_bytes(p, x_coord=False, y_coord=False)

View File

@ -1,94 +0,0 @@
"""
This file is part of nucypher.
nucypher is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
nucypher is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
from cryptography.hazmat.backends.openssl import backend
from cryptography.hazmat.primitives import hashes
from umbral.keys import UmbralPrivateKey
from nucypher.crypto.api import ecdsa_sign, verify_ecdsa
from nucypher.crypto.signing import Signature, Signer
from nucypher.crypto.utils import get_signature_recovery_value, recover_pubkey_from_signature
def test_signature_can_verify():
privkey = UmbralPrivateKey.gen_key()
message = b"peace at dawn"
der_sig_bytes = ecdsa_sign(message, privkey)
assert verify_ecdsa(message, der_sig_bytes, privkey.get_pubkey())
signature = Signature.from_bytes(der_sig_bytes, der_encoded=True)
assert signature.verify(message, privkey.get_pubkey())
def test_signature_rs_serialization():
privkey = UmbralPrivateKey.gen_key()
message = b"peace at dawn"
der_sig_bytes = ecdsa_sign(message, privkey)
signature_from_der = Signature.from_bytes(der_sig_bytes, der_encoded=True)
rs_sig_bytes = bytes(signature_from_der)
assert len(rs_sig_bytes) == 64
signature_from_rs = Signature.from_bytes(rs_sig_bytes, der_encoded=False)
assert signature_from_rs == signature_from_der
assert signature_from_rs == der_sig_bytes
assert signature_from_rs.verify(message, privkey.get_pubkey())
@pytest.mark.parametrize('execution_number', range(100)) # Run this test 100 times.
def test_ecdsa_signature_recovery(execution_number):
privkey = UmbralPrivateKey.gen_key()
pubkey = privkey.get_pubkey()
signer = Signer(private_key=privkey)
message = b"peace at dawn"
signature = signer(message=message)
assert signature.verify(message, pubkey)
v_value = 27
pubkey_bytes = recover_pubkey_from_signature(message=message,
signature=signature,
v_value_to_try=v_value)
if not pubkey_bytes == pubkey.to_bytes():
v_value = 28
pubkey_bytes = recover_pubkey_from_signature(message=message,
signature=signature,
v_value_to_try=v_value)
assert pubkey_bytes == pubkey.to_bytes()
assert bytes([v_value - 27]) == get_signature_recovery_value(message, signature, pubkey)
hash_function = hashes.Hash(hashes.SHA256(), backend=backend)
hash_function.update(message)
prehashed_message = hash_function.finalize()
v_value = 27
pubkey_bytes = recover_pubkey_from_signature(message=prehashed_message,
signature=signature,
v_value_to_try=v_value,
is_prehashed=True)
if not pubkey_bytes == pubkey.to_bytes():
v_value = 28
pubkey_bytes = recover_pubkey_from_signature(message=prehashed_message,
signature=signature,
v_value_to_try=v_value,
is_prehashed=True)
assert pubkey_bytes == pubkey.to_bytes()
assert bytes([v_value - 27]) == get_signature_recovery_value(prehashed_message,
signature,
pubkey,
is_prehashed=True)

View File

@ -43,8 +43,9 @@ def ursula(mocker):
def test_pre_task(mock_ursula_reencrypts, ursula, get_random_checksum_address):
identity_evidence = ursula.decentralized_identity_evidence
evidence = mock_ursula_reencrypts(ursula)
capsule = evidence.task.capsule
task = mock_ursula_reencrypts(ursula)
cfrag = task.cfrag
capsule = task.capsule
capsule_bytes = capsule.to_bytes()
signature = ursula.stamp(capsule_bytes)
@ -61,7 +62,6 @@ def test_pre_task(mock_ursula_reencrypts, ursula, get_random_checksum_address):
assert signature == deserialized_task.signature
# Attaching cfrags to the task
cfrag = evidence.task.cfrag
cfrag_bytes = bytes(VariableLengthBytestring(cfrag.to_bytes()))
cfrag_signature = ursula.stamp(cfrag_bytes)
@ -101,7 +101,7 @@ def test_work_order_with_multiple_capsules(mock_ursula_reencrypts,
federated_alice,
number):
tasks = [mock_ursula_reencrypts(ursula).task for _ in range(number)]
tasks = [mock_ursula_reencrypts(ursula) for _ in range(number)]
material = [(task.capsule, task.signature, task.cfrag, task.cfrag_signature) for task in tasks]
capsules, signatures, cfrags, cfrag_signatures = zip(*material)

View File

@ -21,7 +21,6 @@ import socket
from cryptography.x509 import Certificate
from typing import Iterable, List, Optional, Set
from umbral import pre
from umbral.curvebn import CurveBN
from umbral.keys import UmbralPrivateKey
from umbral.signing import Signer
@ -31,7 +30,7 @@ from nucypher.characters.lawful import Bob
from nucypher.characters.lawful import Ursula
from nucypher.config.characters import UrsulaConfiguration
from nucypher.crypto.utils import canonical_address_from_umbral_key
from nucypher.policy.collections import WorkOrder, IndisputableEvidence
from nucypher.policy.collections import WorkOrder
from tests.constants import NUMBER_OF_URSULAS_IN_DEVELOPMENT_NETWORK
from tests.mock.datastore import MOCK_DB
@ -165,7 +164,7 @@ MOCK_KNOWN_URSULAS_CACHE = dict()
MOCK_URSULA_STARTING_PORT = 51000 # select_test_port()
def _mock_ursula_reencrypts(ursula, corrupt_cfrag: bool = False):
def _mock_ursula_reencrypts(ursula):
delegating_privkey = UmbralPrivateKey.gen_key()
_symmetric_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey())
signing_privkey = UmbralPrivateKey.gen_key()
@ -199,15 +198,7 @@ def _mock_ursula_reencrypts(ursula, corrupt_cfrag: bool = False):
metadata = bytes(ursula.stamp(task_signature))
cfrag = pre.reencrypt(kfrags[0], capsule, metadata=metadata)
if corrupt_cfrag:
cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve)
cfrag_signature = ursula.stamp(bytes(cfrag))
bob = Bob.from_public_keys(verifying_key=pub_key_bob)
task = WorkOrder.PRETask(capsule, task_signature, cfrag, cfrag_signature)
work_order = WorkOrder(bob, None, alice_address, {capsule: task}, None, ursula, blockhash)
evidence = IndisputableEvidence(task, work_order)
return evidence
return WorkOrder.PRETask(capsule, task_signature, cfrag, cfrag_signature)