From 5618b2529289af227e1d6d68d873fbc84ec44b9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Mon, 4 Jun 2018 14:37:32 +0200 Subject: [PATCH] Simplified public API for umbral.pre * Functions in `pre` now only take Umbral keys as arguments, rather than primitive types (Point, CurveBN) * Remove unnecessary arguments from public facing and internal methods when they can be extracted from a Capsule, UmbralPublicKey or UmbralPrivateKey * Adds a getter in Capsule for correctness keys * Adapts the test suite to new simplified API --- tests/test_capsule/test_capsule_operations.py | 13 ++- .../test_capsule/test_capsule_serializers.py | 13 +-- tests/test_correctness.py | 86 ++++++------------- tests/test_keys/test_key_fragments.py | 6 +- tests/test_simple_api.py | 3 +- umbral/_pre.py | 18 +++- umbral/fragments.py | 15 +--- umbral/pre.py | 56 ++++++------ 8 files changed, 85 insertions(+), 125 deletions(-) diff --git a/tests/test_capsule/test_capsule_operations.py b/tests/test_capsule/test_capsule_operations.py index a52c9cf..fb92df2 100644 --- a/tests/test_capsule/test_capsule_operations.py +++ b/tests/test_capsule/test_capsule_operations.py @@ -62,11 +62,11 @@ def test_decapsulation_by_alice(alices_keys): delegating_privkey, _signing_privkey = alices_keys - sym_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey().point_key, params) + sym_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey()) assert len(sym_key) == 32 # The symmetric key sym_key is perhaps used for block cipher here in a real-world scenario. - sym_key_2 = pre._decapsulate_original(delegating_privkey.bn_key, capsule) + sym_key_2 = pre._decapsulate_original(delegating_privkey, capsule) assert sym_key_2 == sym_key @@ -115,8 +115,7 @@ def test_capsule_as_dict_key(alices_keys, bobs_keys): capsule.attach_cfrag(cfrag) # Even if we activate the capsule, it still serves as the same key. - cleartext = pre.decrypt(ciphertext, capsule, receiving_privkey, - delegating_pubkey, signing_pubkey) + cleartext = pre.decrypt(ciphertext, capsule, receiving_privkey) assert some_dict[capsule] == "Thing that Bob wants to try per-Capsule" assert cleartext == plain_data @@ -132,14 +131,14 @@ def test_capsule_length(alices_keys, bobs_keys): priv_key_bob, pub_key_bob = bobs_keys - sym_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey().point_key) + sym_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey()) + + kfrags = pre.split_rekey(delegating_privkey, signer, pub_key_bob, 10, 15) capsule.set_correctness_keys(delegating=delegating_privkey.get_pubkey(), receiving=pub_key_bob, verifying=signing_privkey.get_pubkey()) - kfrags = pre.split_rekey(delegating_privkey, signer, pub_key_bob, 10, 15) - for counter, kfrag in enumerate(kfrags): assert len(capsule) == counter cfrag = pre.reencrypt(kfrag, capsule) diff --git a/tests/test_capsule/test_capsule_serializers.py b/tests/test_capsule/test_capsule_serializers.py index 008efe2..8b104d9 100644 --- a/tests/test_capsule/test_capsule_serializers.py +++ b/tests/test_capsule/test_capsule_serializers.py @@ -11,7 +11,7 @@ def test_capsule_serialization(alices_keys): delegating_privkey, _signing_privkey = alices_keys params = delegating_privkey.params - _symmetric_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey().point_key, params) + _symmetric_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey()) capsule_bytes = capsule.to_bytes() capsule_bytes_casted = bytes(capsule) assert capsule_bytes == capsule_bytes_casted @@ -44,16 +44,17 @@ def test_activated_capsule_serialization(alices_keys, bobs_keys): receiving_privkey, receiving_pubkey = bobs_keys - _unused_key, capsule = pre._encapsulate(delegating_pubkey.point_key, params) - - capsule.set_correctness_keys(delegating=delegating_pubkey, - receiving=receiving_pubkey, - verifying=signing_privkey.get_pubkey()) + _unused_key, capsule = pre._encapsulate(delegating_pubkey) + kfrags = pre.split_rekey(delegating_privkey, signer_alice, receiving_pubkey, 1, 2) cfrag = pre.reencrypt(kfrags[0], capsule) + capsule.set_correctness_keys(delegating=delegating_pubkey, + receiving=receiving_pubkey, + verifying=signing_privkey.get_pubkey()) + capsule.attach_cfrag(cfrag) capsule._reconstruct_shamirs_secret(receiving_privkey) diff --git a/tests/test_correctness.py b/tests/test_correctness.py index 7e985db..1d25bd4 100644 --- a/tests/test_correctness.py +++ b/tests/test_correctness.py @@ -17,7 +17,7 @@ def test_correctness_proof_serialization(alices_keys): params = delegating_privkey.params - _unused_key, capsule = pre._encapsulate(delegating_pubkey.point_key, params) + _unused_key, capsule = pre._encapsulate(delegating_pubkey) kfrags = pre.split_rekey(delegating_privkey, signer, pub_key_bob, 1, 2) # Example of potential metadata to describe the re-encryption request @@ -53,15 +53,15 @@ def test_cheating_ursula_replays_old_reencryption(N, M, alices_keys): params = delegating_privkey.params - _unused_key1, capsule_alice1 = pre._encapsulate(delegating_pubkey.point_key, params) - _unused_key2, capsule_alice2 = pre._encapsulate(delegating_pubkey.point_key, params) + _unused_key1, capsule_alice1 = pre._encapsulate(delegating_pubkey) + _unused_key2, capsule_alice2 = pre._encapsulate(delegating_pubkey) + + kfrags = pre.split_rekey(delegating_privkey, signer, pub_key_bob, M, N) capsule_alice1.set_correctness_keys(delegating=delegating_pubkey, receiving=pub_key_bob, verifying=signing_privkey.get_pubkey()) - kfrags = pre.split_rekey(delegating_privkey, signer, pub_key_bob, M, N) - cfrags, metadata = [], [] for i, kfrag in enumerate(kfrags[:M]): @@ -76,34 +76,25 @@ def test_cheating_ursula_replays_old_reencryption(N, M, alices_keys): else: cfrag = pre.reencrypt(kfrag, capsule_alice1, metadata=metadata_i) - capsule_alice1.attach_cfrag(cfrag) + # Next, we bypass the public method to attach CFrags to the capsule, + # -- called Capsule.append(cfrag) -- and insert it directly in the private + # list of CFrags. In case you were wondering...DON'T DO THIS! + capsule_alice1._attached_cfrags.append(cfrag) cfrags.append(cfrag) # Let's activate the capsule capsule_alice1._reconstruct_shamirs_secret(priv_key_bob) with pytest.raises(pre.GenericUmbralError): - sym_key = pre._decapsulate_reencrypted(pub_key_bob.point_key, - priv_key_bob.bn_key, - delegating_pubkey.point_key, - capsule_alice1 - ) + sym_key = pre._decapsulate_reencrypted(priv_key_bob, capsule_alice1) - assert not cfrags[0].verify_correctness(capsule_alice1, - delegating_pubkey, - signing_privkey.get_pubkey(), - pub_key_bob, - ) + assert not cfrags[0].verify_correctness(capsule_alice1) # The response of cheating Ursula is in cfrags[0], # so the rest of CFrags should be correct: correct_cases = 0 for cfrag_i, metadata_i in zip(cfrags[1:], metadata[1:]): - if cfrag_i.verify_correctness(capsule_alice1, - delegating_pubkey, - signing_privkey.get_pubkey(), - pub_key_bob, - ): + if cfrag_i.verify_correctness(capsule_alice1): correct_cases += 1 else: pytest.fail("One of the cfrags that was supposed to be correct wasn't.") @@ -112,11 +103,7 @@ def test_cheating_ursula_replays_old_reencryption(N, M, alices_keys): # Alternatively, we can try to open the capsule directly. # We should get an exception with an attached list of incorrect cfrags with pytest.raises(pre.UmbralCorrectnessError) as exception_info: - _ = pre._open_capsule(capsule_alice1, - priv_key_bob, - delegating_pubkey, - signing_privkey.get_pubkey(), - ) + _ = pre._open_capsule(capsule_alice1, priv_key_bob) correctness_error = exception_info.value assert cfrags[0] in correctness_error.offending_cfrags assert len(correctness_error.offending_cfrags) == 1 @@ -133,14 +120,14 @@ def test_cheating_ursula_sends_garbage(N, M, alices_keys): params = delegating_privkey.params - sym_key, capsule_alice = pre._encapsulate(delegating_pubkey.point_key, params) + sym_key, capsule_alice = pre._encapsulate(delegating_pubkey) + + kfrags = pre.split_rekey(delegating_privkey, signer, pub_key_bob, M, N) capsule_alice.set_correctness_keys(delegating=delegating_pubkey, receiving=pub_key_bob, verifying=signing_privkey.get_pubkey()) - kfrags = pre.split_rekey(delegating_privkey, signer, pub_key_bob, M, N) - cfrags, metadata = [], [] for i, kfrag in enumerate(kfrags[:M]): # Example of potential metadata to describe the re-encryption request @@ -159,32 +146,20 @@ def test_cheating_ursula_sends_garbage(N, M, alices_keys): capsule_alice._reconstruct_shamirs_secret(priv_key_bob) # activate capsule with pytest.raises(pre.GenericUmbralError): - _unused_key = pre._decapsulate_reencrypted(pub_key_bob.point_key, - priv_key_bob.bn_key, - delegating_pubkey.point_key, - capsule_alice) + _unused_key = pre._decapsulate_reencrypted(priv_key_bob, capsule_alice) - assert not cfrags[0].verify_correctness(capsule_alice, - delegating_pubkey, - signing_privkey.get_pubkey(), - pub_key_bob, - ) + assert not cfrags[0].verify_correctness(capsule_alice) # The response of cheating Ursula is in cfrags[0], # so the rest of CFrags chould be correct: for cfrag_i, metadata_i in zip(cfrags[1:], metadata[1:]): - assert cfrag_i.verify_correctness(capsule_alice, - delegating_pubkey, - signing_privkey.get_pubkey(), - pub_key_bob, - ) + assert cfrag_i.verify_correctness(capsule_alice) # Alternatively, we can try to open the capsule directly. # We should get an exception with an attached list of incorrect cfrags with pytest.raises(pre.UmbralCorrectnessError) as exception_info: - _decapsulated_key = pre._open_capsule(capsule_alice, priv_key_bob, - delegating_pubkey, - signing_privkey.get_pubkey()) + _decapsulated_key = pre._open_capsule(capsule_alice, priv_key_bob) + correctness_error = exception_info.value assert cfrags[0] in correctness_error.offending_cfrags assert len(correctness_error.offending_cfrags) == 1 @@ -202,12 +177,12 @@ def test_decryption_fails_when_it_expects_a_proof_and_there_isnt(N, M, alices_ke plain_data = b'peace at dawn' ciphertext, capsule = pre.encrypt(delegating_privkey.get_pubkey(), plain_data) + kfrags = pre.split_rekey(delegating_privkey, signer, pub_key_bob, M, N) + capsule.set_correctness_keys(delegating=delegating_privkey.get_pubkey(), receiving=pub_key_bob, verifying=signing_privkey.get_pubkey()) - - kfrags = pre.split_rekey(delegating_privkey, signer, pub_key_bob, M, N) - for kfrag in kfrags: + for kfrag in kfrags[:M]: cfrag = pre.reencrypt(kfrag, capsule) capsule.attach_cfrag(cfrag) @@ -217,9 +192,7 @@ def test_decryption_fails_when_it_expects_a_proof_and_there_isnt(N, M, alices_ke cfrag.proof = None with pytest.raises(cfrag.NoProofProvided): - _cleartext = pre.decrypt(ciphertext, capsule, priv_key_bob, - delegating_privkey.get_pubkey(), - signing_privkey.get_pubkey()) + _cleartext = pre.decrypt(ciphertext, capsule, priv_key_bob) @pytest.mark.parametrize("N, M", parameters) @@ -232,7 +205,7 @@ def test_m_of_n(N, M, alices_keys, bobs_keys): params = delegating_privkey.params - sym_key, capsule = pre._encapsulate(delegating_pubkey.point_key, params) + sym_key, capsule = pre._encapsulate(delegating_pubkey) capsule.set_correctness_keys(delegating=delegating_privkey.get_pubkey(), receiving=pub_key_bob, @@ -251,10 +224,7 @@ def test_m_of_n(N, M, alices_keys, bobs_keys): cfrag = pre.reencrypt(kfrag, capsule, metadata=metadata) capsule.attach_cfrag(cfrag) - assert cfrag.verify_correctness(capsule, delegating_pubkey, - signing_privkey.get_pubkey(), pub_key_bob, - ) + assert cfrag.verify_correctness(capsule) - sym_key_from_capsule = pre._open_capsule(capsule, priv_key_bob, delegating_pubkey, - signing_privkey.get_pubkey()) + sym_key_from_capsule = pre._open_capsule(capsule, priv_key_bob) assert sym_key == sym_key_from_capsule diff --git a/tests/test_keys/test_key_fragments.py b/tests/test_keys/test_key_fragments.py index f25a6ea..f35481c 100644 --- a/tests/test_keys/test_key_fragments.py +++ b/tests/test_keys/test_key_fragments.py @@ -32,7 +32,7 @@ def test_cfrag_serialization_with_proof_and_metadata(alices_keys, bobs_keys): _receiving_privkey, receiving_pubkey = bobs_keys - _unused_key, capsule = pre._encapsulate(delegating_pubkey.point_key, delegating_pubkey.params) + _unused_key, capsule = pre._encapsulate(delegating_pubkey) kfrags = pre.split_rekey(delegating_privkey, signer_alice, receiving_pubkey, 1, 2) @@ -70,7 +70,7 @@ def test_cfrag_serialization_with_proof_but_no_metadata(alices_keys, bobs_keys): _receiving_privkey, receiving_pubkey = bobs_keys signer_alice = Signer(signing_privkey) - _unused_key, capsule = pre._encapsulate(delegating_pubkey.point_key, delegating_pubkey.params) + _unused_key, capsule = pre._encapsulate(delegating_pubkey) kfrags = pre.split_rekey(delegating_privkey, signer_alice, receiving_pubkey, 1, 2) @@ -108,7 +108,7 @@ def test_cfrag_serialization_no_proof_no_metadata(alices_keys, bobs_keys): _receiving_privkey, receiving_pubkey = bobs_keys signer_alice = Signer(signing_privkey) - _unused_key, capsule = pre._encapsulate(delegating_pubkey.point_key, delegating_pubkey.params) + _unused_key, capsule = pre._encapsulate(delegating_pubkey) kfrags = pre.split_rekey(delegating_privkey, signer_alice, receiving_pubkey, 1, 2) diff --git a/tests/test_simple_api.py b/tests/test_simple_api.py index 0af43c1..ebc8a94 100644 --- a/tests/test_simple_api.py +++ b/tests/test_simple_api.py @@ -46,8 +46,7 @@ def test_simple_api(N, M, curve=default_curve()): cfrag = pre.reencrypt(kfrag, capsule) capsule.attach_cfrag(cfrag) - reenc_cleartext = pre.decrypt(ciphertext, capsule, receiving_privkey, - delegating_pubkey, signing_pubkey) + reenc_cleartext = pre.decrypt(ciphertext, capsule, receiving_privkey) assert reenc_cleartext == plain_data diff --git a/umbral/_pre.py b/umbral/_pre.py index cf3ac9a..138b5ff 100644 --- a/umbral/_pre.py +++ b/umbral/_pre.py @@ -46,10 +46,20 @@ def prove_cfrag_correctness(cfrag: "CapsuleFrag", def assess_cfrag_correctness(cfrag, - capsule: "Capsule", - delegating_point, - signing_pubkey, - receiving_point): + capsule: "Capsule"): + + correctness_keys = capsule.get_correctness_keys() + + delegating_pubkey = correctness_keys['delegating'] + signing_pubkey = correctness_keys['verifying'] + receiving_pubkey = correctness_keys['receiving'] + + if not all((delegating_pubkey, signing_pubkey, receiving_pubkey)): + raise TypeError("Need all three keys to verify correctness.") + + delegating_point = delegating_pubkey.point_key + receiving_point = receiving_pubkey.point_key + params = capsule._umbral_params #### diff --git a/umbral/fragments.py b/umbral/fragments.py index b9d8f25..2afc461 100644 --- a/umbral/fragments.py +++ b/umbral/fragments.py @@ -220,19 +220,8 @@ class CapsuleFrag(object): return serialized_cfrag - def verify_correctness(self, - capsule: "Capsule", - delegating_pubkey: UmbralPublicKey, - signing_pubkey, - receiving_pubkey: UmbralPublicKey): - if not all((delegating_pubkey, signing_pubkey, receiving_pubkey)): - raise TypeError("Need all three keys to verify correctness.") - - pubkey_a_point = delegating_pubkey.point_key - pubkey_b_point = receiving_pubkey.point_key - - return assess_cfrag_correctness(self, capsule, pubkey_a_point, - signing_pubkey, pubkey_b_point) + def verify_correctness(self, capsule: "Capsule"): + return assess_cfrag_correctness(self, capsule) def attach_proof(self, e2, v2, u1, u2, z3, kfrag_signature, metadata): self.proof = CorrectnessProof(point_e2=e2, diff --git a/umbral/pre.py b/umbral/pre.py index 82ef4ae..3701adb 100644 --- a/umbral/pre.py +++ b/umbral/pre.py @@ -139,6 +139,10 @@ class Capsule(object): else: raise ValueError("The {} key is already set; you can't set it again.".format(key_type)) + + def get_correctness_keys(self): + return dict(self._cfrag_correctness_keys) + def set_correctness_keys(self, delegating: UmbralPublicKey = None, receiving: UmbralPublicKey = None, @@ -189,17 +193,12 @@ class Capsule(object): def activated_components(self) -> Union[Tuple[None, None, None], Tuple[Point, Point, Point]]: return self._point_e_prime, self._point_v_prime, self._point_noninteractive - def _reconstruct_shamirs_secret(self, - priv_b: Union[UmbralPrivateKey, CurveBN] - ) -> None: + def _reconstruct_shamirs_secret(self, priv_b: UmbralPrivateKey) -> None: params = self._umbral_params g = params.g - if isinstance(priv_b, UmbralPrivateKey): - pub_b = priv_b.get_pubkey() - priv_b = priv_b.bn_key - else: - pub_b = priv_b * g + pub_b = priv_b.get_pubkey() + priv_b = priv_b.bn_key cfrag_0 = self._attached_cfrags[0] id_0 = cfrag_0._kfrag_id @@ -369,10 +368,11 @@ def reencrypt(kfrag: KFrag, capsule: Capsule, provide_proof = True, return cfrag -def _encapsulate(alice_pubkey: Point, params: UmbralParameters, +def _encapsulate(alice_pubkey: UmbralPublicKey, key_length = 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) @@ -384,7 +384,7 @@ def _encapsulate(alice_pubkey: Point, params: UmbralParameters, h = CurveBN.hash(pub_r, pub_u, params=params) s = priv_u + (priv_r * h) - shared_key = (priv_r + priv_u) * alice_pubkey + shared_key = (priv_r + priv_u) * alice_pubkey.point_key # Key to be used for symmetric encryption key = kdf(shared_key, key_length) @@ -392,10 +392,12 @@ def _encapsulate(alice_pubkey: Point, params: UmbralParameters, return key, Capsule(point_e=pub_r, point_v=pub_u, bn_sig=s, params=params) -def _decapsulate_original(priv_key: CurveBN, capsule: Capsule, +def _decapsulate_original(priv_key: UmbralPrivateKey, capsule: Capsule, key_length = DEM_KEYSIZE) -> bytes: """Derive the same symmetric key""" + priv_key = priv_key.bn_key + shared_key = priv_key * (capsule._point_e + capsule._point_v) key = kdf(shared_key, key_length) @@ -407,12 +409,14 @@ def _decapsulate_original(priv_key: CurveBN, capsule: Capsule, return key -def _decapsulate_reencrypted(pub_key: Point, priv_key: CurveBN, - orig_pub_key: Point, capsule: Capsule, +def _decapsulate_reencrypted(receiving_privkey: UmbralPrivateKey, capsule: Capsule, key_length = DEM_KEYSIZE) -> bytes: """Derive the same symmetric key""" params = capsule._umbral_params + pub_key = receiving_privkey.get_pubkey().point_key + priv_key = receiving_privkey.bn_key + ni = capsule._point_noninteractive d = CurveBN.hash(ni, pub_key, priv_key * ni, params=params) @@ -428,6 +432,7 @@ def _decapsulate_reencrypted(pub_key: Point, priv_key: CurveBN, s = capsule._bn_sig h = CurveBN.hash(e, v, params=params) inv_d = ~d + orig_pub_key = capsule.get_correctness_keys()['delegating'].point_key if not (s * inv_d) * orig_pub_key == (h * e_prime) + v_prime: raise GenericUmbralError() @@ -441,9 +446,7 @@ def encrypt(alice_pubkey: UmbralPublicKey, plaintext: bytes) -> Tuple[bytes, Cap Returns the ciphertext and the KEM Capsule. """ - params = alice_pubkey.params - - key, capsule = _encapsulate(alice_pubkey.point_key, params, DEM_KEYSIZE) + key, capsule = _encapsulate(alice_pubkey, DEM_KEYSIZE) capsule_bytes = bytes(capsule) @@ -455,8 +458,6 @@ def encrypt(alice_pubkey: UmbralPublicKey, plaintext: bytes) -> Tuple[bytes, Cap def _open_capsule(capsule: Capsule, receiving_privkey: UmbralPrivateKey, - delegating_pubkey: UmbralPublicKey, - signing_pubkey: UmbralPublicKey, check_proof=True) -> bytes: """ Activates the Capsule from the attached CFrags, @@ -466,33 +467,26 @@ def _open_capsule(capsule: Capsule, """ receiving_pubkey = receiving_privkey.get_pubkey() - priv_b = receiving_privkey.bn_key if check_proof: offending_cfrags = [] for cfrag in capsule._attached_cfrags: - if not cfrag.verify_correctness(capsule=capsule, - delegating_pubkey=delegating_pubkey, - signing_pubkey=signing_pubkey, - receiving_pubkey=receiving_pubkey): + if not cfrag.verify_correctness(capsule): offending_cfrags.append(cfrag) if offending_cfrags: error_msg = "Decryption error: Some CFrags are not correct" raise UmbralCorrectnessError(error_msg, offending_cfrags) - capsule._reconstruct_shamirs_secret(priv_b) + capsule._reconstruct_shamirs_secret(receiving_privkey) - key = _decapsulate_reencrypted(receiving_pubkey.point_key, priv_b, - delegating_pubkey.point_key, capsule) + key = _decapsulate_reencrypted(receiving_privkey, capsule) return key def decrypt(ciphertext: bytes, capsule: Capsule, decrypting_key: UmbralPrivateKey, - delegating_pubkey: UmbralPublicKey = None, - verifying_key = None, check_proof=True) -> bytes: """ Opens the capsule and gets what's inside. @@ -505,9 +499,7 @@ def decrypt(ciphertext: bytes, # Since there are cfrags attached, we assume this is Bob opening the Capsule. # (i.e., this is a re-encrypted capsule) - encapsulated_key = _open_capsule(capsule, decrypting_key, - delegating_pubkey, verifying_key, - check_proof=check_proof) + encapsulated_key = _open_capsule(capsule, decrypting_key, check_proof=check_proof) dem = UmbralDEM(encapsulated_key) original_capsule_bytes = capsule._original_to_bytes() @@ -515,7 +507,7 @@ def decrypt(ciphertext: bytes, else: # Since there aren't cfrags attached, we assume this is Alice opening the Capsule. # (i.e., this is an original capsule) - decapsulated_key = _decapsulate_original(decrypting_key.bn_key, capsule) + decapsulated_key = _decapsulate_original(decrypting_key, capsule) dem = UmbralDEM(decapsulated_key) capsule_bytes = bytes(capsule)