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

Implement Verifiers for KZG and SNARK Proofs in Contracts #267

Merged
merged 18 commits into from
Feb 26, 2024
Merged

Conversation

sifnoc
Copy link
Member

@sifnoc sifnoc commented Feb 20, 2024

  • Verifier test code is located in contracts/test/Verifiers.ts.
  • The pre-generated proofs for testing are located at kzg_prover/bin as a JSON file.
    • The proofs are generated by these two scripts: gen_commitment.rs and gen_inclusion_proof.rs.
  • Only the SNARK verifier is generated from the gen_verifier.rs script in kzg_prover.
  • Retained ProverGWC for further benchmarks on a higher number of currencies.

Todo

  • Fix the revert error while verifying the Inclusion proof.
  • Align terminology in verifier comments with that used in other references.
  • Update README for generating verifer and proofs.

@sifnoc
Copy link
Member Author

sifnoc commented Feb 22, 2024

I removed revert message in the InclusionVerifier due to the bug in ethers: ethers-io/ethers.js#3605

@sifnoc sifnoc marked this pull request as ready for review February 22, 2024 19:59
// Memory positions for the verifying key.
// The memory location starts at 0x200 due to the maximum operation on the ec_pairing function being 0x180, marking the maximum memory location used
uint256 internal constant VK_MPTR = 0x200;
uint256 internal constant VK_DIGEST_MPTR = 0x200;
Copy link
Contributor

Choose a reason for hiding this comment

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

VK_DIGEST_MPTR is not used

Copy link
Member Author

Choose a reason for hiding this comment

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

True, I will address this by copying only the values needed in the verifier from the VerifyingKey.

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 at eb067b9


bytes memory slicedSnarkProof = new bytes(grandsumProof.length);
for (uint256 i = 0; i < grandsumProof.length; i++) {
slicedSnarkProof[i] = snarkProof[i + 64]; // Skip first 64 bytes, it's not for total balance.
Copy link
Contributor

Choose a reason for hiding this comment

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

Solidity now supports array slicing, maybe we can try to take advantage of that? https://docs.soliditylang.org/en/v0.6.0/types.html#array-slices

Copy link
Member Author

Choose a reason for hiding this comment

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

Saved 146,361 gas by applying the array slice to the submitCommitment method. Thanks!
It's updated at eb067b9

Copy link
Contributor

Choose a reason for hiding this comment

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

Wow, I didn't even expect such an improvement, sounds great!


require(grandSumVerifier.verifyProof(verifyingKey, combinedProofs, totalBalances), "Invalid grandsum proof");

// Slice the proof aa length as grandsumProof
Copy link
Contributor

Choose a reason for hiding this comment

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

The comment seems to be unrelated to the code

Copy link
Member Author

Choose a reason for hiding this comment

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

It's removed at 0230050

// Slice the proof aa length as grandsumProof
commitments[timestamp] = slicedSnarkProof;

emit LiabilitiesCommitmentSubmitted(timestamp, totalBalances, slicedSnarkProof);
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 that we can actually put the combined proof into the event as it binds together the range check and the grand sum opening proofs, and also store the combined proof in the commitments mapping.

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 agree with including both the snark proof and the grand sum proof in the event. However, I'm unsure about storing the grand sum proof in the commitment mapping. Is there a potential use for it in the future?

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed, no need for the grand sum proof in the commitments mapping :)

Copy link
Member Author

Choose a reason for hiding this comment

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

It's updated at 6db5fb7

uint256[] memory challenges,
uint256[] memory values
) public view returns (bool) {
require(values.length > 1, "Invalid values length");
Copy link
Contributor

@alxkzmn alxkzmn Feb 23, 2024

Choose a reason for hiding this comment

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

We can check the exact values length here because we know the cryptocurrencyNames length that was passed in the constructor.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah yes, It been fixed at 0230050

});

it("should not submit an invalid commitment", async () => {
it("should reject submit an invalid total balances", async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd say either "reject" or "not submit"

Copy link
Member Author

Choose a reason for hiding this comment

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

It's updated at 6db5fb7

});

it("should revert if the caller is not the owner", async () => {
it("should reject submit when grandsum proof length mismatches with total balances", async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here with reject/submit

Copy link
Member Author

Choose a reason for hiding this comment

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

It's updated at 6db5fb7

expect(await snarkVerifier.verifyProof(verifyingKey.address, commitmentCalldata.range_check_snark_proof, [1])).to.be.true;
});

it("should fail to verify snark proof without the number of instances", async () => {
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 this test is not necessary because the instance issue should be fixed by merging the upstream changes.

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay, I will remove 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.

Replaced another test case instead of this at 6db5fb7

commitmentCalldata = deploymentInfo.commitmentCalldata;

// InclusionVerifier requires BN256G2 contract for performing elliptic curve operations on G2 subgroup
// const bn256g2 = await deployBN256G2();
Copy link
Contributor

@alxkzmn alxkzmn Feb 23, 2024

Choose a reason for hiding this comment

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

BN256G2 mention can be removed

Copy link
Member Author

Choose a reason for hiding this comment

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

It removed at 6db5fb7

let mut grand_sums_kzg_proof = Vec::new();
for balance_column in 1..(N_CURRENCIES + 1) {
let f_poly = advice_polys.advice_polys.get(balance_column).unwrap();
let kzg_commitment = commit_kzg(&params, f_poly);
Copy link
Contributor

@alxkzmn alxkzmn Feb 23, 2024

Choose a reason for hiding this comment

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

This can be replaced with the committed point taken from the zk-proof transcript.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, Indeed! Thanks for point out!

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Great tip! It was much helped
It updated at 6db5fb7

let mut inclusion_proof: Vec<Vec<u8>> = Vec::new();
for column_index in column_range {
let f_poly = advice_polys.advice_polys.get(column_index).unwrap();
let kzg_commitment = commit_kzg(&params, f_poly);
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, the commitment can be taken from the zk-proof transcript.

Copy link
Member Author

Choose a reason for hiding this comment

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

It updated at 6db5fb7

struct InclusionProofCallData {
proof: String,
challenges: Vec<U256>,
username: String,
Copy link
Contributor

@alxkzmn alxkzmn Feb 23, 2024

Choose a reason for hiding this comment

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

Let's rename "username" to "userId" because it's not a plain-text username but is supposed to be a hash.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, that makes sense. Okay, I will replace 'username' with 'userId'

Copy link
Member Author

Choose a reason for hiding this comment

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

It updated at 6db5fb7

@@ -73,6 +72,10 @@ fn main() {
);
assert!(result.is_ok());

let result_unwrapped = result.unwrap();
result_unwrapped.0.expect("prover should not fail");
let _advice_polys = result_unwrapped.1.clone();
Copy link
Contributor

Choose a reason for hiding this comment

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

_advice_polys is not used

Copy link
Member Author

Choose a reason for hiding this comment

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

It removed at 6db5fb7

Copy link
Contributor

@alxkzmn alxkzmn left a comment

Choose a reason for hiding this comment

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

Awesome job! Let's follow the consistent naming pattern for grand sum-related things (separate "grand sum" in text and proper snake/camel case in code, e.g., "grand_sum" and "grandSum", including the file names).

@sifnoc
Copy link
Member Author

sifnoc commented Feb 23, 2024

Awesome job! Let's follow the consistent naming pattern for grand sum-related things (separate "grand sum" in text and proper snake/camel case in code, e.g., "grand_sum" and "grandSum", including the file names).

Thanks, That's great naming idea

@sifnoc sifnoc requested a review from alxkzmn February 23, 2024 17:09
it("should fail to verify snark proof without the number of instances", async () => {
await expect(snarkVerifier.verifyProof(verifyingKey.address, commitmentCalldata.range_check_snark_proof, [])).to.be.reverted;
it("should revert grand sum proof", async () => {
await expect(snarkVerifier.verifyProof(verifyingKey.address, commitmentCalldata.grand_sums_batch_proof, [1])).to.be.reverted;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why should it revert 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.

Because the verifier is for verifying snark proof, not for verifying grand sum proof.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would rename the test to something like "should revert with invalid proof".

inclusionProof = inclusionCalldata.proof;
userId = inclusionCalldata.user_id;
challenges = inclusionCalldata.challenges;
userIdBigUint = inclusionCalldata.user_values[0];
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we still need the userId if we have it in inclusionCalldata.user_values[0]?

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually 'userId' is not used anywhere in the test code. I will remove it on the test code.
Would it be better to remove it from the csv file as well?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we can remove it from the csv, it is used in some tests (e.g., test_valid_univariate_grand_sum_full_prover) and should be used in production as well.

Copy link
Contributor

@alxkzmn alxkzmn left a comment

Choose a reason for hiding this comment

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

LGTM! Can merge after resolving the conflicts.

@sifnoc sifnoc merged commit f411d4d into v2 Feb 26, 2024
6 checks passed
@sifnoc sifnoc deleted the v2-kzg-verifier branch February 26, 2024 07:22
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.

2 participants