Skip to content

Commit

Permalink
verifier: ProofLinking: Implement proof linking relation (#36)
Browse files Browse the repository at this point in the history
* verifier: Verifier: Accept extra opening elements on verify interface

* verifier: Verifier: Change to `VerifierCore` library

* verifier: ProofLinking: Implement proof linking relation
  • Loading branch information
joeykraut authored Feb 22, 2025
1 parent 0c56305 commit ee62c08
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 63 deletions.
2 changes: 1 addition & 1 deletion lib/solidity-bn254
Submodule solidity-bn254 updated 1 files
+14 −0 src/BN254.sol
2 changes: 0 additions & 2 deletions src/verifier/BN254Helpers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@ library BN254Helpers {

/// @dev The 2-adicity of the BN254 scalar field's modulus
uint256 constant SCALAR_FIELD_TWO_ADICITY = 28;
// forge-fmt: disable
/// @dev The 2-adic root of unity for the BN254 scalar field
BN254.ScalarField constant TWO_ADIC_ROOT = BN254.ScalarField.wrap(
19_103_219_067_921_713_944_291_392_827_692_070_036_145_651_957_329_286_315_305_642_004_821_462_161_904
);
// forge-fmt: enable

/// @dev Compute the nth root of unity for the BN254 scalar field
/// @param n The exponent, assumed to be a power of 2
Expand Down
95 changes: 95 additions & 0 deletions src/verifier/ProofLinking.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: Apache
pragma solidity ^0.8.0;

/// @title Utils for the PlonK proof linking relation defined here:
/// https://renegade-fi.notion.site/Proof-Linking-PlonK-00964f558b184e4c94b92247f4ebc5d8

import { TranscriptLib, Transcript } from "./Transcript.sol";
import { ProofLinkingArgument, OpeningElements, LinkingProof, ProofLinkingVK } from "./Types.sol";
import { BN254Helpers } from "./BN254Helpers.sol";
import { BN254 } from "solidity-bn254/BN254.sol";

library ProofLinking {
using TranscriptLib for Transcript;

/// @notice Create a proof linking argument from

/// @notice Create a set of opening elements for the proof linking relation
function createOpeningElements(ProofLinkingArgument[] memory arguments)
internal
view
returns (OpeningElements memory)
{
BN254.G1Point[] memory lhsPoints = new BN254.G1Point[](arguments.length);
BN254.G1Point[] memory rhsPoints = new BN254.G1Point[](arguments.length);
BN254.ScalarField[] memory challenges = new BN254.ScalarField[](arguments.length);

for (uint256 i = 0; i < arguments.length; i++) {
BN254.G1Point memory wireComm0 = arguments[i].wire_comm0;
BN254.G1Point memory wireComm1 = arguments[i].wire_comm1;
LinkingProof memory linkingProof = arguments[i].proof;
ProofLinkingVK memory vk = arguments[i].vk;

// Compute the opening challenge for the proof linking relation
BN254.ScalarField challenge =
computeOpeningChallenge(wireComm0, wireComm1, linkingProof.linking_quotient_poly_comm);

// Compute the evaluation of the vanishing polynomial over the linking domain
BN254.ScalarField subdomainVanishingEval = BN254Helpers.ONE;
BN254.ScalarField subdomainElement = BN254.ScalarField.wrap(
BN254.powSmall(BN254.ScalarField.unwrap(vk.link_group_generator), vk.link_group_offset, BN254.R_MOD)
);

// Compute the evaluation of the subdomain vanishing polynomial at the challenge:
// (x - w_0) * (x - w_1) * ... * (x - w_{n-1})
for (uint256 j = 0; j < vk.link_group_size; j++) {
subdomainVanishingEval = BN254.mul(subdomainVanishingEval, BN254.sub(challenge, subdomainElement));
subdomainElement = BN254.mul(subdomainElement, vk.link_group_generator);
}

// Compute a commitment to the polynomial a_1(x) - a_2(x) - Z(challenge) * linkingQuotientPolyComm
// This polynomial should be zero at the challenge point, so we evaluate an opening at the challenge
BN254.G1Point memory wireDiffComm = BN254.sub(wireComm0, wireComm1);
BN254.ScalarField vanishingCoeff = BN254.negate(subdomainVanishingEval);
BN254.G1Point memory linkingPolyComm =
BN254.add(wireDiffComm, BN254.scalarMul(linkingProof.linking_quotient_poly_comm, vanishingCoeff));

// Instead of checking the traditional form of KZG opening, e.g (for opening proof pi):
// e(\pi, [x-challenge]_2) ?= e(comm - eval, [1]_2)
// We manipulate the relation to fit into the existing PlonK opening check:
// e(\pi, [x]_2) ?= e(comm - eval - \pi * challenge, [1]_2)
// The eval should be zero at the challenge point, so this simplifies to:
// e(\pi, [x]_2) ?= e(comm - \pi * challenge, [1]_2)
// Finally, the calling interface expects the right hand side to be negated, so we return:
// \pi * challenge - comm
// for the RHS point
BN254.G1Point memory lhs = linkingProof.linking_poly_opening;
BN254.G1Point memory rhs =
BN254.sub(BN254.scalarMul(linkingProof.linking_poly_opening, challenge), linkingPolyComm);

lhsPoints[i] = lhs;
rhsPoints[i] = rhs;
challenges[i] = challenge;
}

return OpeningElements({ lhsTerms: lhsPoints, rhsTerms: rhsPoints, lastChallenges: challenges });
}

/// @dev Compute the proof link opening challenge
function computeOpeningChallenge(
BN254.G1Point memory wireComm0,
BN254.G1Point memory wireComm1,
BN254.G1Point memory linkingQuotientPolyComm
)
internal
pure
returns (BN254.ScalarField)
{
Transcript memory transcript = TranscriptLib.newTranscript();
transcript.appendPoint(wireComm0);
transcript.appendPoint(wireComm1);
transcript.appendPoint(linkingQuotientPolyComm);

return transcript.getChallenge();
}
}
2 changes: 1 addition & 1 deletion src/verifier/Transcript.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ struct Transcript {
/// @title The Fiat-Shamir transcript used by the verifier
library TranscriptLib {
/// @dev Creates a new transcript in memory
function new_transcript() internal pure returns (Transcript memory t) {
function newTranscript() internal pure returns (Transcript memory t) {
t.hashStateLow = 0;
t.hashStateHigh = 0;
t.elements = new bytes(0);
Expand Down
52 changes: 51 additions & 1 deletion src/verifier/Types.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import {BN254} from "solidity-bn254/BN254.sol";
import { BN254 } from "solidity-bn254/BN254.sol";

/// @dev The number of wire types in the arithmetization
uint256 constant NUM_WIRE_TYPES = 5;
Expand Down Expand Up @@ -29,6 +29,14 @@ struct PlonkProof {
BN254.ScalarField z_bar;
}

/// @title A proof of a group of linked inputs between two Plonk proofs
struct LinkingProof {
/// @dev The commitment to the linking quotient polynomial
BN254.G1Point linking_quotient_poly_comm;
/// @dev The opening proof of the linking polynomial
BN254.G1Point linking_poly_opening;
}

/// @title A Plonk verification key
struct VerificationKey {
/// The number of gates in the circuit
Expand All @@ -49,6 +57,16 @@ struct VerificationKey {
BN254.G2Point x_h;
}

/// @title A verification key for the proof linking relation
struct ProofLinkingVK {
/// @dev The generator of the subdomain over which the linked inputs are defined
BN254.ScalarField link_group_generator;
/// @dev The offset into the domain at which the subdomain begins
uint256 link_group_offset;
/// @dev The number of linked inputs, equivalently the size of the subdomain
uint256 link_group_size;
}

/// @title The public coin challenges used throughout the Plonk protocol
/// @notice These challenges are obtained via a Fiat-Shamir transformation
struct Challenges {
Expand All @@ -65,3 +83,35 @@ struct Challenges {
/// @dev The multipoint evaluation challenge, generated at the end of round 5 of the prover algorithm
BN254.ScalarField u;
}

/// @title An instance of a proof linking argument
struct ProofLinkingArgument {
/// @dev The commitment to the first proof's first wire polynomial
BN254.G1Point wire_comm0;
/// @dev The commitment to the second proof's first wire polynomial
BN254.G1Point wire_comm1;
/// @dev The linking proof itself
LinkingProof proof;
/// @dev The proof linking relation verification key
ProofLinkingVK vk;
}

/// @title A set of opening elements used in the final pairing product check
struct OpeningElements {
/// @dev The set of left hand side G1 elements
BN254.G1Point[] lhsTerms;
/// @dev The set of right hand side G1 elements
BN254.G1Point[] rhsTerms;
/// @dev The last challenge squeezed from each transcript
BN254.ScalarField[] lastChallenges;
}

/// @notice Create empty opening elements
/// @return An OpeningElements struct with empty arrays
function emptyOpeningElements() pure returns (OpeningElements memory) {
return OpeningElements({
lhsTerms: new BN254.G1Point[](0),
rhsTerms: new BN254.G1Point[](0),
lastChallenges: new BN254.ScalarField[](0)
});
}
72 changes: 49 additions & 23 deletions src/verifier/Verifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@
pragma solidity ^0.8.0;

import { Transcript } from "./Transcript.sol";
import { PlonkProof, VerificationKey, Challenges, NUM_WIRE_TYPES, NUM_SELECTORS } from "./Types.sol";
import {
PlonkProof,
VerificationKey,
Challenges,
NUM_WIRE_TYPES,
NUM_SELECTORS,
OpeningElements,
emptyOpeningElements
} from "./Types.sol";
import { TranscriptLib } from "./Transcript.sol";
import { BN254 } from "solidity-bn254/BN254.sol";
import { BN254Helpers } from "./BN254Helpers.sol";
Expand All @@ -23,10 +31,9 @@ bytes4 constant SCALAR_FIELD_N_BITS = bytes4(uint32(254) << 24);
/// @title A verifier for Plonk proofs
/// @notice This implementation currently follows that outlined in the paper closely:
/// https://eprint.iacr.org/2019/953.pdf
contract Verifier {
library VerifierCore {
using TranscriptLib for Transcript;

/// @notice Verify a single plonk proof
/// @notice Verify a single plonk proof
/// @param proof The proof to verify
/// @param publicInputs The public inputs to the proof
Expand All @@ -50,19 +57,22 @@ contract Verifier {
VerificationKey[] memory vkArray = new VerificationKey[](1);
vkArray[0] = vk;

return batchVerify(proofArray, publicInputsArray, vkArray);
OpeningElements memory extraOpeningElements = emptyOpeningElements();
return batchVerify(proofArray, publicInputsArray, vkArray, extraOpeningElements);
}

/// @notice Verify a batch of Plonk proofs using the arithmetization defined in `mpc-jellyfish`:
/// https://github.com/renegade-fi/mpc-jellyfish
/// @param proofs The proofs to verify
/// @param publicInputs The public inputs to the proofs
/// @param vks The verification keys for the circuit
/// @param extraOpeningElements The extra opening elements to use in the batch verification
/// @return True if the proofs are valid, false otherwise
function batchVerify(
PlonkProof[] memory proofs,
BN254.ScalarField[][] memory publicInputs,
VerificationKey[] memory vks
VerificationKey[] memory vks,
OpeningElements memory extraOpeningElements
)
public
view
Expand Down Expand Up @@ -112,45 +122,61 @@ contract Verifier {
BN254.scalarMul(proof.w_zeta, challenges.zeta),
BN254.scalarMul(proof.w_zeta_omega, BN254.mul(challenges.u, BN254.mul(challenges.zeta, omega)))
);
rhsTerm = BN254.add(rhsTerm, BN254.add(batchCommitment, BN254.negate(batchEval)));
rhsTerm = BN254.add(rhsTerm, BN254.sub(batchCommitment, batchEval));

lhsTerms[i] = lhsTerm;
rhsTerms[i] = BN254.negate(rhsTerm);
lastChallenges[i] = challenges.u;
}

return verifyBatchOpening(vks[0].h, vks[0].x_h, lhsTerms, rhsTerms, lastChallenges);
OpeningElements memory openingElements =
OpeningElements({ lhsTerms: lhsTerms, rhsTerms: rhsTerms, lastChallenges: lastChallenges });
return verifyBatchOpening(vks[0].h, vks[0].x_h, openingElements, extraOpeningElements);
}

/// Verify a batch opening of proofs
/// @notice Verify a batch opening of proofs
/// @param h The base G2 point
/// @param x_h The base G2 point
/// @param proofOpeningElements The opening elements for the proofs
/// @param extraOpeningElements The extra opening elements to use in the batch verification
/// @return True if the batch opening is valid, false otherwise
function verifyBatchOpening(
BN254.G2Point memory h,
BN254.G2Point memory x_h,
BN254.G1Point[] memory lhsG1Terms,
BN254.G1Point[] memory rhsG1Terms,
BN254.ScalarField[] memory lastChallenges
OpeningElements memory proofOpeningElements,
OpeningElements memory extraOpeningElements
)
public
view
returns (bool)
{
uint256 numProofs = lhsG1Terms.length;
uint256 numProofs = proofOpeningElements.lhsTerms.length + extraOpeningElements.lhsTerms.length;

// Sample a random scalar to parameterize the random linear combination
// If only one proof is supplied, no randomization is needed
BN254.ScalarField r = BN254Helpers.ONE;
if (numProofs > 1) {
Transcript memory transcript = TranscriptLib.new_transcript();
transcript.appendScalars(lastChallenges);
Transcript memory transcript = TranscriptLib.newTranscript();
transcript.appendScalars(proofOpeningElements.lastChallenges);
transcript.appendScalars(extraOpeningElements.lastChallenges);
r = transcript.getChallenge();
}

BN254.ScalarField rCurr = r;
BN254.G1Point memory lhsTerm = lhsG1Terms[0];
BN254.G1Point memory rhsTerm = rhsG1Terms[0];
for (uint256 i = 1; i < numProofs; i++) {
lhsTerm = BN254.add(lhsTerm, BN254.scalarMul(lhsG1Terms[i], rCurr));
rhsTerm = BN254.add(rhsTerm, BN254.scalarMul(rhsG1Terms[i], rCurr));
BN254.G1Point memory lhsTerm = proofOpeningElements.lhsTerms[0];
BN254.G1Point memory rhsTerm = proofOpeningElements.rhsTerms[0];

// Add the proof opening elements
for (uint256 i = 1; i < proofOpeningElements.lhsTerms.length; i++) {
lhsTerm = BN254.add(lhsTerm, BN254.scalarMul(proofOpeningElements.lhsTerms[i], rCurr));
rhsTerm = BN254.add(rhsTerm, BN254.scalarMul(proofOpeningElements.rhsTerms[i], rCurr));
rCurr = BN254.mul(rCurr, r);
}

// Add the extra opening elements
for (uint256 i = 0; i < extraOpeningElements.lhsTerms.length; i++) {
lhsTerm = BN254.add(lhsTerm, BN254.scalarMul(extraOpeningElements.lhsTerms[i], rCurr));
rhsTerm = BN254.add(rhsTerm, BN254.scalarMul(extraOpeningElements.rhsTerms[i], rCurr));
rCurr = BN254.mul(rCurr, r);
}

Expand Down Expand Up @@ -221,7 +247,7 @@ contract Verifier {

for (uint256 i = 0; i < proofs.length; i++) {
// Create a new transcript
Transcript memory transcript = TranscriptLib.new_transcript();
Transcript memory transcript = TranscriptLib.newTranscript();

// Append the verification key metadata and public inputs
bytes memory nBitsBytes = abi.encodePacked(SCALAR_FIELD_N_BITS);
Expand Down Expand Up @@ -319,7 +345,7 @@ contract Verifier {
BN254.ScalarField currOmegaPow = BN254Helpers.ONE;
for (uint256 i = 0; i < publicInputs.length; i++) {
BN254.ScalarField lagrangeNum = BN254.mul(vanishingDivN, currOmegaPow);
BN254.ScalarField lagrangeDenom = BN254.add(zeta, BN254.negate(currOmegaPow));
BN254.ScalarField lagrangeDenom = BN254.sub(zeta, currOmegaPow);
BN254.ScalarField lagrangeEval = BN254.mul(lagrangeNum, BN254.invert(lagrangeDenom));
currOmegaPow = BN254.mul(currOmegaPow, omega);

Expand Down Expand Up @@ -351,7 +377,7 @@ contract Verifier {

// Term 2: -L_1(\zeta) * \alpha^2
BN254.ScalarField term2 = BN254.mul(lagrange1Eval, BN254.mul(alpha, alpha));
res = BN254.add(res, BN254.negate(term2));
res = BN254.sub(res, term2);

// Add the terms from the permutation argument
BN254.ScalarField term3 = BN254.mul(alpha, zEval);
Expand All @@ -368,7 +394,7 @@ contract Verifier {
// Add in the final term without the sigma eval
BN254.ScalarField lastPermTerm = BN254.add(wireEvals[wireEvals.length - 1], gamma);
term3 = BN254.mul(term3, lastPermTerm);
res = BN254.add(res, BN254.negate(term3));
res = BN254.sub(res, term3);

return res;
}
Expand Down
8 changes: 4 additions & 4 deletions test/Transcript.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ contract TranscriptTest is TestUtils {
uint256 TEST_DATA_BYTES = 1024;

// Create a new transcript
Transcript memory transcript = TranscriptLib.new_transcript();
Transcript memory transcript = TranscriptLib.newTranscript();

// Generate random test data
bytes memory testData = vm.randomBytes(TEST_DATA_BYTES);
Expand Down Expand Up @@ -47,7 +47,7 @@ contract TranscriptTest is TestUtils {
uint256 NUM_TEST_INPUTS = 5;

// Create a new transcript
Transcript memory transcript = TranscriptLib.new_transcript();
Transcript memory transcript = TranscriptLib.newTranscript();

// Generate multiple random test inputs
bytes[] memory testInputs = new bytes[](NUM_TEST_INPUTS);
Expand Down Expand Up @@ -93,7 +93,7 @@ contract TranscriptTest is TestUtils {
uint256[] memory expectedChallenges = runReferenceImpl(inputs);

// Create a new transcript
Transcript memory transcript = TranscriptLib.new_transcript();
Transcript memory transcript = TranscriptLib.newTranscript();

// Append the scalars
transcript.appendScalars(scalars);
Expand Down Expand Up @@ -125,7 +125,7 @@ contract TranscriptTest is TestUtils {
uint256[] memory expectedChallenges = runReferenceImpl(inputs);

// Create a new transcript
Transcript memory transcript = TranscriptLib.new_transcript();
Transcript memory transcript = TranscriptLib.newTranscript();

// Append the points
transcript.appendPoints(points);
Expand Down
Loading

0 comments on commit ee62c08

Please sign in to comment.