def test_bad_capsule_fails_reencryption(kfrags): params = default_params() bollocks_capsule = Capsule(params, point_e=Point.gen_rand(), point_v=Point.gen_rand(), bn_sig=CurveBN.gen_rand()) for kfrag in kfrags: with pytest.raises(Capsule.NotValid): pre.reencrypt(kfrag, bollocks_capsule)
def gen_key( cls, params: Optional[UmbralParameters] = None) -> 'UmbralPrivateKey': """ Generates a private key and returns it. """ if params is None: params = default_params() bn_key = CurveBN.gen_rand(params.curve) return cls(bn_key, params)
def test_capsule_equality(): params = default_params() one_capsule = Capsule(params, point_e=Point.gen_rand(), point_v=Point.gen_rand(), bn_sig=CurveBN.gen_rand()) another_capsule = Capsule(params, point_e=Point.gen_rand(), point_v=Point.gen_rand(), bn_sig=CurveBN.gen_rand()) assert one_capsule != another_capsule activated_capsule = Capsule(params, point_e_prime=Point.gen_rand(), point_v_prime=Point.gen_rand(), point_noninteractive=Point.gen_rand()) assert activated_capsule != one_capsule
def _encapsulate(alice_pub_key: Point, key_length=32, params: UmbralParameters=None) -> Tuple[bytes, Capsule]: """Generates a symmetric key and its associated KEM ciphertext""" params = params if params is not None else default_params() g = params.g priv_r = CurveBN.gen_rand(params.curve) pub_r = priv_r * g priv_u = CurveBN.gen_rand(params.curve) pub_u = priv_u * g h = CurveBN.hash_to_bn(pub_r, pub_u, params=params) s = priv_u + (priv_r * h) shared_key = (priv_r + priv_u) * alice_pub_key # Key to be used for symmetric encryption key = kdf(shared_key, key_length) return key, Capsule(point_e=pub_r, point_v=pub_u, bn_sig=s)
def _encapsulate(alice_pubkey: UmbralPublicKey, key_length: int = DEM_KEYSIZE) -> Tuple[bytes, Capsule]: """Generates a symmetric key and its associated KEM ciphertext""" params = alice_pubkey.params g = params.g priv_r = CurveBN.gen_rand(params.curve) pub_r = priv_r * g priv_u = CurveBN.gen_rand(params.curve) pub_u = priv_u * g h = CurveBN.hash(pub_r, pub_u, params=params) s = priv_u + (priv_r * h) shared_key = (priv_r + priv_u) * alice_pubkey.point_key # Key to be used for symmetric encryption key = kdf(shared_key, key_length) return key, Capsule(point_e=pub_r, point_v=pub_u, bn_sig=s, params=params)
def test_cannot_create_capsule_from_bogus_material(alices_keys): params = alices_keys[0].params with pytest.raises(TypeError): _capsule_of_questionable_parentage = Capsule(params, point_e=Point.gen_rand(), point_v=42, bn_sig=CurveBN.gen_rand()) with pytest.raises(TypeError): _capsule_of_questionable_parentage = Capsule(params, point_e=Point.gen_rand(), point_v=Point.gen_rand(), bn_sig=42)
def test_bad_capsule_fails_reencryption(alices_keys, bobs_keys): delegating_privkey, _signing_privkey = alices_keys signer_alice = Signer(_signing_privkey) _receiving_privkey, receiving_pubkey = bobs_keys kfrags = pre.split_rekey(delegating_privkey, signer_alice, receiving_pubkey, 1, 2) bollocks_capsule = Capsule(point_e=Point.gen_rand(), point_v=Point.gen_rand(), bn_sig=CurveBN.gen_rand()) with pytest.raises(Capsule.NotValid): pre.reencrypt(kfrags[0], bollocks_capsule)
def prove_correctness(self, capsule, kfrag, metadata: Optional[bytes] = None): params = capsule.params # Check correctness of original ciphertext if not capsule.verify(): raise capsule.NotValid("Capsule verification failed.") rk = kfrag.bn_key t = CurveBN.gen_rand(params.curve) #### # Here are the formulaic constituents shared with `verify_correctness`. #### e = capsule.point_e v = capsule.point_v e1 = self.point_e1 v1 = self.point_v1 u = params.u u1 = kfrag.point_commitment e2 = t * e # type: Any v2 = t * v # type: Any u2 = t * u # type: Any hash_input = [e, e1, e2, v, v1, v2, u, u1, u2] if metadata is not None: hash_input.append(metadata) h = hash_to_curvebn(*hash_input, params=params, hash_class=ExtendedKeccak) ######## z3 = t + h * rk self.attach_proof(e2, v2, u1, u2, metadata=metadata, z3=z3, kfrag_signature=kfrag.signature_for_bob)
def gen_rand(cls, curve: Optional[Curve] = None) -> 'Point': """ Returns a Point object with a cryptographically secure EC_POINT based on the provided curve. """ curve = curve if curve is not None else default_curve() rand_point = openssl._get_new_EC_POINT(curve) rand_bn = CurveBN.gen_rand(curve).bignum with backend._tmp_bn_ctx() as bn_ctx: res = backend._lib.EC_POINT_mul(curve.ec_group, rand_point, backend._ffi.NULL, curve.generator, rand_bn, bn_ctx) backend.openssl_assert(res == 1) return cls(rand_point, curve)
def test_capsule_creation(alices_keys): with pytest.raises(TypeError): rare_capsule = Capsule() # Alice cannot make a capsule this way. # Some users may create capsules their own way. custom_capsule = Capsule(point_e=Point.gen_rand(), point_v=Point.gen_rand(), bn_sig=CurveBN.gen_rand()) assert isinstance(custom_capsule, Capsule) # Typical Alice, constructing a typical capsule _, alices_public_key = alices_keys plaintext = b'peace at dawn' ciphertext, typical_capsule = pre.encrypt(alices_public_key, plaintext) assert isinstance(typical_capsule, Capsule)
def _mock_ursula_reencrypts(ursula, corrupt_cfrag: bool = False): delegating_privkey = UmbralPrivateKey.gen_key() _symmetric_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey()) signing_privkey = UmbralPrivateKey.gen_key() signing_pubkey = signing_privkey.get_pubkey() signer = Signer(signing_privkey) priv_key_bob = UmbralPrivateKey.gen_key() pub_key_bob = priv_key_bob.get_pubkey() kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey, signer=signer, receiving_pubkey=pub_key_bob, threshold=2, N=4, sign_delegating_key=False, sign_receiving_key=False) capsule.set_correctness_keys(delegating_privkey.get_pubkey(), pub_key_bob, signing_pubkey) ursula_pubkey = ursula.stamp.as_umbral_pubkey() alice_address = canonical_address_from_umbral_key(signing_pubkey) blockhash = bytes(32) specification = b''.join((bytes(capsule), bytes(ursula_pubkey), bytes(ursula.decentralized_identity_evidence), alice_address, blockhash)) bobs_signer = Signer(priv_key_bob) task_signature = bytes(bobs_signer(specification)) 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 = bytes(ursula.stamp(bytes(cfrag))) bob = Bob.from_public_keys(verifying_key=pub_key_bob) task = WorkOrder.Task(capsule, task_signature, cfrag, cfrag_signature) work_order = WorkOrder(bob, None, alice_address, [task], None, ursula, blockhash) evidence = IndisputableEvidence(task, work_order) return evidence
def prove_cfrag_correctness( cfrag: "CapsuleFrag", kfrag: "KFrag", capsule: "Capsule", metadata: bytes = None, params: UmbralParameters = None) -> "CorrectnessProof": params = params if params is not None else default_params() rk = kfrag._bn_key t = CurveBN.gen_rand(params.curve) #### ## Here are the formulaic constituents shared with `assess_cfrag_correctness`. #### e = capsule._point_e v = capsule._point_v e1 = cfrag._point_e1 v1 = cfrag._point_v1 u = params.u u1 = kfrag._point_commitment e2 = t * e v2 = t * v u2 = t * u hash_input = (e, e1, e2, v, v1, v2, u, u1, u2) if metadata is not None: hash_input += (metadata, ) h = CurveBN.hash(*hash_input, params=params) ######## z3 = t + h * rk cfrag.attach_proof(e2, v2, u1, u2, metadata=metadata, z3=z3, kfrag_signature=kfrag.signature) # Check correctness of original ciphertext (check nº 2) at the end # to avoid timing oracles if not capsule.verify(): raise capsule.NotValid("Capsule verification failed.")
def test_cannot_attach_cfrag_without_keys(): """ We need the proper keys to verify the correctness of CFrags in order to attach them to a Capsule. """ capsule = Capsule(point_e=Point.gen_rand(), point_v=Point.gen_rand(), bn_sig=CurveBN.gen_rand()) cfrag = CapsuleFrag( point_e1=Point.gen_rand(), point_v1=Point.gen_rand(), kfrag_id=os.urandom(10), point_noninteractive=Point.gen_rand(), point_xcoord=Point.gen_rand(), ) with pytest.raises(TypeError): capsule.attach_cfrag(cfrag)
def _prove_correctness(cfrag: CapsuleFrag, kfrag: KFrag, capsule: Capsule, metadata: bytes=None, params: UmbralParameters=None ) -> CorrectnessProof: params = params if params is not None else default_params() e1 = cfrag._point_e1 v1 = cfrag._point_v1 e = capsule._point_e v = capsule._point_v u = params.u u1 = kfrag._point_commitment rk = kfrag._bn_key t = CurveBN.gen_rand(params.curve) e2 = t * e v2 = t * v u2 = t * u hash_input = [e, e1, e2, v, v1, v2, u, u1, u2] if metadata is not None: hash_input.append(metadata) h = CurveBN.hash_to_bn(*hash_input, params=params) z3 = t + h * rk cfrag.proof = CorrectnessProof(point_e2=e2, point_v2=v2, point_kfrag_commitment=u1, point_kfrag_pok=u2, bn_kfrag_sig1=kfrag._bn_sig1, bn_kfrag_sig2=kfrag._bn_sig2, bn_sig=z3, metadata=metadata) # Check correctness of original ciphertext (check nº 2) at the end # to avoid timing oracles if not capsule.verify(params): raise capsule.NotValid("Capsule verification failed.")
def test_cannot_create_capsule_from_bogus_material(alices_keys): with pytest.raises(TypeError): capsule_of_questionable_parentage = pre.Capsule(point_e=Point.gen_rand(), point_v=42, bn_sig=CurveBN.gen_rand()) with pytest.raises(TypeError): capsule_of_questionable_parentage = pre.Capsule(point_e=Point.gen_rand(), point_v=Point.gen_rand(), bn_sig=42) with pytest.raises(TypeError): capsule_of_questionable_parentage = pre.Capsule(point_e_prime=Point.gen_rand(), point_v_prime=42, point_noninteractive=Point.gen_rand()) with pytest.raises(TypeError): capsule_of_questionable_parentage = pre.Capsule(point_e_prime=Point.gen_rand(), point_v_prime=Point.gen_rand(), point_noninteractive=42)
def gen_rand(cls, curve: ec.EllipticCurve=None): """ Returns a Point object with a cryptographically secure EC_POINT based on the provided curve. """ curve = curve if curve is not None else default_curve() curve_nid = backend._elliptic_curve_to_nid(curve) group = openssl._get_ec_group_by_curve_nid(curve_nid) generator = openssl._get_ec_generator_by_curve_nid(curve_nid) rand_point = openssl._get_new_EC_POINT(ec_group=group) rand_bn = CurveBN.gen_rand(curve).bignum with backend._tmp_bn_ctx() as bn_ctx: res = backend._lib.EC_POINT_mul( group, rand_point, backend._ffi.NULL, generator, rand_bn, bn_ctx ) backend.openssl_assert(res == 1) return Point(rand_point, curve_nid, group)
def prove_cfrag_correctness(cfrag: 'CapsuleFrag', kfrag: 'KFrag', capsule: 'Capsule', metadata: Optional[bytes] = None ) -> None: params = capsule.params # Check correctness of original ciphertext if not capsule.verify(): raise capsule.NotValid("Capsule verification failed.") rk = kfrag._bn_key t = CurveBN.gen_rand(params.curve) #### # Here are the formulaic constituents shared with `assess_cfrag_correctness`. #### e = capsule._point_e v = capsule._point_v e1 = cfrag._point_e1 v1 = cfrag._point_v1 u = params.u u1 = kfrag._point_commitment e2 = t * e v2 = t * v u2 = t * u hash_input = [e, e1, e2, v, v1, v2, u, u1, u2] if metadata is not None: hash_input.append(metadata) h = CurveBN.hash(*hash_input, params=params) ######## z3 = t + h * rk cfrag.attach_proof(e2, v2, u1, u2, metadata=metadata, z3=z3, kfrag_signature=kfrag.signature_for_bob)
def test_cannot_set_different_keys(): """ Once a key is set on a Capsule, it can't be changed to a different key. """ capsule = Capsule(point_e=Point.gen_rand(), point_v=Point.gen_rand(), bn_sig=CurveBN.gen_rand()) capsule.set_correctness_keys( delegating=UmbralPrivateKey.gen_key().get_pubkey(), receiving=UmbralPrivateKey.gen_key().get_pubkey(), verifying=UmbralPrivateKey.gen_key().get_pubkey()) with pytest.raises(ValueError): capsule.set_correctness_keys( delegating=UmbralPrivateKey.gen_key().get_pubkey()) with pytest.raises(ValueError): capsule.set_correctness_keys( receiving=UmbralPrivateKey.gen_key().get_pubkey()) with pytest.raises(ValueError): capsule.set_correctness_keys( verifying=UmbralPrivateKey.gen_key().get_pubkey())
def test_evaluate_cfrag(testerchain, escrow, adjudicator_contract): creator, miner, wrong_miner, investigator, *everyone_else = testerchain.interface.w3.eth.accounts evaluation_log = adjudicator_contract.events.CFragEvaluated.createFilter( fromBlock='latest') worker_stake = 1000 worker_penalty_history = 0 investigator_balance = 0 number_of_evaluations = 0 # Prepare one miner tx = escrow.functions.setMinerInfo(miner, worker_stake).transact() testerchain.wait_for_receipt(tx) # Generate miner's Umbral key miner_umbral_private_key = UmbralPrivateKey.gen_key() miner_umbral_public_key_bytes = miner_umbral_private_key.get_pubkey( ).to_bytes(is_compressed=False) # Sign Umbral public key using eth-key hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(miner_umbral_public_key_bytes) miner_umbral_public_key_hash = hash_ctx.finalize() provider = testerchain.interface.provider address = to_canonical_address(miner) sig_key = provider.ethereum_tester.backend._key_lookup[address] signed_miner_umbral_public_key = bytes( sig_key.sign_msg_hash(miner_umbral_public_key_hash)) # Prepare hash of the data metadata = os.urandom(33) capsule, cfrag = fragments(metadata) assert cfrag.verify_correctness(capsule) capsule_bytes = capsule.to_bytes() cfrag_bytes = cfrag.to_bytes() # Bob prepares supporting Evidence evidence = IndisputableEvidence(capsule, cfrag, ursula=None) evidence_data = evidence.precompute_values() assert len(evidence_data) == 20 * 32 + 32 + 20 + 1 # This check is a workaround in order to test the signature validation is correct. # In reality, this check should be part of the isCapsuleFragCorrect logic, # but we're facing with "Stack too deep" errors at the moment. # address = adjudicator_contract.functions.aliceAddress(cfrag_bytes, evidence_data).call() # assert address == to_checksum_address(evidence_data[-21:-1].hex()) proof_challenge_scalar = int(evidence.get_proof_challenge_scalar()) # computeProofChallengeScalar = adjudicator_contract.functions.computeProofChallengeScalar # assert proof_challenge_scalar == computeProofChallengeScalar(capsule_bytes, cfrag_bytes).call() hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(capsule_bytes + cfrag_bytes) data_hash = hash_ctx.finalize() # This capsule and cFrag are not yet evaluated assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call() # Generate requester's Umbral key requester_umbral_private_key = UmbralPrivateKey.gen_key() requester_umbral_public_key_bytes = requester_umbral_private_key.get_pubkey( ).to_bytes(is_compressed=False) # Sign capsule and cFrag capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key) capsule_signature_by_requester_and_miner = sign_data( capsule_signature_by_requester, miner_umbral_private_key) cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key) # Challenge using good data args = (capsule_bytes, capsule_signature_by_requester, capsule_signature_by_requester_and_miner, cfrag_bytes, cfrag_signature_by_miner, requester_umbral_public_key_bytes, miner_umbral_public_key_bytes, signed_miner_umbral_public_key, evidence_data) assert worker_stake == escrow.functions.minerInfo(miner).call()[0] tx = adjudicator_contract.functions.evaluateCFrag(*args).transact( {'from': investigator}) testerchain.wait_for_receipt(tx) number_of_evaluations += 1 # Hash of the data is saved and miner was not slashed assert adjudicator_contract.functions.evaluatedCFrags(data_hash).call() assert worker_stake == escrow.functions.minerInfo(miner).call()[0] 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 miner == event_args['miner'] assert investigator == event_args['investigator'] assert event_args['correctness'] # Test: Don't evaluate miner with data that already was checked with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag(*args).transact() testerchain.wait_for_receipt(tx) # Test: Ursula produces incorrect proof: metadata = os.urandom(34) capsule, cfrag = fragments(metadata) capsule_bytes = capsule.to_bytes() # Corrupt proof cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve) cfrag_bytes = cfrag.to_bytes() assert not cfrag.verify_correctness(capsule) hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(capsule_bytes + cfrag_bytes) data_hash = hash_ctx.finalize() capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key) capsule_signature_by_requester_and_miner = sign_data( capsule_signature_by_requester, miner_umbral_private_key) cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key) evidence = IndisputableEvidence(capsule, cfrag, ursula=None) evidence_data = evidence.precompute_values() args = (capsule_bytes, capsule_signature_by_requester, capsule_signature_by_requester_and_miner, cfrag_bytes, cfrag_signature_by_miner, requester_umbral_public_key_bytes, miner_umbral_public_key_bytes, signed_miner_umbral_public_key, evidence_data) assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call() tx = adjudicator_contract.functions.evaluateCFrag(*args).transact( {'from': investigator}) testerchain.wait_for_receipt(tx) number_of_evaluations += 1 # Hash of the data is saved and miner was slashed assert adjudicator_contract.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.minerInfo(miner).call()[0] 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 miner == event_args['miner'] assert investigator == event_args['investigator'] assert not event_args['correctness'] ############################### # Test: Bob produces wrong precomputed data ############################### metadata = os.urandom(34) capsule, cfrag = fragments(metadata) capsule_bytes = capsule.to_bytes() cfrag_bytes = cfrag.to_bytes() assert cfrag.verify_correctness(capsule) hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(capsule_bytes + cfrag_bytes) data_hash = hash_ctx.finalize() capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key) capsule_signature_by_requester_and_miner = sign_data( capsule_signature_by_requester, miner_umbral_private_key) cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key) evidence = IndisputableEvidence(capsule, cfrag, ursula=None) evidence_data = evidence.precompute_values() # 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, capsule_signature_by_requester, capsule_signature_by_requester_and_miner, cfrag_bytes, cfrag_signature_by_miner, requester_umbral_public_key_bytes, miner_umbral_public_key_bytes, signed_miner_umbral_public_key, evidence_data) assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call() # Evaluation must fail since Bob precomputed wrong values with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag(*args).transact( {'from': investigator}) testerchain.wait_for_receipt(tx) ############################### # Test: Signatures and Metadata mismatch ############################### # TODO ############################### # Test: Second violation. Penalty is increased ############################### # Prepare hash of the data metadata = os.urandom(34) capsule, cfrag = fragments(metadata) capsule_bytes = capsule.to_bytes() # Corrupt proof cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve) cfrag_bytes = cfrag.to_bytes() hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(capsule_bytes + cfrag_bytes) data_hash = hash_ctx.finalize() capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key) capsule_signature_by_requester_and_miner = sign_data( capsule_signature_by_requester, miner_umbral_private_key) cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key) evidence = IndisputableEvidence(capsule, cfrag, ursula=None) evidence_data = evidence.precompute_values() args = [ capsule_bytes, capsule_signature_by_requester, capsule_signature_by_requester_and_miner, cfrag_bytes, cfrag_signature_by_miner, requester_umbral_public_key_bytes, miner_umbral_public_key_bytes, signed_miner_umbral_public_key, evidence_data ] assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call() worker_stake = escrow.functions.minerInfo(miner).call()[0] investigator_balance = escrow.functions.rewardInfo(investigator).call() assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call() tx = adjudicator_contract.functions.evaluateCFrag(*args).transact( {'from': investigator}) testerchain.wait_for_receipt(tx) number_of_evaluations += 1 assert adjudicator_contract.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 + PENALTY_HISTORY_COEFFICIENT worker_stake -= penalty investigator_balance += reward worker_penalty_history += 1 assert worker_stake == escrow.functions.minerInfo(miner).call()[0] 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 miner == event_args['miner'] assert investigator == event_args['investigator'] assert not event_args['correctness'] ############################### # Test: Third violation. Penalty reaches the maximum allowed ############################### # Prepare corrupted data again capsule, cfrag = fragments(metadata) capsule_bytes = capsule.to_bytes() # Corrupt proof cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve) cfrag_bytes = cfrag.to_bytes() hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(capsule_bytes + cfrag_bytes) data_hash = hash_ctx.finalize() capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key) capsule_signature_by_requester_and_miner = sign_data( capsule_signature_by_requester, miner_umbral_private_key) cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key) evidence = IndisputableEvidence(capsule, cfrag, ursula=None) evidence_data = evidence.precompute_values() args = [ capsule_bytes, capsule_signature_by_requester, capsule_signature_by_requester_and_miner, cfrag_bytes, cfrag_signature_by_miner, requester_umbral_public_key_bytes, miner_umbral_public_key_bytes, signed_miner_umbral_public_key, evidence_data ] worker_stake = escrow.functions.minerInfo(miner).call()[0] investigator_balance = escrow.functions.rewardInfo(investigator).call() assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call() tx = adjudicator_contract.functions.evaluateCFrag(*args).transact( {'from': investigator}) testerchain.wait_for_receipt(tx) number_of_evaluations += 1 assert adjudicator_contract.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 // PERCENTAGE_PENALTY_COEFFICIENT worker_stake -= penalty investigator_balance += reward worker_penalty_history += 1 assert worker_stake == escrow.functions.minerInfo(miner).call()[0] 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 miner == event_args['miner'] assert investigator == event_args['investigator'] assert not event_args['correctness'] ################# # Test: Invalid evaluations ############## # Can't evaluate miner using broken signatures wrong_args = args[:] wrong_args[1] = capsule_signature_by_requester[1:] with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag( *wrong_args).transact() testerchain.wait_for_receipt(tx) wrong_args = args[:] wrong_args[2] = capsule_signature_by_requester_and_miner[1:] with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag( *wrong_args).transact() testerchain.wait_for_receipt(tx) wrong_args = args[:] wrong_args[4] = cfrag_signature_by_miner[1:] with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag( *wrong_args).transact() testerchain.wait_for_receipt(tx) wrong_args = args[:] wrong_args[7] = signed_miner_umbral_public_key[1:] with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag( *wrong_args).transact() testerchain.wait_for_receipt(tx) # Can't evaluate miner using wrong keys wrong_args = args[:] wrong_args[5] = UmbralPrivateKey.gen_key().get_pubkey().to_bytes( is_compressed=False) with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag( *wrong_args).transact() testerchain.wait_for_receipt(tx) wrong_args = args[:] wrong_args[6] = UmbralPrivateKey.gen_key().get_pubkey().to_bytes( is_compressed=False) with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag( *wrong_args).transact() testerchain.wait_for_receipt(tx) # Can't use signature for another data wrong_args = args[:] wrong_args[0] = bytes(args[0][0] + 1) + args[0][1:] with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag( *wrong_args).transact() testerchain.wait_for_receipt(tx) wrong_args = args[:] wrong_args[3] = bytes(args[3][0] + 1) + args[3][1:] with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag( *wrong_args).transact() testerchain.wait_for_receipt(tx) # Can't evaluate nonexistent miner address = to_canonical_address(wrong_miner) sig_key = provider.ethereum_tester.backend._key_lookup[address] signed_wrong_miner_umbral_public_key = bytes( sig_key.sign_msg_hash(miner_umbral_public_key_hash)) wrong_args = args[:] wrong_args[7] = signed_wrong_miner_umbral_public_key with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag( *wrong_args).transact() testerchain.wait_for_receipt(tx)
def generate_args_for_slashing(testerchain, miner, corrupt: bool = True): def sign_data(data, umbral_privkey): umbral_pubkey_bytes = umbral_privkey.get_pubkey().to_bytes( is_compressed=False) # Prepare hash of the data hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(data) data_hash = hash_ctx.finalize() # Sign data and calculate recoverable signature cryptography_priv_key = umbral_privkey.to_cryptography_privkey() signature_der_bytes = cryptography_priv_key.sign( data, ec.ECDSA(hashes.SHA256())) signature = Signature.from_bytes(signature_der_bytes, der_encoded=True) recoverable_signature = bytes(signature) + bytes([0]) pubkey_bytes = coincurve.PublicKey.from_signature_and_message(recoverable_signature, data_hash, hasher=None) \ .format(compressed=False) if pubkey_bytes != umbral_pubkey_bytes: recoverable_signature = bytes(signature) + bytes([1]) return recoverable_signature delegating_privkey = UmbralPrivateKey.gen_key() _symmetric_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey()) signing_privkey = UmbralPrivateKey.gen_key() signer = Signer(signing_privkey) priv_key_bob = UmbralPrivateKey.gen_key() pub_key_bob = priv_key_bob.get_pubkey() kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey, signer=signer, receiving_pubkey=pub_key_bob, threshold=2, N=4, sign_delegating_key=False, sign_receiving_key=False) capsule.set_correctness_keys(delegating_privkey.get_pubkey(), pub_key_bob, signing_privkey.get_pubkey()) cfrag = pre.reencrypt(kfrags[0], capsule, metadata=os.urandom(34)) capsule_bytes = capsule.to_bytes() if corrupt: cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve) cfrag_bytes = cfrag.to_bytes() hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(capsule_bytes + cfrag_bytes) requester_umbral_private_key = UmbralPrivateKey.gen_key() requester_umbral_public_key_bytes = requester_umbral_private_key.get_pubkey( ).to_bytes(is_compressed=False) capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key) miner_umbral_private_key = UmbralPrivateKey.gen_key() miner_umbral_public_key_bytes = miner_umbral_private_key.get_pubkey( ).to_bytes(is_compressed=False) # Sign Umbral public key using eth-key hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(miner_umbral_public_key_bytes) miner_umbral_public_key_hash = hash_ctx.finalize() address = to_canonical_address(miner) sig_key = testerchain.interface.provider.ethereum_tester.backend._key_lookup[ address] signed_miner_umbral_public_key = bytes( sig_key.sign_msg_hash(miner_umbral_public_key_hash)) capsule_signature_by_requester_and_miner = sign_data( capsule_signature_by_requester, miner_umbral_private_key) cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key) evidence = IndisputableEvidence(capsule, cfrag, ursula=None) evidence_data = evidence.precompute_values() return (capsule_bytes, capsule_signature_by_requester, capsule_signature_by_requester_and_miner, cfrag_bytes, cfrag_signature_by_miner, requester_umbral_public_key_bytes, miner_umbral_public_key_bytes, signed_miner_umbral_public_key, evidence_data)
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 split_rekey(delegating_privkey: UmbralPrivateKey, signer: Signer, receiving_pubkey: UmbralPublicKey, threshold: int, N: int) -> List[KFrag]: """ Creates a re-encryption key from Alice to Bob and splits it in KFrags, using Shamir's Secret Sharing. Requires a threshold number of KFrags out of N to guarantee correctness of re-encryption. Returns a list of KFrags. """ if threshold <= 0 or threshold > N: raise ValueError( 'Arguments threshold and N must satisfy 0 < threshold <= N') if delegating_privkey.params != receiving_pubkey.params: raise ValueError("Keys must have the same parameter set.") params = delegating_privkey.params g = params.g pubkey_a_point = delegating_privkey.get_pubkey().point_key privkey_a_bn = delegating_privkey.bn_key pubkey_b_point = receiving_pubkey.point_key # 'ni' stands for 'Non Interactive'. # This point is used as an ephemeral public key in a DH key exchange, # and the resulting shared secret 'd' allows to make Umbral non-interactive priv_ni = CurveBN.gen_rand(params.curve) ni = priv_ni * g d = CurveBN.hash(ni, pubkey_b_point, pubkey_b_point * priv_ni, params=params) coeffs = [privkey_a_bn * (~d)] coeffs += [CurveBN.gen_rand(params.curve) for _ in range(threshold - 1)] u = params.u # 'xcoord' stands for 'X coordinate'. # This point is used as an ephemeral public key in a DH key exchange, # and the resulting shared secret 'dh_xcoord' contributes to prevent # reconstruction of the re-encryption key without Bob's intervention priv_xcoord = CurveBN.gen_rand(params.curve) xcoord = priv_xcoord * g dh_xcoord = priv_xcoord * pubkey_b_point blake2b = hashes.Hash(hashes.BLAKE2b(64), backend=backend) blake2b.update(xcoord.to_bytes()) blake2b.update(pubkey_b_point.to_bytes()) blake2b.update(dh_xcoord.to_bytes()) hashed_dh_tuple = blake2b.finalize() bn_size = CurveBN.expected_bytes_length(params.curve) kfrags = [] for _ in range(N): id = os.urandom(bn_size) share_x = CurveBN.hash(id, hashed_dh_tuple, params=params) rk = poly_eval(coeffs, share_x) u1 = rk * u kfrag_validity_message = bytes().join( bytes(material) for material in (id, pubkey_a_point, pubkey_b_point, u1, ni, xcoord)) signature = signer(kfrag_validity_message) kfrag = KFrag(id=id, bn_key=rk, point_noninteractive=ni, point_commitment=u1, point_xcoord=xcoord, signature=signature) kfrags.append(kfrag) return kfrags
verifying=verifying_key) cfrag = pre.reencrypt(kfrags[0], capsule) points = [capsule.point_e, cfrag.point_e1, cfrag.proof.point_e2, capsule.point_v, cfrag.point_v1, cfrag.proof.point_v2, capsule.params.u, cfrag.proof.point_kfrag_commitment, cfrag.proof.point_kfrag_pok] z = cfrag.proof.bn_sig ####################### # CurveBN arithmetics # ####################### # Let's generate two random CurveBNs bn1 = CurveBN.gen_rand(curve) bn2 = CurveBN.gen_rand(curve) # Expected results for some binary operations expected = [('Addition', bn1 + bn2), ('Subtraction', bn1 - bn2), ('Multiplication', bn1 * bn2), ('Division', bn1 / bn2), ('Pow', bn1 ** bn2), ('Mod', bn1 % bn2), ('Inverse', ~bn1), ('Neg', -bn1), ] expected = [{'operation': op, 'result': hexlify(result)} for (op, result) in expected]
def share_secret(secret, threshold, n): coeff = [secret] + [CurveBN.gen_rand() for _ in range(threshold - 1)] xs = [CurveBN.gen_rand() for _ in range(n)] points = [(x, utils.poly_eval(coeff, x)) for x in xs] return points
def test_evaluate_cfrag(testerchain, escrow, adjudicator_contract): creator, miner, wrong_miner, investigator, *everyone_else = testerchain.interface.w3.eth.accounts evaluation_log = adjudicator_contract.events.CFragEvaluated.createFilter(fromBlock='latest') # TODO: Move this to an integration test? umbral_params = default_params() u_xcoord, u_ycoord = umbral_params.u.to_affine() u_sign = 2 + (u_ycoord % 2) assert u_sign == adjudicator_contract.functions.UMBRAL_PARAMETER_U_SIGN().call() assert u_xcoord == adjudicator_contract.functions.UMBRAL_PARAMETER_U_XCOORD().call() assert u_ycoord == adjudicator_contract.functions.UMBRAL_PARAMETER_U_YCOORD().call() # TODO: Move this to an integration test? test_data = os.urandom(40) h = hash_to_curvebn(test_data, params=umbral_params, hash_class=ExtendedKeccak) assert int(h) == adjudicator_contract.functions.extendedKeccakToBN(test_data).call() # Prepare one miner tx = escrow.functions.setMinerInfo(miner, 1000).transact() testerchain.wait_for_receipt(tx) # Generate miner's Umbral key miner_umbral_private_key = UmbralPrivateKey.gen_key() miner_umbral_public_key_bytes = miner_umbral_private_key.get_pubkey().to_bytes(is_compressed=False) # Sign Umbral public key using eth-key hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(miner_umbral_public_key_bytes) miner_umbral_public_key_hash = hash_ctx.finalize() provider = testerchain.interface.provider address = to_canonical_address(miner) sig_key = provider.ethereum_tester.backend._key_lookup[address] signed_miner_umbral_public_key = bytes(sig_key.sign_msg_hash(miner_umbral_public_key_hash)) # Prepare hash of the data metadata = os.urandom(33) capsule, cfrag = fragments(metadata) assert cfrag.verify_correctness(capsule) capsule_bytes = capsule.to_bytes() cfrag_bytes = cfrag.to_bytes() # Bob prepares supporting Evidence evidence = IndisputableEvidence(capsule, cfrag, ursula=None) evidence_data = evidence.precompute_values() assert len(evidence_data) == 20 * 32 + 32 + 20 + 1 # This check is a workaround in order to test the signature validation is correct. # In reality, this check should be part of the isCapsuleFragCorrect logic, # but we're facing with "Stack too deep" errors at the moment. address = adjudicator_contract.functions.aliceAddress(cfrag_bytes, evidence_data).call() assert address == to_checksum_address(evidence_data[-21:-1].hex()) proof_challenge_scalar = int(evidence.get_proof_challenge_scalar()) computeProofChallengeScalar = adjudicator_contract.functions.computeProofChallengeScalar assert proof_challenge_scalar == computeProofChallengeScalar(capsule_bytes, cfrag_bytes).call() hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(capsule_bytes + cfrag_bytes) data_hash = hash_ctx.finalize() # This capsule and cFrag are not yet evaluated assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call() # Generate requester's Umbral key requester_umbral_private_key = UmbralPrivateKey.gen_key() requester_umbral_public_key_bytes = requester_umbral_private_key.get_pubkey().to_bytes(is_compressed=False) # Sign capsule and cFrag capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key) capsule_signature_by_requester_and_miner = sign_data(capsule_signature_by_requester, miner_umbral_private_key) cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key) # Challenge using good data args = (capsule_bytes, capsule_signature_by_requester, capsule_signature_by_requester_and_miner, cfrag_bytes, cfrag_signature_by_miner, requester_umbral_public_key_bytes, miner_umbral_public_key_bytes, signed_miner_umbral_public_key, evidence_data) value = escrow.functions.minerInfo(miner).call()[0] tx = adjudicator_contract.functions.evaluateCFrag(*args).transact({'from': investigator}) testerchain.wait_for_receipt(tx) # Hash of the data is saved and miner was not slashed assert adjudicator_contract.functions.evaluatedCFrags(data_hash).call() assert value == escrow.functions.minerInfo(miner).call()[0] assert 0 == escrow.functions.rewardInfo(investigator).call() events = evaluation_log.get_all_entries() assert 1 == len(events) event_args = events[0]['args'] assert data_hash == event_args['evaluationHash'] assert miner == event_args['miner'] assert investigator == event_args['investigator'] assert event_args['correctness'] # Can't evaluate miner with data that already was checked with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag(*args).transact() testerchain.wait_for_receipt(tx) # Challenge using bad data metadata = os.urandom(34) capsule, cfrag = fragments(metadata) capsule_bytes = capsule.to_bytes() # Corrupt proof cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve) cfrag_bytes = cfrag.to_bytes() hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(capsule_bytes + cfrag_bytes) data_hash = hash_ctx.finalize() capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key) capsule_signature_by_requester_and_miner = sign_data(capsule_signature_by_requester, miner_umbral_private_key) cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key) evidence = IndisputableEvidence(capsule, cfrag, ursula=None) evidence_data = evidence.precompute_values() args = (capsule_bytes, capsule_signature_by_requester, capsule_signature_by_requester_and_miner, cfrag_bytes, cfrag_signature_by_miner, requester_umbral_public_key_bytes, miner_umbral_public_key_bytes, signed_miner_umbral_public_key, evidence_data) assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call() tx = adjudicator_contract.functions.evaluateCFrag(*args).transact({'from': investigator}) testerchain.wait_for_receipt(tx) # Hash of the data is saved and miner was slashed assert adjudicator_contract.functions.evaluatedCFrags(data_hash).call() assert value - BASE_PENALTY == escrow.functions.minerInfo(miner).call()[0] assert BASE_PENALTY / REWARD_COEFFICIENT == escrow.functions.rewardInfo(investigator).call() events = evaluation_log.get_all_entries() assert 2 == len(events) event_args = events[1]['args'] assert data_hash == event_args['evaluationHash'] assert miner == event_args['miner'] assert investigator == event_args['investigator'] assert not event_args['correctness'] # Prepare hash of the data metadata = os.urandom(34) capsule, cfrag = fragments(metadata) capsule_bytes = capsule.to_bytes() # Corrupt proof cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve) cfrag_bytes = cfrag.to_bytes() hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(capsule_bytes + cfrag_bytes) data_hash = hash_ctx.finalize() capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key) capsule_signature_by_requester_and_miner = sign_data(capsule_signature_by_requester, miner_umbral_private_key) cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key) evidence = IndisputableEvidence(capsule, cfrag, ursula=None) evidence_data = evidence.precompute_values() args = [capsule_bytes, capsule_signature_by_requester, capsule_signature_by_requester_and_miner, cfrag_bytes, cfrag_signature_by_miner, requester_umbral_public_key_bytes, miner_umbral_public_key_bytes, signed_miner_umbral_public_key, evidence_data] assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call() # Can't evaluate miner using broken signatures wrong_args = args[:] wrong_args[1] = capsule_signature_by_requester[1:] with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact() testerchain.wait_for_receipt(tx) wrong_args = args[:] wrong_args[2] = capsule_signature_by_requester_and_miner[1:] with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact() testerchain.wait_for_receipt(tx) wrong_args = args[:] wrong_args[4] = cfrag_signature_by_miner[1:] with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact() testerchain.wait_for_receipt(tx) wrong_args = args[:] wrong_args[7] = signed_miner_umbral_public_key[1:] with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact() testerchain.wait_for_receipt(tx) # Can't evaluate miner using wrong keys wrong_args = args[:] wrong_args[5] = UmbralPrivateKey.gen_key().get_pubkey().to_bytes(is_compressed=False) with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact() testerchain.wait_for_receipt(tx) wrong_args = args[:] wrong_args[6] = UmbralPrivateKey.gen_key().get_pubkey().to_bytes(is_compressed=False) with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact() testerchain.wait_for_receipt(tx) # Can't use signature for another data wrong_args = args[:] wrong_args[0] = bytes(args[0][0] + 1) + args[0][1:] with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact() testerchain.wait_for_receipt(tx) wrong_args = args[:] wrong_args[3] = bytes(args[3][0] + 1) + args[3][1:] with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact() testerchain.wait_for_receipt(tx) # Can't evaluate nonexistent miner address = to_canonical_address(wrong_miner) sig_key = provider.ethereum_tester.backend._key_lookup[address] signed_wrong_miner_umbral_public_key = bytes(sig_key.sign_msg_hash(miner_umbral_public_key_hash)) wrong_args = args[:] wrong_args[7] = signed_wrong_miner_umbral_public_key with pytest.raises((TransactionFailed, ValueError)): tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact() testerchain.wait_for_receipt(tx) # Initial arguments were correct value = escrow.functions.minerInfo(miner).call()[0] reward = escrow.functions.rewardInfo(investigator).call() assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call() tx = adjudicator_contract.functions.evaluateCFrag(*args).transact({'from': investigator}) testerchain.wait_for_receipt(tx) assert adjudicator_contract.functions.evaluatedCFrags(data_hash).call() # Penalty was increased because it's the second violation assert value - (BASE_PENALTY + PENALTY_HISTORY_COEFFICIENT) == escrow.functions.minerInfo(miner).call()[0] assert reward + (BASE_PENALTY + PENALTY_HISTORY_COEFFICIENT) / REWARD_COEFFICIENT == \ escrow.functions.rewardInfo(investigator).call() events = evaluation_log.get_all_entries() assert 3 == len(events) event_args = events[2]['args'] assert data_hash == event_args['evaluationHash'] assert miner == event_args['miner'] assert investigator == event_args['investigator'] assert not event_args['correctness'] # Prepare corrupted data again capsule, cfrag = fragments(metadata) capsule_bytes = capsule.to_bytes() # Corrupt proof cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve) cfrag_bytes = cfrag.to_bytes() hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(capsule_bytes + cfrag_bytes) data_hash = hash_ctx.finalize() capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key) capsule_signature_by_requester_and_miner = sign_data(capsule_signature_by_requester, miner_umbral_private_key) cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key) evidence = IndisputableEvidence(capsule, cfrag, ursula=None) evidence_data = evidence.precompute_values() args = (capsule_bytes, capsule_signature_by_requester, capsule_signature_by_requester_and_miner, cfrag_bytes, cfrag_signature_by_miner, requester_umbral_public_key_bytes, miner_umbral_public_key_bytes, signed_miner_umbral_public_key, evidence_data) value = escrow.functions.minerInfo(miner).call()[0] reward = escrow.functions.rewardInfo(investigator).call() assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call() tx = adjudicator_contract.functions.evaluateCFrag(*args).transact({'from': investigator}) testerchain.wait_for_receipt(tx) assert adjudicator_contract.functions.evaluatedCFrags(data_hash).call() # Penalty was decreased because it's more than maximum available percentage of value assert value - value // PERCENTAGE_PENALTY_COEFFICIENT == escrow.functions.minerInfo(miner).call()[0] assert reward + value // PERCENTAGE_PENALTY_COEFFICIENT / REWARD_COEFFICIENT == \ escrow.functions.rewardInfo(investigator).call() events = evaluation_log.get_all_entries() assert 4 == len(events) event_args = events[3]['args'] assert data_hash == event_args['evaluationHash'] assert miner == event_args['miner'] assert investigator == event_args['investigator'] assert not event_args['correctness']
def random_ec_curvebn2(): yield CurveBN.gen_rand()
def split_rekey(privkey_a_bn: Union[UmbralPrivateKey, CurveBN], signer_a: Signer, pubkey_b_point: Union[UmbralPublicKey, Point], threshold: int, N: int, params: UmbralParameters = None) -> List[KFrag]: """ Creates a re-encryption key from Alice to Bob and splits it in KFrags, using Shamir's Secret Sharing. Requires a threshold number of KFrags out of N to guarantee correctness of re-encryption. Returns a list of KFrags. """ params = params if params is not None else default_params() g = params.g if isinstance(privkey_a_bn, UmbralPrivateKey): pubkey_a_point = privkey_a_bn.get_pubkey().point_key privkey_a_bn = privkey_a_bn.bn_key else: pubkey_a_point = privkey_a_bn * g if isinstance(pubkey_b_point, UmbralPublicKey): pubkey_b_point = pubkey_b_point.point_key # 'ni' stands for 'Non Interactive'. # This point is used as an ephemeral public key in a DH key exchange, # and the resulting shared secret 'd' allows to make Umbral non-interactive priv_ni = CurveBN.gen_rand(params.curve) ni = priv_ni * g d = CurveBN.hash(ni, pubkey_b_point, pubkey_b_point * priv_ni, params=params) coeffs = [privkey_a_bn * (~d)] coeffs += [CurveBN.gen_rand(params.curve) for _ in range(threshold - 1)] u = params.u # 'xcoord' stands for 'X coordinate'. # This point is used as an ephemeral public key in a DH key exchange, # and the resulting shared secret 'dh_xcoord' contributes to prevent # reconstruction of the re-encryption key without Bob's intervention priv_xcoord = CurveBN.gen_rand(params.curve) xcoord = priv_xcoord * g dh_xcoord = priv_xcoord * pubkey_b_point blake2b = hashes.Hash(hashes.BLAKE2b(64), backend=backend) blake2b.update(xcoord.to_bytes()) blake2b.update(pubkey_b_point.to_bytes()) blake2b.update(dh_xcoord.to_bytes()) hashed_dh_tuple = blake2b.finalize() bn_size = CurveBN.expected_bytes_length(params.curve) kfrags = [] for _ in range(N): id = os.urandom(bn_size) share_x = CurveBN.hash(id, hashed_dh_tuple, params=params) rk = poly_eval(coeffs, share_x) u1 = rk * u kfrag_validity_message = bytes().join( bytes(material) for material in (id, pubkey_a_point, pubkey_b_point, u1, ni, xcoord)) signature = signer_a(kfrag_validity_message) kfrag = KFrag(id=id, bn_key=rk, point_noninteractive=ni, point_commitment=u1, point_xcoord=xcoord, signature=signature) kfrags.append(kfrag) return kfrags
def generate_kfrags( delegating_privkey: UmbralPrivateKey, receiving_pubkey: UmbralPublicKey, threshold: int, N: int, signer: Signer, sign_delegating_key: Optional[bool] = True, sign_receiving_key: Optional[bool] = True, ) -> List[KFrag]: """ Creates a re-encryption key from Alice's delegating public key to Bob's receiving public key, and splits it in KFrags, using Shamir's Secret Sharing. Requires a threshold number of KFrags out of N. Returns a list of N KFrags """ if threshold <= 0 or threshold > N: raise ValueError( 'Arguments threshold and N must satisfy 0 < threshold <= N') if delegating_privkey.params != receiving_pubkey.params: raise ValueError("Keys must have the same parameter set.") params = delegating_privkey.params g = params.g delegating_pubkey = delegating_privkey.get_pubkey() bob_pubkey_point = receiving_pubkey.point_key # The precursor point is used as an ephemeral public key in a DH key exchange, # and the resulting shared secret 'dh_point' is used to derive other secret values private_precursor = CurveBN.gen_rand(params.curve) precursor = private_precursor * g # type: Any dh_point = private_precursor * bob_pubkey_point # Secret value 'd' allows to make Umbral non-interactive d = hash_to_curvebn(precursor, bob_pubkey_point, dh_point, bytes(constants.NON_INTERACTIVE), params=params) # Coefficients of the generating polynomial coefficients = [delegating_privkey.bn_key * (~d)] coefficients += [ CurveBN.gen_rand(params.curve) for _ in range(threshold - 1) ] bn_size = CurveBN.expected_bytes_length(params.curve) kfrags = list() for _ in range(N): kfrag_id = os.urandom(bn_size) # The index of the re-encryption key share (which in Shamir's Secret # Sharing corresponds to x in the tuple (x, f(x)), with f being the # generating polynomial), is used to prevent reconstruction of the # re-encryption key without Bob's intervention share_index = hash_to_curvebn(precursor, bob_pubkey_point, dh_point, bytes(constants.X_COORDINATE), kfrag_id, params=params) # The re-encryption key share is the result of evaluating the generating # polynomial for the index value rk = poly_eval(coefficients, share_index) commitment = rk * params.u # type: Any validity_message_for_bob = ( kfrag_id, delegating_pubkey, receiving_pubkey, commitment, precursor, ) # type: Any validity_message_for_bob = bytes().join( bytes(item) for item in validity_message_for_bob) signature_for_bob = signer(validity_message_for_bob) if sign_delegating_key and sign_receiving_key: mode = DELEGATING_AND_RECEIVING elif sign_delegating_key: mode = DELEGATING_ONLY elif sign_receiving_key: mode = RECEIVING_ONLY else: mode = NO_KEY validity_message_for_proxy = [kfrag_id, commitment, precursor, mode] # type: Any if sign_delegating_key: validity_message_for_proxy.append(delegating_pubkey) if sign_receiving_key: validity_message_for_proxy.append(receiving_pubkey) validity_message_for_proxy = bytes().join( bytes(item) for item in validity_message_for_proxy) signature_for_proxy = signer(validity_message_for_proxy) kfrag = KFrag( identifier=kfrag_id, bn_key=rk, point_commitment=commitment, point_precursor=precursor, signature_for_proxy=signature_for_proxy, signature_for_bob=signature_for_bob, keys_in_signature=mode, ) kfrags.append(kfrag) return kfrags