Skip to content

Re-encryption with correctness proofs#115

Merged
cygnusv merged 36 commits intonucypher:masterfrom
cygnusv:cfrags-with-correctness-proof
Apr 24, 2018
Merged

Re-encryption with correctness proofs#115
cygnusv merged 36 commits intonucypher:masterfrom
cygnusv:cfrags-with-correctness-proof

Conversation

@cygnusv
Copy link
Member

@cygnusv cygnusv commented Apr 17, 2018

What this does:

  • Renames ChallengeResponse to CorrectnessProof, and integrates it inside CFrags (Solves Integrate the challenge protocol as part of the re-encryption request/response #100)
  • Re-encryption now produces correctness proofs by default, although this can be disabled with the parameter provide_proof.
  • Re-encryption correctness is verified during decryption. If it fails, a new type of exception called UmbralCorrectnessError is raised, which includes a list of the CFrags that don't pass the correctness verification (so that Bob can take the action he considers more convenient).
  • Metadata is now integrated into the CorrectnessProof (and hence, into the CFrag). As a consequence, metadata is now a parameter of pre.reencrypt() (although optional).
  • Metadata can be any arbitrary bytes object (Closes Define format for re-encryption metadata? #120)
  • Tests for everything

@cygnusv cygnusv requested review from jMyles and tuxxy and removed request for jMyles and tuxxy April 17, 2018 10:44
@cygnusv cygnusv changed the title [WIP] CFrags with correctness proof Re-encryption with correctness proofs Apr 22, 2018
Copy link
Contributor

@tuxxy tuxxy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, see my comments.

'timestamp' : time.time(),
'capsule' : bytes(capsule),
}
metadata = str(metadata).encode()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Obviously, we want a better form of serialization than just turning a dict into a string.

I presume this is being blocked due to not having a format for metadata in the KMS?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, to me this is just a rough example for testing purposes. We want something reasonable, beyond a simple b"this is the metadata of a request", but without being something too complex.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a way, I think b"this is the metadata of a request" is better.

In this case, we're showing casting a dict, with an int for ursula_id, and time.time()? I mean, are we really nudging the user in a good direction here?

I think it's probably better to either structure metadata or leave it as flat, arbitrary bytes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, let's go the the initial approach then.

sig = BigNum.from_bytes(data.read(key_size), curve)

metadata = data.read()
if metadata == bytes(0):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is build a bit weirdly.

You can simply do metadata = data.read() or None. If there is no data, it will be b'' which loses precedence to None, then you don't have to set it to None.

That is unless you are setting metadata to be 0x00 and it will always have that byte, but I don't like that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

+ sig

if self.metadata is not None:
result = result + self.metadata
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also do result += self.metadata or b''. This will concat the empty bytestring if self.metadata is None and removes the need for L135.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


return cls(e1, v1, kfrag_id, eph_ni)
proof = data.read()
proof = CorrectnessProof.from_bytes(proof, curve) if proof != bytes(0) else None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same logic applies here from what I listed above.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

serialized_cfrag = e1 + v1 + kfrag_id + eph_ni

if self.proof is not None:
serialized_cfrag += self.proof.to_bytes()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As well as here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mmm, no, it fails when self.proof is None. I'll revert it to the previous way.


def _open_capsule(capsule: Capsule, bob_private_key: UmbralPrivateKey,
alice_pub_key: UmbralPublicKey, params: UmbralParameters=None) -> bytes:
def _open_capsule(capsule: Capsule, bob_privkey: UmbralPrivateKey,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

priv_b = bob_private_key.bn_key
pub_b = priv_b * params.g
priv_b = bob_privkey.bn_key
pub_b = bob_privkey.get_pubkey().point_key
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If my PR gets in before this one, you can do bob_privkey.pubkey.point_key because it gets cached.

pub_a = alice_pub_key.point_key
pub_a = alice_pubkey.point_key

# TODO: Change dict for a list if issue #116 goes through
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an old TODO?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I added it here in light of issue #116

def _challenge(kfrag: KFrag, capsule: Capsule,
cfrag: CapsuleFrag, challenge_metadata: bytes=None,
params: UmbralParameters=None) -> ChallengeResponse:
def _prove_correctness(cfrag: CapsuleFrag, kfrag: KFrag, capsule: Capsule,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think _prove_correctness has to be above reencrypt for it to be used?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might it also be better as a classmethod on CorrectnessProof?

class UmbralCorrectnessError(GenericUmbralError):
def __init__(self, message, offending_cfrags):
super().__init__(message)
self.offending_cfrags = offending_cfrags
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a need to capture these here even if we handle them ~L485?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to include them in the exception, so in the application (i.e., the KMS), Bob could know which cfrags were incorrect and act consequently.

cfrag = pre.reencrypt(kfrag, capsule, metadata=metadata)
capsule.attach_cfrag(cfrag)

assert pre._verify_correctness(capsule, cfrag,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the simplicity of the interface!

Copy link

@michwill michwill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

Copy link
Contributor

@jMyles jMyles left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a clear step forward in terms of the tooling. I think that exactly how Bob can expect to benefit needs to be reflected with updated documentation.


from umbral import pre, keys
from umbral.point import Point
from umbral.fragments import CorrectnessProof
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like you imported this and then didn't use it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was using it through pre.CorrectnessProof, which was it's previous location. I forgot to change it. Now it's fixed.

'timestamp' : time.time(),
'capsule' : bytes(capsule),
}
metadata = str(metadata).encode()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a way, I think b"this is the metadata of a request" is better.

In this case, we're showing casting a dict, with an int for ursula_id, and time.time()? I mean, are we really nudging the user in a good direction here?

I think it's probably better to either structure metadata or leave it as flat, arbitrary bytes.


# 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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, groovy.

umbral/pre.py Outdated
try:
proof = cfrag.proof
except AttributeError:
raise ValueError("CFrag doesn't have a correctnes proof attached")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Untested - needs a test I think.

Copy link
Member Author

@cygnusv cygnusv Apr 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it's better to remove the try, since it doesn't fail when cfrag.proof is set to None, which will fail anyway a few lines below. So, I wrote a test that checks that the AttributeError is raised when there's no proof but one is expected. It's called test_decryption_fails_when_it_expects_a_proof_and_there_isnt.


if self.metadata is not None:
result = result + self.metadata
result += self.metadata or b''
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an odd line - the user is not allowed to use something falsey as metadata?

Copy link
Contributor

@tuxxy tuxxy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐧 Looks good to merge.

@cygnusv cygnusv merged commit 01d8e6f into nucypher:master Apr 24, 2018
@cygnusv cygnusv deleted the cfrags-with-correctness-proof branch April 24, 2018 08:36
@cygnusv cygnusv mentioned this pull request May 8, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants