mirror of https://github.com/nucypher/nucypher.git
Remove IndisputableEvidence and related functions and tests
parent
57df58b4a6
commit
26076b1d98
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
|
|
|
@ -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])
|
||||
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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})
|
||||
|
|
|
@ -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()
|
|
@ -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
|
|
@ -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']
|
|
@ -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})
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue