Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VRF API refactor #137

Merged
merged 9 commits into from
Nov 22, 2022
Merged

VRF API refactor #137

merged 9 commits into from
Nov 22, 2022

Conversation

tessico
Copy link
Contributor

@tessico tessico commented Nov 17, 2022

Description

closes: #125


Before we can merge this PR, please make sure that all the following items have been
checked off. If any of the checklist items are not applicable, please leave them but
write a little note why.

  • Targeted PR against correct branch (main)
  • Linked to GitHub issue with discussion and accepted design OR have an explanation in the PR that describes this work.
  • Wrote unit tests
  • Updated relevant documentation in the code
  • Added a relevant changelog entry to the Pending section in CHANGELOG.md
  • Re-reviewed Files changed in the GitHub PR explorer

@CLAassistant
Copy link

CLAassistant commented Nov 17, 2022

CLA assistant check
All committers have signed the CLA.

@chancharles92
Copy link
Contributor

@tessico Let me know when the PR is ready, and you can add me and @alxiong as reviewers.

Copy link
Contributor

@alxiong alxiong left a comment

Choose a reason for hiding this comment

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

Good job 👍 ! @tessico , only a few comments

  • maybe you can just start a branch from this repo instead of from your fork, (I believe you have write access, right?) so that it's slightly easier for us to switch to your branch without adding another git remote. (thanks!)
  • please also take a look at the pain of adding trait bound in the downstream here, we should add these traits to our trait VrfScheme::PublicKey/SecretKey/Proof etc) cc @DieracDelta

primitives/src/vrf/blsvrf.rs Show resolved Hide resolved
Comment on lines 64 to 69
&self,
pp: &Self::PublicParameter,
proof: &Self::Proof,
public_key: &Self::PublicKey,
input: &Self::Input,
) -> Result<bool, PrimitivesError>;
Copy link
Contributor

Choose a reason for hiding this comment

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

@philippecamacho suggested previously that we should add output: Self::Output to the input param to verify() so that the users won't accidentally use the wrong (proof, output) combo.

as you could potentially use a proof, then H_1(proof, "dom sep: app1") and H_2(proof, "dom sep: app2) to get different VRF output.

I believe we knew it's not the most standard-compliant API but it's less error-prone.

cc @fkrell @chancharles92

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you mean that the verify function will take proof, output, ... and internally check evaluate(proof) =? output as an extra safety check?

I'm against that. It would mean that a user (potentially) runs the evaluate sub-routing twice - first to actually get the VRF output and second to verify that a proof is valid.

Unless the only scenario when someone calls verify(proof, output, ...) is when they are provided both proof and output by a prover - then the "safe" API would make sense.
But I can imagine that a more likely scenario is when the prover produces a proof, runs evaluate for themselves, and only posts/shares a proof without the output - leaving it for the verifier to run evaluate for themselves (once!) and then finally verify(proof, ...).

Copy link
Contributor

@chancharles92 chancharles92 Nov 18, 2022

Choose a reason for hiding this comment

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

I think we can have 2 verify APIs, the new set of APIs is as follows:

  1. prove(pp, sec_key, input) -> proof
  2. proof_to_hash(pp, proof) -> output
  3. evaluate(pp, sec_key, input) -> output (i.e. proof_to_hash(pp, prove(pp, sec_key, input)))
  4. verify(pp, pub_key, proof, input) -> (bit, output) (this API also outputs hash as the hash is much cheaper than sig verification and the user doesn't need to call proof_to_hash again to obtain the hash)
  5. verify_hash_consistency(pp, proof, output) -> bit (note that this API is also cheap as it doesn't check signature)

The APIs 1..4 are exactly identical to the standard (https://datatracker.ietf.org/doc/draft-irtf-cfrg-vrf/), the API 5 is used to address the issue mentioned above where the prover and verifier may use an inconsistent hashing scheme so that their outputs are inconsistent.

Copy link
Contributor

@chancharles92 chancharles92 Nov 18, 2022

Choose a reason for hiding this comment

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

Actually now I prefer to remove API 5 as the work can be done in API 4: so we just modify the API 4 as follows:
verify(pp, pub_key, proof, input, output') -> (bit, output)
Since API 4 anyway computes output itself, it can add an additional equality check that output' ?= output, which is almost for free.
cc @alxiong @tessico

Copy link
Contributor Author

@tessico tessico Nov 18, 2022

Choose a reason for hiding this comment

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

I agree that we should return (bit, output) from the verify() function. I missed that, sorry.

I would argue, however, that there might be cases where output is not known beforehand and cannot be used as input to the verify routine (unless the user explicitly calls proof_to_hash first). Please correct me if that's never the case.

If it's so, we would have these alternatives:

  1. We give output' an Option<Self::Output> type, i.e. verify(pp, pub_key, proof, input, option(output')) -> (bit, output). If available, we perform the check, and otherwise just return the result.
  2. We delegate correctness checking to the user. This option is fully IRTF draft compatible.

I've got an example of alternative 2 here. The nice thing is that Rust can help with ensuring that output is used, e.g. by the #[must_use] attribute, as shown in the v2 branch. This is my preferred approach. There are numerous ways that users can screw up the protocol, I'm not sure whether we should try to prevent against such fundamental mistakes as using a wrong hash function.

Also as @alxiong suggested I've just created a new branch instead of a branch in a fork. For the sake of keeping these discussions I guess we can keep this PR as is now? There's a branch with the same name and I intend to keep them in-sync if people wanna check it out directly.

Copy link
Contributor Author

@tessico tessico Nov 18, 2022

Choose a reason for hiding this comment

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

  1. If we use alternative 2 (over alternative 1), I'm not fully sure how the user would be able to check that it's in sync with the prover.

If the prover shares only proof, and doesn't provide the output (e.g. only proof is posted on-chain, or some other way), then the verifier has no way to ensure sync anyway.

If the prover shares (proof, output'), then the verifier can call verify(proof, ...) = (bit, output) and for themselves check if output' == output.

@chancharles92

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's still have evaluate API but provide a default implementation by composing prove and proof_to_hash

So currently I just renamed evaluate -> proof_to_hash.

You saying that we add an extra evaluate(secret_key, input) function corresponding to VRF_hash in the draft as a convenience, right? Would you add that for the Vrf trait or specifically for the BLS VRF as a struct method?

Copy link
Contributor

Choose a reason for hiding this comment

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

You saying that we add an extra evaluate(secret_key, input) function corresponding to VRF_hash in the draft as a convenience, right? Would you add that for the Vrf trait or specifically for the BLS VRF as a struct method?
Yes, and add it to Vrf trait

Copy link
Contributor Author

@tessico tessico Nov 21, 2022

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a little confused, is that commit on this PR or on another branch @tessico ?

@alxiong alxiong requested a review from nyospe November 18, 2022 11:31
let message_bad = [1u8; 32];
sign_and_verify::<BLSVRFScheme, Sha256>(&message);
failed_verification::<BLSVRFScheme, Sha256>(&message, &message_bad);
let message = vec![0u8; 32];
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we also test the correctness of the hash output somewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, will add

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in b9f3c50.

Comment on lines 64 to 69
&self,
pp: &Self::PublicParameter,
proof: &Self::Proof,
public_key: &Self::PublicKey,
input: &Self::Input,
) -> Result<bool, PrimitivesError>;
Copy link
Contributor

@chancharles92 chancharles92 Nov 18, 2022

Choose a reason for hiding this comment

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

I think we can have 2 verify APIs, the new set of APIs is as follows:

  1. prove(pp, sec_key, input) -> proof
  2. proof_to_hash(pp, proof) -> output
  3. evaluate(pp, sec_key, input) -> output (i.e. proof_to_hash(pp, prove(pp, sec_key, input)))
  4. verify(pp, pub_key, proof, input) -> (bit, output) (this API also outputs hash as the hash is much cheaper than sig verification and the user doesn't need to call proof_to_hash again to obtain the hash)
  5. verify_hash_consistency(pp, proof, output) -> bit (note that this API is also cheap as it doesn't check signature)

The APIs 1..4 are exactly identical to the standard (https://datatracker.ietf.org/doc/draft-irtf-cfrg-vrf/), the API 5 is used to address the issue mentioned above where the prover and verifier may use an inconsistent hashing scheme so that their outputs are inconsistent.

Composing prove and proof_to_hash calls
- test the hash output directly
- test evaluation(sk, m) = proof_to_hash(prove(sk, m))
- generate random test vectors
@tessico
Copy link
Contributor Author

tessico commented Nov 21, 2022

I've updated the branches, and also references in the discussion threads here, such that that all links point to commits in this branch.
This branch tessico/vrf-refactor is in sync with EspressoSys/vrf-refactor branch for convenience of checking it out locally @alxiong.

Copy link
Contributor

@alxiong alxiong left a comment

Choose a reason for hiding this comment

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

LGTM! (feel free to add trait bounds of associate types on trait Vrf and trait SignatureScheme in a separate PR)

@tessico tessico merged commit 668661f into EspressoSystems:main Nov 22, 2022
@tessico tessico deleted the vrf-refactor branch November 22, 2022 17:56
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.

VRF API refactor
5 participants