From 054777eb531abb2e5d66fbef5f5b48c4b109ff4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 2 Oct 2018 17:33:35 +0200 Subject: [PATCH] KFrags now has 2 signatures (Bob and proxy) and a new point * point_precursor removes need for point_xcoord and point_noninteractive * Fixes access to protected attributes in some places --- tests/functional/test_correctness.py | 26 -- tests/functional/test_vectors.py | 2 +- tests/scenario/test_simple_api.py | 2 +- tests/unit/test_capsule_correctness_checks.py | 6 +- tests/unit/test_capsule_operations.py | 2 +- tests/unit/test_capsule_serializers.py | 3 +- tests/unit/test_cfrags.py | 6 +- tests/unit/test_kfrags.py | 7 +- .../unit/test_serialization_property_based.py | 19 +- umbral/__about__.py | 2 +- umbral/_pre.py | 20 +- umbral/fragments.py | 86 ++++--- umbral/pre.py | 223 +++++++++--------- vectors/generate_test_vectors.py | 6 +- 14 files changed, 200 insertions(+), 210 deletions(-) diff --git a/tests/functional/test_correctness.py b/tests/functional/test_correctness.py index ab8c8f6..5f2c644 100644 --- a/tests/functional/test_correctness.py +++ b/tests/functional/test_correctness.py @@ -128,32 +128,6 @@ def test_cfrag_with_missing_proof_cannot_be_attached(kfrags, prepared_capsule): capsule.attach_cfrag(cfrag) -def test_inconsistent_cfrags(bobs_keys, kfrags, prepared_capsule): - receiving_privkey, receiving_pubkey = bobs_keys - - capsule = prepared_capsule - - cfrags = [] - for kfrag in kfrags: - cfrag = pre.reencrypt(kfrag, capsule) - cfrags.append(cfrag) - - # For all cfrags that belong to the same policy, the values - # cfrag._point_noninteractive and cfrag._point_noninteractive - # must be the same. If we swap them, it shouldn't be possible - # to attach the cfrag to the capsule. Let's mangle the first CFrag - cfrags[0]._point_noninteractive, cfrags[0]._point_xcoord = cfrags[0]._point_xcoord, cfrags[0]._point_noninteractive - with pytest.raises(pre.UmbralCorrectnessError): - capsule.attach_cfrag(cfrags[0]) - - #  The remaining M cfrags should be fine. - for cfrag in cfrags[1:]: - capsule.attach_cfrag(cfrag) - - # Just for fun, let's try to reconstruct the capsule with them: - capsule._reconstruct_shamirs_secret(receiving_privkey) - - def test_kfrags_signed_without_correctness_keys(alices_keys, bobs_keys, capsule): delegating_privkey, signing_privkey = alices_keys delegating_pubkey = delegating_privkey.get_pubkey() diff --git a/tests/functional/test_vectors.py b/tests/functional/test_vectors.py index ccf7261..6c19a79 100644 --- a/tests/functional/test_vectors.py +++ b/tests/functional/test_vectors.py @@ -188,7 +188,7 @@ def test_cfrags(): assert new_cfrag._point_e1 == cfrag._point_e1 assert new_cfrag._point_v1 == cfrag._point_v1 assert new_cfrag._kfrag_id == cfrag._kfrag_id - assert new_cfrag._point_noninteractive == cfrag._point_noninteractive + assert new_cfrag._point_precursor == cfrag._point_precursor assert new_cfrag._point_xcoord == cfrag._point_xcoord assert new_cfrag.proof is None assert cfrag.to_bytes() == new_cfrag.to_bytes() \ No newline at end of file diff --git a/tests/scenario/test_simple_api.py b/tests/scenario/test_simple_api.py index a7fdeff..a965eeb 100644 --- a/tests/scenario/test_simple_api.py +++ b/tests/scenario/test_simple_api.py @@ -97,4 +97,4 @@ def test_simple_api(N, M, curve=default_curve()): @pytest.mark.parametrize("N, M", parameters) def test_simple_api_on_multiple_curves(N, M, curve): test_simple_api(N, M, curve) - + diff --git a/tests/unit/test_capsule_correctness_checks.py b/tests/unit/test_capsule_correctness_checks.py index dc04c0a..98722c1 100644 --- a/tests/unit/test_capsule_correctness_checks.py +++ b/tests/unit/test_capsule_correctness_checks.py @@ -44,8 +44,7 @@ def test_cannot_attach_cfrag_without_keys(): 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(), + point_precursor=Point.gen_rand(), ) with pytest.raises(TypeError): @@ -67,8 +66,7 @@ def test_cannot_attach_cfrag_without_proof(): 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(), + point_precursor=Point.gen_rand(), ) key_details = capsule.set_correctness_keys( UmbralPrivateKey.gen_key().get_pubkey(), diff --git a/tests/unit/test_capsule_operations.py b/tests/unit/test_capsule_operations.py index c4eeb2b..7924e83 100644 --- a/tests/unit/test_capsule_operations.py +++ b/tests/unit/test_capsule_operations.py @@ -71,7 +71,7 @@ def test_capsule_equality(): activated_capsule = Capsule(params, point_e_prime=Point.gen_rand(), point_v_prime=Point.gen_rand(), - point_noninteractive=Point.gen_rand()) + point_precursor=Point.gen_rand()) assert activated_capsule != one_capsule diff --git a/tests/unit/test_capsule_serializers.py b/tests/unit/test_capsule_serializers.py index b8b9278..1548820 100644 --- a/tests/unit/test_capsule_serializers.py +++ b/tests/unit/test_capsule_serializers.py @@ -59,7 +59,6 @@ def test_activated_capsule_serialization(prepared_capsule, kfrags, bobs_keys): capsule.attach_cfrag(cfrag) - capsule._reconstruct_shamirs_secret(receiving_privkey) rec_capsule_bytes = capsule.to_bytes() @@ -74,7 +73,7 @@ def test_activated_capsule_serialization(prepared_capsule, kfrags, bobs_keys): assert new_rec_capsule._point_e_prime == capsule._point_e_prime assert new_rec_capsule._point_v_prime == capsule._point_v_prime - assert new_rec_capsule._point_noninteractive == capsule._point_noninteractive + assert new_rec_capsule._point_precursor == capsule._point_precursor def test_cannot_create_capsule_from_bogus_material(alices_keys): diff --git a/tests/unit/test_cfrags.py b/tests/unit/test_cfrags.py index a9faddf..3a2c0c8 100644 --- a/tests/unit/test_cfrags.py +++ b/tests/unit/test_cfrags.py @@ -37,7 +37,7 @@ def test_cfrag_serialization_with_proof_and_metadata(prepared_capsule, kfrags): assert new_cfrag._point_e1 == cfrag._point_e1 assert new_cfrag._point_v1 == cfrag._point_v1 assert new_cfrag._kfrag_id == cfrag._kfrag_id - assert new_cfrag._point_noninteractive == cfrag._point_noninteractive + assert new_cfrag._point_precursor == cfrag._point_precursor new_proof = new_cfrag.proof assert new_proof is not None @@ -68,7 +68,7 @@ def test_cfrag_serialization_with_proof_but_no_metadata(prepared_capsule, kfrags assert new_cfrag._point_e1 == cfrag._point_e1 assert new_cfrag._point_v1 == cfrag._point_v1 assert new_cfrag._kfrag_id == cfrag._kfrag_id - assert new_cfrag._point_noninteractive == cfrag._point_noninteractive + assert new_cfrag._point_precursor == cfrag._point_precursor new_proof = new_cfrag.proof assert new_proof is not None @@ -95,7 +95,7 @@ def test_cfrag_serialization_no_proof_no_metadata(prepared_capsule, kfrags): assert new_cfrag._point_e1 == cfrag._point_e1 assert new_cfrag._point_v1 == cfrag._point_v1 assert new_cfrag._kfrag_id == cfrag._kfrag_id - assert new_cfrag._point_noninteractive == cfrag._point_noninteractive + assert new_cfrag._point_precursor == cfrag._point_precursor new_proof = new_cfrag.proof assert new_proof is None diff --git a/tests/unit/test_kfrags.py b/tests/unit/test_kfrags.py index 30f218a..f1eb015 100644 --- a/tests/unit/test_kfrags.py +++ b/tests/unit/test_kfrags.py @@ -30,11 +30,10 @@ def test_kfrag_serialization(alices_keys, bobs_keys, kfrags): assert len(kfrag_bytes) == KFrag.expected_bytes_length() new_kfrag = KFrag.from_bytes(kfrag_bytes) - assert new_kfrag._id == kfrag._id + assert new_kfrag.id == kfrag.id assert new_kfrag._bn_key == kfrag._bn_key - assert new_kfrag._point_noninteractive == kfrag._point_noninteractive + assert new_kfrag._point_precursor == kfrag._point_precursor assert new_kfrag._point_commitment == kfrag._point_commitment - assert new_kfrag._point_xcoord == kfrag._point_xcoord assert new_kfrag.verify(signing_pubkey=signing_privkey.get_pubkey(), delegating_pubkey=delegating_privkey.get_pubkey(), @@ -48,7 +47,7 @@ def test_kfrag_verify_for_capsule(prepared_capsule, kfrags): assert kfrag.verify_for_capsule(prepared_capsule) # If we alter some element, the verification fails - previous_id, kfrag._id = kfrag._id, bytes(32) + previous_id, kfrag._id = kfrag.id, bytes(32) assert not kfrag.verify_for_capsule(prepared_capsule) # Let's restore the KFrag, and alter the re-encryption key instead diff --git a/tests/unit/test_serialization_property_based.py b/tests/unit/test_serialization_property_based.py index 2c22531..eb2ba47 100644 --- a/tests/unit/test_serialization_property_based.py +++ b/tests/unit/test_serialization_property_based.py @@ -49,12 +49,12 @@ signatures = tuples(integers(min_value=1, max_value=backend._bn_to_int(curve.ord # # utility def assert_kfrag_eq(k0, k1): - assert(all([ k0._id == k1._id - , k0._bn_key == k1._bn_key - , k0._point_noninteractive == k1._point_noninteractive - , k0._point_commitment == k1._point_commitment - , k0._point_xcoord == k1._point_xcoord - , k0.signature == k1.signature + assert(all([ k0.id == k1.id + , k0._bn_key == k1._bn_key + , k0._point_precursor == k1._point_precursor + , k0._point_commitment == k1._point_commitment + , k0.signature_for_bob == k1.signature_for_bob + , k0.signature_for_proxy == k1.signature_for_proxy ])) def assert_cp_eq(c0, c1): @@ -79,10 +79,11 @@ def test_bn_roundtrip(bn): def test_point_roundtrip(p, c): assert(p == Point.from_bytes(p.to_bytes(is_compressed=c))) -@given(binary(min_size=bn_size, max_size=bn_size), bns, points, points, points, signatures) +@given(binary(min_size=bn_size, max_size=bn_size), bns, points, points, signatures, signatures) @settings(max_examples=max_examples, timeout=unlimited) -def test_kfrag_roundtrip(d, b0, p0, p1, p2, sig): - k = KFrag(d, b0, p0, p1, p2, sig) +def test_kfrag_roundtrip(d, b0, p0, p1, sig_proxy, sig_bob): + k = KFrag(identifier=d, bn_key=b0, point_commitment=p0, point_precursor=p1, + signature_for_proxy=sig_proxy, signature_for_bob=sig_bob) assert_kfrag_eq(k, KFrag.from_bytes(k.to_bytes())) @given(points, points, bns) diff --git a/umbral/__about__.py b/umbral/__about__.py index d959a80..92da21b 100644 --- a/umbral/__about__.py +++ b/umbral/__about__.py @@ -8,7 +8,7 @@ __title__ = "umbral" __url__ = "https://github.com/nucypher/pyUmbral" -__summary__ = 'Nucypher\'s Umbral Proxy Re-Encryption Implementation', +__summary__ = 'NuCypher\'s Umbral Proxy Re-Encryption Implementation', __version__ = "0.1.0-alpha.4" diff --git a/umbral/_pre.py b/umbral/_pre.py index a1a4991..533f7b6 100644 --- a/umbral/_pre.py +++ b/umbral/_pre.py @@ -31,7 +31,7 @@ def prove_cfrag_correctness(cfrag: 'CapsuleFrag', metadata: Optional[bytes] = None ) -> None: - params = capsule._umbral_params + params = capsule.params # Check correctness of original ciphertext if not capsule.verify(): @@ -63,7 +63,7 @@ def prove_cfrag_correctness(cfrag: 'CapsuleFrag', z3 = t + h * rk - cfrag.attach_proof(e2, v2, u1, u2, metadata=metadata, z3=z3, kfrag_signature=kfrag.signature) + cfrag.attach_proof(e2, v2, u1, u2, metadata=metadata, z3=z3, kfrag_signature=kfrag.signature_for_bob) def assess_cfrag_correctness(cfrag: 'CapsuleFrag', capsule: 'Capsule') -> bool: @@ -74,7 +74,7 @@ def assess_cfrag_correctness(cfrag: 'CapsuleFrag', capsule: 'Capsule') -> bool: signing_pubkey = correctness_keys['verifying'] receiving_pubkey = correctness_keys['receiving'] - params = capsule._umbral_params + params = capsule.params #### # Here are the formulaic constituents shared with `prove_cfrag_correctness`. @@ -104,8 +104,7 @@ def assess_cfrag_correctness(cfrag: 'CapsuleFrag', capsule: 'Capsule') -> bool: h = CurveBN.hash(*hash_input, params=params) ######## - ni = cfrag._point_noninteractive - xcoord = cfrag._point_xcoord + precursor = cfrag._point_precursor kfrag_id = cfrag._kfrag_id pubkey_size = UmbralPublicKey.expected_bytes_length(curve=params.curve) @@ -116,7 +115,7 @@ def assess_cfrag_correctness(cfrag: 'CapsuleFrag', capsule: 'Capsule') -> bool: if receiving_pubkey is None: receiving_pubkey = b'\x00' * pubkey_size - validity_input = (kfrag_id, delegating_pubkey, receiving_pubkey, u1, ni, xcoord) + validity_input = (kfrag_id, delegating_pubkey, receiving_pubkey, u1, precursor) kfrag_validity_message = bytes().join(bytes(item) for item in validity_input) valid_kfrag_signature = cfrag.proof.kfrag_signature.verify(kfrag_validity_message, signing_pubkey) @@ -152,11 +151,10 @@ def verify_kfrag(kfrag: 'KFrag', u = params.u - kfrag_id = kfrag._id + kfrag_id = kfrag.id key = kfrag._bn_key u1 = kfrag._point_commitment - ni = kfrag._point_noninteractive - xcoord = kfrag._point_xcoord + precursor = kfrag._point_precursor #  We check that the commitment u1 is well-formed correct_commitment = u1 == key * u @@ -169,9 +167,9 @@ def verify_kfrag(kfrag: 'KFrag', if receiving_pubkey is None: receiving_pubkey = b'\x00' * pubkey_size - validity_input = (kfrag_id, delegating_pubkey, receiving_pubkey, u1, ni, xcoord) + validity_input = (kfrag_id, delegating_pubkey, receiving_pubkey, u1, precursor) kfrag_validity_message = bytes().join(bytes(item) for item in validity_input) - valid_kfrag_signature = kfrag.signature.verify(kfrag_validity_message, signing_pubkey) + valid_kfrag_signature = kfrag.signature_for_proxy.verify(kfrag_validity_message, signing_pubkey) return correct_commitment & valid_kfrag_signature diff --git a/umbral/fragments.py b/umbral/fragments.py index 0032219..aa0d3d6 100644 --- a/umbral/fragments.py +++ b/umbral/fragments.py @@ -34,20 +34,26 @@ from umbral.params import UmbralParameters class KFrag(object): + def __init__(self, + identifier: bytes, + bn_key: CurveBN, + point_commitment: Point, + point_precursor: Point, + signature_for_proxy: Signature, + signature_for_bob: Signature, + ) -> None: + self.id = identifier + self._bn_key = bn_key + self._point_commitment = point_commitment + self._point_precursor = point_precursor + self.signature_for_proxy = signature_for_proxy + self.signature_for_bob = signature_for_bob + class NotValid(ValueError): """ raised if the KFrag does not pass verification. """ - def __init__(self, id: bytes, bn_key: CurveBN, point_noninteractive: Point, - point_commitment: Point, point_xcoord: Point, signature: Signature) -> None: - self._id = id - self._bn_key = bn_key - self._point_noninteractive = point_noninteractive - self._point_commitment = point_commitment - self._point_xcoord = point_xcoord - self.signature = signature - @classmethod def expected_bytes_length(cls, curve: Optional[EllipticCurve] = None) -> int: """ @@ -58,7 +64,14 @@ class KFrag(object): bn_size = CurveBN.expected_bytes_length(curve) point_size = Point.expected_bytes_length(curve) - return (bn_size * 4) + (point_size * 3) + # self.id --> 1 bn_size + # self._bn_key --> 1 bn_size + # self._point_commitment --> 1 point_size + # self._point_precursor --> 1 point_size + # self.signature_for_proxy --> 2 bn_size + # self.signature_for_bob --> 2 bn_size + + return bn_size * 6 + point_size * 2 @classmethod def from_bytes(cls, data: bytes, curve: Optional[EllipticCurve] = None) -> 'KFrag': @@ -69,15 +82,16 @@ class KFrag(object): bn_size = CurveBN.expected_bytes_length(curve) point_size = Point.expected_bytes_length(curve) + signature_size = Signature.expected_bytes_length(curve) arguments = {'curve': curve} splitter = BytestringSplitter( bn_size, # id (CurveBN, bn_size, arguments), # bn_key - (Point, point_size, arguments), # point_noninteractive (Point, point_size, arguments), # point_commitment - (Point, point_size, arguments), # point_xcoord - (Signature, Signature.expected_bytes_length(curve), arguments) + (Point, point_size, arguments), # point_precursor + (Signature, signature_size, arguments), # signature_for_proxy + (Signature, signature_size, arguments), # signature_for_bob ) components = splitter(data) @@ -88,20 +102,20 @@ class KFrag(object): Serialize the KFrag into a bytestring. """ key = self._bn_key.to_bytes() - ni = self._point_noninteractive.to_bytes() commitment = self._point_commitment.to_bytes() - xcoord = self._point_xcoord.to_bytes() - signature = bytes(self.signature) + precursor = self._point_precursor.to_bytes() + signature_for_proxy = bytes(self.signature_for_proxy) + signature_for_bob = bytes(self.signature_for_bob) - return self._id + key + ni + commitment + xcoord + signature + return self.id + key + commitment + precursor \ + + signature_for_proxy + signature_for_bob def verify(self, signing_pubkey: UmbralPublicKey, delegating_pubkey: UmbralPublicKey = None, receiving_pubkey: UmbralPublicKey = None, params: Optional[UmbralParameters] = None, - ) -> bool: - + ) -> bool: if params is None: params = default_params() return verify_kfrag(kfrag=self, @@ -111,10 +125,9 @@ class KFrag(object): receiving_pubkey=receiving_pubkey) def verify_for_capsule(self, capsule: 'Capsule') -> bool: - correctness_keys = capsule.get_correctness_keys() - return self.verify(params=capsule._umbral_params, + return self.verify(params=capsule.params, signing_pubkey=correctness_keys["verifying"], delegating_pubkey=correctness_keys["delegating"], receiving_pubkey=correctness_keys["receiving"]) @@ -126,10 +139,10 @@ class KFrag(object): return hmac.compare_digest(bytes(self), bytes(other)) def __hash__(self): - return hash(bytes(self._id)) + return hash(bytes(self.id)) def __repr__(self): - return "{}:{}".format(self.__class__.__name__, self._id.hex()[:15]) + return "{}:{}".format(self.__class__.__name__, self.id.hex()[:15]) class CorrectnessProof(object): @@ -207,14 +220,12 @@ class CapsuleFrag(object): point_e1: Point, point_v1: Point, kfrag_id: bytes, - point_noninteractive: Point, - point_xcoord: Point, + point_precursor: Point, proof: Optional[CorrectnessProof] = None) -> None: self._point_e1 = point_e1 self._point_v1 = point_v1 self._kfrag_id = kfrag_id - self._point_noninteractive = point_noninteractive - self._point_xcoord = point_xcoord + self._point_precursor = point_precursor self.proof = proof class NoProofProvided(TypeError): @@ -233,7 +244,7 @@ class CapsuleFrag(object): bn_size = CurveBN.expected_bytes_length(curve) point_size = Point.expected_bytes_length(curve) - return (bn_size * 1) + (point_size * 4) + return (bn_size * 1) + (point_size * 3) @classmethod def from_bytes(cls, data: bytes, curve: Optional[EllipticCurve] = None) -> 'CapsuleFrag': @@ -250,8 +261,7 @@ class CapsuleFrag(object): (Point, point_size, arguments), # point_e1 (Point, point_size, arguments), # point_v1 bn_size, # kfrag_id - (Point, point_size, arguments), # point_noninteractive - (Point, point_size, arguments) # point_xcoord + (Point, point_size, arguments), # point_precursor ) components = splitter(data, return_remainder=True) @@ -265,10 +275,9 @@ class CapsuleFrag(object): """ e1 = self._point_e1.to_bytes() v1 = self._point_v1.to_bytes() - ni = self._point_noninteractive.to_bytes() - xcoord = self._point_xcoord.to_bytes() + precursor = self._point_precursor.to_bytes() - serialized_cfrag = e1 + v1 + self._kfrag_id + ni + xcoord + serialized_cfrag = e1 + v1 + self._kfrag_id + precursor if self.proof is not None: serialized_cfrag += self.proof.to_bytes() @@ -278,8 +287,15 @@ class CapsuleFrag(object): def verify_correctness(self, capsule: 'Capsule') -> bool: return assess_cfrag_correctness(self, capsule) - def attach_proof(self, e2: Point, v2: Point, u1: Point, u2: Point, z3: CurveBN, kfrag_signature: Signature, + def attach_proof(self, + e2: Point, + v2: Point, + u1: Point, + u2: Point, + z3: CurveBN, + kfrag_signature: Signature, metadata: Optional[bytes]) -> None: + self.proof = CorrectnessProof(point_e2=e2, point_v2=v2, point_kfrag_commitment=u1, @@ -293,4 +309,4 @@ class CapsuleFrag(object): return self.to_bytes() def __repr__(self): - return "CFrag:{}".format(self._point_e1.to_bytes().hex()[2:17]) \ No newline at end of file + return "CFrag:{}".format(self._point_e1.to_bytes().hex()[2:17]) diff --git a/umbral/pre.py b/umbral/pre.py index 9701d18..d2be2dd 100644 --- a/umbral/pre.py +++ b/umbral/pre.py @@ -21,8 +21,6 @@ import os import typing from typing import Dict, List, Optional, Tuple, Union -from cryptography.hazmat.backends.openssl import backend -from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurve from bytestring_splitter import BytestringSplitter @@ -56,19 +54,19 @@ class Capsule(object): bn_sig: Optional[CurveBN] = None, point_e_prime: Optional[Point] = None, point_v_prime: Optional[Point] = None, - point_noninteractive: Optional[Point] = None, + point_precursor: Optional[Point] = None, delegating_pubkey: Optional[UmbralPublicKey] = None, receiving_pubkey: Optional[UmbralPublicKey] = None, verifying_pubkey: None = None ) -> None: - self._umbral_params = params # TODO: Change to self.params (#167) + self.params = params if isinstance(point_e, Point): if not isinstance(point_v, Point) or not isinstance(bn_sig, CurveBN): raise TypeError("Need point_e, point_v, and bn_sig to make a Capsule.") elif isinstance(point_e_prime, Point): - if not isinstance(point_v_prime, Point) or not isinstance(point_noninteractive, Point): + if not isinstance(point_v_prime, Point) or not isinstance(point_precursor, Point): raise TypeError("Need e_prime, v_prime, and point_noninteractive to make an activated Capsule.") else: raise TypeError( @@ -85,7 +83,7 @@ class Capsule(object): self._point_e_prime = point_e_prime self._point_v_prime = point_v_prime - self._point_noninteractive = point_noninteractive + self._point_precursor = point_precursor self._attached_cfrags = list() # type: list @@ -136,7 +134,7 @@ class Capsule(object): (CurveBN, bn_size, arguments), # bn_sig (Point, point_size, arguments), # point_e_prime (Point, point_size, arguments), # point_v_prime - (Point, point_size, arguments) # point_noninteractive + (Point, point_size, arguments) # point_precursor ) else: raise ValueError("Byte string does not have a valid length for a Capsule") @@ -153,7 +151,7 @@ class Capsule(object): if current_key is None: if key is None: return False - elif self._umbral_params != key.params: + elif self.params != key.params: raise TypeError("You are trying to set a key with different UmbralParameters.") else: self._cfrag_correctness_keys[key_type] = key @@ -193,11 +191,11 @@ class Capsule(object): def verify(self) -> bool: - g = self._umbral_params.g + g = self.params.g e = self._point_e v = self._point_v s = self._bn_sig - h = CurveBN.hash(e, v, params=self._umbral_params) + h = CurveBN.hash(e, v, params=self.params) result = s * g == v + (h * e) # type: bool return result @@ -213,51 +211,44 @@ class Capsule(object): return self._point_e, self._point_v, self._bn_sig 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 + return self._point_e_prime, self._point_v_prime, self._point_precursor def _reconstruct_shamirs_secret(self, priv_b: UmbralPrivateKey) -> None: - params = self._umbral_params - g = params.g + params = self.params pub_b = priv_b.get_pubkey() priv_b = priv_b.bn_key - cfrag_0 = self._attached_cfrags[0] - id_0 = cfrag_0._kfrag_id - ni = cfrag_0._point_noninteractive - xcoord = cfrag_0._point_xcoord - - dh_xcoord = priv_b * xcoord - - blake2b = hashes.Hash(hashes.BLAKE2b(64), backend=backend) - blake2b.update(xcoord.to_bytes()) - blake2b.update(pub_b.to_bytes()) - blake2b.update(dh_xcoord.to_bytes()) - hashed_dh_tuple = blake2b.finalize() + precursor = self._attached_cfrags[0]._point_precursor + dh_point = priv_b * precursor if len(self._attached_cfrags) > 1: - xs = [CurveBN.hash(cfrag._kfrag_id, hashed_dh_tuple, params=params) + xs = [CurveBN.hash(precursor, + pub_b, + dh_point, + b"X-COORDINATE", + cfrag._kfrag_id, + params=params) for cfrag in self._attached_cfrags] - x_0 = CurveBN.hash(id_0, hashed_dh_tuple, params=params) - lambda_0 = lambda_coeff(x_0, xs) - e = lambda_0 * cfrag_0._point_e1 - v = lambda_0 * cfrag_0._point_v1 - for cfrag in self._attached_cfrags[1:]: - if (ni, xcoord) != (cfrag._point_noninteractive, cfrag._point_xcoord): + e_summands = list() + v_summands = list() + for cfrag, x in zip(self._attached_cfrags, xs): + if precursor != cfrag._point_precursor: raise ValueError("Attached CFrags are not pairwise consistent") - x_i = CurveBN.hash(cfrag._kfrag_id, hashed_dh_tuple, params=params) - lambda_i = lambda_coeff(x_i, xs) - e = e + (lambda_i * cfrag._point_e1) - v = v + (lambda_i * cfrag._point_v1) - else: - e = cfrag_0._point_e1 - v = cfrag_0._point_v1 + lambda_i = lambda_coeff(x, xs) + e_summands.append(lambda_i * cfrag._point_e1) + v_summands.append(lambda_i * cfrag._point_v1) - self._point_e_prime = e - self._point_v_prime = v - self._point_noninteractive = ni + self._point_e_prime = sum(e_summands[1:], e_summands[0]) + self._point_v_prime = sum(v_summands[1:], v_summands[0]) + + else: + self._point_e_prime = self._attached_cfrags[0]._point_e1 + self._point_v_prime = self._attached_cfrags[0]._point_v1 + + self._point_precursor = precursor def __bytes__(self) -> bytes: return self.to_bytes() @@ -308,11 +299,11 @@ def split_rekey(delegating_privkey: UmbralPrivateKey, sign_receiving_key : Optional[bool] = True, ) -> 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. + 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 KFrags. + Returns a dictionary which includes the list of N KFrags """ if threshold <= 0 or threshold > N: @@ -328,64 +319,77 @@ def split_rekey(delegating_privkey: UmbralPrivateKey, delegating_pubkey = delegating_privkey.get_pubkey() delegating_privkey = delegating_privkey.bn_key - pubkey_b_point = receiving_pubkey.point_key + bob_pubkey_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) + # 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 - coeffs = [delegating_privkey * (~d)] - coeffs += [CurveBN.gen_rand(params.curve) for _ in range(threshold - 1)] + dh_point = private_precursor * bob_pubkey_point - u = params.u + # Secret value 'd' allows to make Umbral non-interactive + d = CurveBN.hash(precursor, + bob_pubkey_point, + dh_point, + b"NON-INTERACTIVE", + params=params) - # '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() + # Coefficients of the generating polynomial + coefficients = [delegating_privkey * (~d)] + coefficients += [CurveBN.gen_rand(params.curve) for _ in range(threshold - 1)] bn_size = CurveBN.expected_bytes_length(params.curve) - kfrags = [] + kfrags = list() for _ in range(N): kfrag_id = os.urandom(bn_size) - share_x = CurveBN.hash(kfrag_id, hashed_dh_tuple, params=params) + # 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 = CurveBN.hash(precursor, + bob_pubkey_point, + dh_point, + b"X-COORDINATE", + kfrag_id, + params=params) - rk = poly_eval(coeffs, share_x) + # The re-encryption key share is the result of evaluating the generating + # polynomial for the index value + rk = poly_eval(coefficients, share_index) - u1 = rk * u + commitment = rk * params.u + + validity_message_for_bob = (kfrag_id, + delegating_pubkey, + receiving_pubkey, + commitment, + precursor, + ) + validity_message_for_bob = bytes().join(bytes(item) for item in validity_message_for_bob) + signature_for_bob = signer(validity_message_for_bob) pubkey_size = UmbralPublicKey.expected_bytes_length(curve=params.curve) - if not sign_delegating_key: - delegating_pubkey = b'\x00' * pubkey_size - if not sign_receiving_key: - receiving_pubkey = b'\x00' * pubkey_size + blank_pubkey = b'\x00' * pubkey_size - validity_input = (kfrag_id, delegating_pubkey, receiving_pubkey, u1, ni, xcoord) + validity_message_for_proxy = (kfrag_id, + delegating_pubkey if sign_delegating_key else blank_pubkey, + receiving_pubkey if sign_receiving_key else blank_pubkey, + commitment, + precursor, + ) + validity_message_for_proxy = bytes().join(bytes(item) for item in validity_message_for_proxy) + signature_for_proxy = signer(validity_message_for_proxy) - kfrag_validity_message = bytes().join(bytes(item) for item in validity_input) - signature = signer(kfrag_validity_message) - - kfrag = KFrag(id=kfrag_id, + kfrag = KFrag(identifier=kfrag_id, bn_key=rk, - point_noninteractive=ni, - point_commitment=u1, - point_xcoord=xcoord, - signature=signature) + point_commitment=commitment, + point_precursor=precursor, + signature_for_proxy=signature_for_proxy, + signature_for_bob=signature_for_bob, + ) kfrags.append(kfrag) @@ -405,9 +409,8 @@ def reencrypt(kfrag: KFrag, capsule: Capsule, provide_proof: bool = True, e1 = rk * capsule._point_e v1 = rk * capsule._point_v - cfrag = CapsuleFrag(point_e1=e1, point_v1=v1, kfrag_id=kfrag._id, - point_noninteractive=kfrag._point_noninteractive, - point_xcoord=kfrag._point_xcoord) + cfrag = CapsuleFrag(point_e1=e1, point_v1=v1, kfrag_id=kfrag.id, + point_precursor=kfrag._point_precursor) if provide_proof: prove_cfrag_correctness(cfrag, kfrag, capsule, metadata) @@ -439,51 +442,54 @@ def _encapsulate(alice_pubkey: UmbralPublicKey, return key, Capsule(point_e=pub_r, point_v=pub_u, bn_sig=s, params=params) -def _decapsulate_original(priv_key: UmbralPrivateKey, capsule: Capsule, +def _decapsulate_original(priv_key: UmbralPrivateKey, + capsule: Capsule, key_length: int = 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) - if not capsule.verify(): # Check correctness of original ciphertext - # (check nº 2) at the end to avoid timing oracles raise capsule.NotValid("Capsule verification failed.") + shared_key = priv_key.bn_key * (capsule._point_e + capsule._point_v) + key = kdf(shared_key, key_length) return key def _decapsulate_reencrypted(receiving_privkey: UmbralPrivateKey, capsule: Capsule, key_length: int = DEM_KEYSIZE) -> bytes: - """Derive the same symmetric key""" - params = capsule._umbral_params + """Derive the same symmetric encapsulated_key""" + params = capsule.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) + precursor = capsule._point_precursor - e_prime = capsule._point_e_prime - v_prime = capsule._point_v_prime + dh_point = priv_key * precursor - shared_key = d * (e_prime + v_prime) - - key = kdf(shared_key, key_length) + # Secret value 'd' allows to make Umbral non-interactive + d = CurveBN.hash(precursor, + pub_key, + dh_point, + b"NON-INTERACTIVE", + params=params) + inv_d = ~d e = capsule._point_e v = capsule._point_v s = capsule._bn_sig h = CurveBN.hash(e, v, params=params) - inv_d = ~d + e_prime = capsule._point_e_prime + v_prime = capsule._point_v_prime 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() - return key + + shared_key = d * (e_prime + v_prime) + encapsulated_key = kdf(shared_key, key_length) + return encapsulated_key def encrypt(alice_pubkey: UmbralPublicKey, plaintext: bytes) -> Tuple[bytes, Capsule]: @@ -553,5 +559,4 @@ def decrypt(ciphertext: bytes, capsule: Capsule, decrypting_key: UmbralPrivateKe dem = UmbralDEM(encapsulated_key) cleartext = dem.decrypt(ciphertext, authenticated_data=capsule_bytes) - return cleartext diff --git a/vectors/generate_test_vectors.py b/vectors/generate_test_vectors.py index c277ff2..76d8d21 100644 --- a/vectors/generate_test_vectors.py +++ b/vectors/generate_test_vectors.py @@ -60,9 +60,9 @@ plain_data = b'peace at dawn' ciphertext, capsule = pre.encrypt(delegating_key, plain_data) 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._umbral_params.u, cfrag.proof._point_kfrag_commitment, cfrag.proof._point_kfrag_pok ] +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