diff --git a/backend/plonk/bls12-381/solidity.go b/backend/plonk/bls12-381/solidity.go new file mode 100644 index 0000000000..96d7b11739 --- /dev/null +++ b/backend/plonk/bls12-381/solidity.go @@ -0,0 +1,1340 @@ +package plonk + +const tmplSolidityVerifier = `// SPDX-License-Identifier: Apache-2.0 + +// Copyright 2023 Consensys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by gnark DO NOT EDIT + +pragma solidity {{ .Cfg.PragmaVersion }}; + +contract PlonkVerifier { + + uint256 private constant R_MOD = 52435875175126190479447740508185965837690552500527637822603658699938581184513; + uint256 private constant R_MOD_MINUS_ONE = 52435875175126190479447740508185965837690552500527637822603658699938581184512; + {{ range $index, $element := .Vk.Kzg.G2 }} + uint256 private constant G2_SRS_{{ $index }}_X_0_lo = {{ (fpstr_lo $element.X.A0) }}; + uint256 private constant G2_SRS_{{ $index }}_X_0_hi = {{ (fpstr_hi $element.X.A0) }}; + uint256 private constant G2_SRS_{{ $index }}_X_1_lo = {{ (fpstr_lo $element.X.A1) }}; + uint256 private constant G2_SRS_{{ $index }}_X_1_hi = {{ (fpstr_hi $element.X.A1) }}; + uint256 private constant G2_SRS_{{ $index }}_Y_0_lo = {{ (fpstr_lo $element.Y.A0) }}; + uint256 private constant G2_SRS_{{ $index }}_Y_0_hi = {{ (fpstr_hi $element.Y.A0) }}; + uint256 private constant G2_SRS_{{ $index }}_Y_1_lo = {{ (fpstr_lo $element.Y.A1) }}; + uint256 private constant G2_SRS_{{ $index }}_Y_1_hi = {{ (fpstr_hi $element.Y.A1) }}; + {{ end }} + uint256 private constant G1_SRS_X_lo = {{ fpstr_lo .Vk.Kzg.G1.X }}; + uint256 private constant G1_SRS_X_hi = {{ fpstr_hi .Vk.Kzg.G1.X }}; + uint256 private constant G1_SRS_Y_lo = {{ fpstr_lo .Vk.Kzg.G1.Y }}; + uint256 private constant G1_SRS_Y_hi = {{ fpstr_hi .Vk.Kzg.G1.Y }}; + + // ----------------------- vk --------------------- + uint256 private constant VK_NB_PUBLIC_INPUTS = {{ .Vk.NbPublicVariables }}; + uint256 private constant VK_DOMAIN_SIZE = {{ .Vk.Size }}; + uint256 private constant VK_INV_DOMAIN_SIZE = {{ (frstr .Vk.SizeInv) }}; + uint256 private constant VK_OMEGA = {{ (frstr .Vk.Generator) }}; + uint256 private constant VK_QL_COM_X_lo = {{ (fpstr_lo .Vk.Ql.X) }}; + uint256 private constant VK_QL_COM_X_hi = {{ (fpstr_hi .Vk.Ql.X) }}; + uint256 private constant VK_QL_COM_Y_lo = {{ (fpstr_lo .Vk.Ql.Y) }}; + uint256 private constant VK_QL_COM_Y_hi = {{ (fpstr_hi .Vk.Ql.Y) }}; + uint256 private constant VK_QR_COM_X_lo = {{ (fpstr_lo .Vk.Qr.X) }}; + uint256 private constant VK_QR_COM_X_hi = {{ (fpstr_hi .Vk.Qr.X) }}; + uint256 private constant VK_QR_COM_Y_lo = {{ (fpstr_lo .Vk.Qr.Y) }}; + uint256 private constant VK_QR_COM_Y_hi = {{ (fpstr_hi .Vk.Qr.Y) }}; + uint256 private constant VK_QM_COM_X_lo = {{ (fpstr_lo .Vk.Qm.X) }}; + uint256 private constant VK_QM_COM_X_hi = {{ (fpstr_hi .Vk.Qm.X) }}; + uint256 private constant VK_QM_COM_Y_lo = {{ (fpstr_lo .Vk.Qm.Y) }}; + uint256 private constant VK_QM_COM_Y_hi = {{ (fpstr_hi .Vk.Qm.Y) }}; + uint256 private constant VK_QO_COM_X_lo = {{ (fpstr_lo .Vk.Qo.X) }}; + uint256 private constant VK_QO_COM_X_hi = {{ (fpstr_hi .Vk.Qo.X) }}; + uint256 private constant VK_QO_COM_Y_lo = {{ (fpstr_lo .Vk.Qo.Y) }}; + uint256 private constant VK_QO_COM_Y_hi = {{ (fpstr_hi .Vk.Qo.Y) }}; + uint256 private constant VK_QK_COM_X_lo = {{ (fpstr_lo .Vk.Qk.X) }}; + uint256 private constant VK_QK_COM_X_hi = {{ (fpstr_hi .Vk.Qk.X) }}; + uint256 private constant VK_QK_COM_Y_lo = {{ (fpstr_lo .Vk.Qk.Y) }}; + uint256 private constant VK_QK_COM_Y_hi = {{ (fpstr_hi .Vk.Qk.Y) }}; + {{ range $index, $element := .Vk.S }} + uint256 private constant VK_S{{ inc $index }}_COM_X_lo = {{ (fpstr_lo $element.X) }}; + uint256 private constant VK_S{{ inc $index }}_COM_X_hi = {{ (fpstr_hi $element.X) }}; + uint256 private constant VK_S{{ inc $index }}_COM_Y_lo = {{ (fpstr_lo $element.Y) }}; + uint256 private constant VK_S{{ inc $index }}_COM_Y_hi = {{ (fpstr_hi $element.Y) }}; + {{ end }} + uint256 private constant VK_COSET_SHIFT = {{ frstr .Vk.CosetShift }}; + + {{ range $index, $element := .Vk.Qcp}} + uint256 private constant VK_QCP_{{ $index }}_X_lo = {{ (fpstr_lo $element.X) }}; + uint256 private constant VK_QCP_{{ $index }}_X_hi = {{ (fpstr_hi $element.X) }}; + uint256 private constant VK_QCP_{{ $index }}_Y_lo = {{ (fpstr_lo $element.Y) }}; + uint256 private constant VK_QCP_{{ $index }}_Y_hi = {{ (fpstr_hi $element.Y) }}; + {{ end }} + + {{ range $index, $element := .Vk.CommitmentConstraintIndexes -}} + uint256 private constant VK_INDEX_COMMIT_API_{{ $index }} = {{ $element }}; + {{ end -}} + uint256 private constant VK_NB_CUSTOM_GATES = {{ len .Vk.CommitmentConstraintIndexes }}; + + // ------------------------------------------------ + + // size of the proof without call custom gate + uint256 private constant FIXED_PROOF_SIZE = 0x420; + + // offset proof + {{ $offset := 0 }} + uint256 private constant PROOF_L_COM_X = {{ hex $offset }};{{ $offset = add $offset 0x30}} + uint256 private constant PROOF_L_COM_Y = {{ hex $offset }};{{ $offset = add $offset 0x30}} + uint256 private constant PROOF_R_COM_X = {{ hex $offset }};{{ $offset = add $offset 0x30}} + uint256 private constant PROOF_R_COM_Y = {{ hex $offset }};{{ $offset = add $offset 0x30}} + uint256 private constant PROOF_O_COM_X = {{ hex $offset }};{{ $offset = add $offset 0x30}} + uint256 private constant PROOF_O_COM_Y = {{ hex $offset }};{{ $offset = add $offset 0x30}} + + // h = h_0 + x^{n+2}h_1 + x^{2(n+2)}h_2 + uint256 private constant PROOF_H_0_COM_X = {{ hex $offset }};{{ $offset = add $offset 0x30}} + uint256 private constant PROOF_H_0_COM_Y = {{ hex $offset }};{{ $offset = add $offset 0x30}} + uint256 private constant PROOF_H_1_COM_X = {{ hex $offset }};{{ $offset = add $offset 0x30}} + uint256 private constant PROOF_H_1_COM_Y = {{ hex $offset }};{{ $offset = add $offset 0x30}} + uint256 private constant PROOF_H_2_COM_X = {{ hex $offset }};{{ $offset = add $offset 0x30}} + uint256 private constant PROOF_H_2_COM_Y = {{ hex $offset }};{{ $offset = add $offset 0x30}} + + // "evaluations of wire polynomials at zeta + uint256 private constant PROOF_L_AT_ZETA = {{ hex $offset }};{{ $offset = add $offset 0x20}} + uint256 private constant PROOF_R_AT_ZETA = {{ hex $offset }};{{ $offset = add $offset 0x20}} + uint256 private constant PROOF_O_AT_ZETA = {{ hex $offset }};{{ $offset = add $offset 0x20}} + + // S1(zeta),S2(zeta) + uint256 private constant PROOF_S1_AT_ZETA = {{ hex $offset }};{{ $offset = add $offset 0x20}} // Sσ1(zeta) + uint256 private constant PROOF_S2_AT_ZETA = {{ hex $offset }};{{ $offset = add $offset 0x20}} // Sσ2(zeta) + + // [Z] + uint256 private constant PROOF_GRAND_PRODUCT_COMMITMENT_X = {{ hex $offset }};{{ $offset = add $offset 0x30}} + uint256 private constant PROOF_GRAND_PRODUCT_COMMITMENT_Y = {{ hex $offset }};{{ $offset = add $offset 0x30}} + + uint256 private constant PROOF_GRAND_PRODUCT_AT_ZETA_OMEGA = {{ hex $offset }};{{ $offset = add $offset 0x20}} // z(w*zeta) + + // Folded proof for the opening of linearised poly, l, r, o, s_1, s_2, qcp + uint256 private constant PROOF_BATCH_OPENING_AT_ZETA_X = {{ hex $offset }};{{ $offset = add $offset 0x30}} + uint256 private constant PROOF_BATCH_OPENING_AT_ZETA_Y = {{ hex $offset }};{{ $offset = add $offset 0x30}} + + uint256 private constant PROOF_OPENING_AT_ZETA_OMEGA_X = {{ hex $offset }};{{ $offset = add $offset 0x30}} + uint256 private constant PROOF_OPENING_AT_ZETA_OMEGA_Y = {{ hex $offset }};{{ $offset = add $offset 0x30}} + + uint256 private constant PROOF_QCP_AT_ZETA = {{ hex $offset }}; + uint256 private constant PROOF_BSB_COMMITMENTS = {{ hex (add $offset (mul (len .Vk.CommitmentConstraintIndexes) 0x20 ) )}}; + + // -------- offset state + + // challenges to check the claimed quotient + {{ $offset = 0 }} + uint256 private constant STATE_ALPHA = {{ hex $offset }};{{ $offset = add $offset 0x20}} + uint256 private constant STATE_BETA = {{ hex $offset }};{{ $offset = add $offset 0x20}} + uint256 private constant STATE_GAMMA = {{ hex $offset }};{{ $offset = add $offset 0x20}} + uint256 private constant STATE_ZETA = {{ hex $offset }};{{ $offset = add $offset 0x20}} + uint256 private constant STATE_ALPHA_SQUARE_LAGRANGE_0 = {{ hex $offset }};{{ $offset = add $offset 0x20}} + uint256 private constant STATE_FOLDED_H = {{ hex $offset }}; // each coord is on 0x40 bytes, the top 0x10 bytes equal to 0{{ $offset = add $offset 0x80}} + uint256 private constant STATE_LINEARISED_POLYNOMIAL = {{ hex $offset }};{{ $offset = add $offset 0x80}} + uint256 private constant STATE_OPENING_LINEARISED_POLYNOMIAL_ZETA = {{ hex $offset }};{{ $offset = add $offset 0x20}} + uint256 private constant STATE_FOLDED_CLAIMED_VALUES = {{ hex $offset }};{{ $offset = add $offset 0x20}} // Folded proof for the opening of H, linearised poly, l, r, o, s_1, s_2, qcp + uint256 private constant STATE_FOLDED_DIGESTS = {{ hex $offset }};{{ $offset = add $offset 0x80}} // linearised poly, l, r, o, s_1, s_2, qcp + uint256 private constant STATE_PI = {{ hex $offset }};{{ $offset = add $offset 0x20}} + uint256 private constant STATE_ZETA_POWER_N_MINUS_ONE = {{ hex $offset }};{{ $offset = add $offset 0x20}} + uint256 private constant STATE_ZETA_POWER_N_PLUS_TWO = {{ hex $offset }};{{ $offset = add $offset 0x20}} + uint256 private constant STATE_ZETA_POWER_N_PLUS_TWO_SQUARE = {{ hex $offset }};{{ $offset = add $offset 0x20}} + uint256 private constant STATE_GAMMA_KZG = {{ hex $offset }};{{ $offset = add $offset 0x20}} + uint256 private constant STATE_SUCCESS = {{ hex $offset }};{{ $offset = add $offset 0x20}} + uint256 private constant STATE_CHECK_VAR = {{ hex $offset }};{{ $offset = add $offset 0x20}} // /!\ this slot is used for debugging only + uint256 private constant STATE_LAST_MEM = {{ hex $offset }};{{ $offset = add $offset 0x20}} + + // -------- utils (for Fiat Shamir) + uint256 private constant FS_ALPHA = 0x616C706861; // "alpha" + uint256 private constant FS_BETA = 0x62657461; // "beta" + uint256 private constant FS_GAMMA = 0x67616d6d61; // "gamma" + uint256 private constant FS_ZETA = 0x7a657461; // "zeta" + uint256 private constant FS_GAMMA_KZG = 0x67616d6d61; // "gamma" + + // -------- errors + uint256 private constant ERROR_STRING_ID = 0x08c379a000000000000000000000000000000000000000000000000000000000; // selector for function Error(string) + + {{ if (gt (len .Vk.CommitmentConstraintIndexes) 0 )}} + // -------- utils (for hash_fr) + uint256 private constant HASH_FR_BB = 340282366920938463463374607431768211456; // 2**128 + uint256 private constant HASH_FR_ZERO_UINT256 = 0; + uint8 private constant HASH_FR_LEN_IN_BYTES = 48; + uint8 private constant HASH_FR_SIZE_DOMAIN = 11; + uint8 private constant HASH_FR_ONE = 1; + uint8 private constant HASH_FR_TWO = 2; + {{ end }} + + // -------- precompiles + uint8 private constant SHA2 = 0x2; + uint8 private constant MOD_EXP = 0x5; + uint8 private constant EC_ADD = 0x6; + uint8 private constant BLS12_MSM_G1 = 0x0c; + uint8 private constant BLS12_PAIR = 0x0f; + + /// Verify a Plonk proof. + /// Reverts if the proof or the public inputs are malformed. + /// @param proof serialised plonk proof (using gnark's MarshalSolidity) + /// @param public_inputs (must be reduced) + /// @return success true if the proof passes false otherwise + function Verify(bytes calldata proof, uint256[] calldata public_inputs) + public view returns(bool success) { + + assembly { + + let mem := mload(0x40) + let freeMem := add(mem, STATE_LAST_MEM) + + // sanity checks + check_number_of_public_inputs(public_inputs.length) + check_inputs_size(public_inputs.length, public_inputs.offset) + check_proof_size(proof.length) + check_proof_openings_size(proof.offset) + + // // compute the challenges + let prev_challenge_non_reduced + prev_challenge_non_reduced := derive_gamma(proof.offset, public_inputs.length, public_inputs.offset) + prev_challenge_non_reduced := derive_beta(prev_challenge_non_reduced) + prev_challenge_non_reduced := derive_alpha(proof.offset, prev_challenge_non_reduced) + derive_zeta(proof.offset, prev_challenge_non_reduced) + + // evaluation of Z=Xⁿ-1 at ζ, we save this value + let zeta := mload(add(mem, STATE_ZETA)) + let zeta_power_n_minus_one := addmod(pow(zeta, VK_DOMAIN_SIZE, freeMem), sub(R_MOD, 1), R_MOD) + mstore(add(mem, STATE_ZETA_POWER_N_MINUS_ONE), zeta_power_n_minus_one) + + // public inputs contribution + let l_pi := sum_pi_wo_api_commit(public_inputs.offset, public_inputs.length, freeMem) + {{ if (gt (len .Vk.CommitmentConstraintIndexes) 0 ) -}} + let l_pi_commit := sum_pi_commit(proof.offset, public_inputs.length, freeMem) + l_pi := addmod(l_pi_commit, l_pi, R_MOD) + {{ end -}} + mstore(add(mem, STATE_PI), l_pi) + + compute_alpha_square_lagrange_0() + compute_zeta_powers_n_plus_two() + compute_commitment_linearised_polynomial(proof.offset) + compute_opening_linearised_polynomial(proof.offset) + compute_gamma_kzg(proof.offset) + fold_state(proof.offset) + batch_verify_multi_points(proof.offset) + success := mload(add(mem, STATE_SUCCESS)) + + // // Beginning errors ------------------------------------------------- + + function error_nb_public_inputs() { + let ptError := mload(0x40) + mstore(ptError, ERROR_STRING_ID) // selector for function Error(string) + mstore(add(ptError, 0x4), 0x20) + mstore(add(ptError, 0x24), 0x1d) + mstore(add(ptError, 0x44), "wrong number of public inputs") + revert(ptError, 0x64) + } + + /// Called when an exponentiation mod r fails + function error_mod_exp() { + let ptError := mload(0x40) + mstore(ptError, ERROR_STRING_ID) // selector for function Error(string) + mstore(add(ptError, 0x4), 0x20) + mstore(add(ptError, 0x24), 0xc) + mstore(add(ptError, 0x44), "error mod exp") + revert(ptError, 0x64) + } + + /// Called when an operation on Bn254 fails + /// @dev for instance when calling EcMul on a point not on Bn254. + function error_ec_op() { + let ptError := mload(0x40) + mstore(ptError, ERROR_STRING_ID) // selector for function Error(string) + mstore(add(ptError, 0x4), 0x20) + mstore(add(ptError, 0x24), 0x12) + mstore(add(ptError, 0x44), "error ec operation") + revert(ptError, 0x64) + } + + /// Called when one of the public inputs is not reduced. + function error_inputs_size() { + let ptError := mload(0x40) + mstore(ptError, ERROR_STRING_ID) // selector for function Error(string) + mstore(add(ptError, 0x4), 0x20) + mstore(add(ptError, 0x24), 0x18) + mstore(add(ptError, 0x44), "inputs are bigger than r") + revert(ptError, 0x64) + } + + /// Called when the size proof is not as expected + /// @dev to avoid overflow attack for instance + function error_proof_size() { + let ptError := mload(0x40) + mstore(ptError, ERROR_STRING_ID) // selector for function Error(string) + mstore(add(ptError, 0x4), 0x20) + mstore(add(ptError, 0x24), 0x10) + mstore(add(ptError, 0x44), "wrong proof size") + revert(ptError, 0x64) + } + + /// Called when one the openings is bigger than r + /// The openings are the claimed evalutions of a polynomial + /// in a Kzg proof. + function error_proof_openings_size() { + let ptError := mload(0x40) + mstore(ptError, ERROR_STRING_ID) // selector for function Error(string) + mstore(add(ptError, 0x4), 0x20) + mstore(add(ptError, 0x24), 0x16) + mstore(add(ptError, 0x44), "openings bigger than r") + revert(ptError, 0x64) + } + + function error_pairing() { + let ptError := mload(0x40) + mstore(ptError, ERROR_STRING_ID) // selector for function Error(string) + mstore(add(ptError, 0x4), 0x20) + mstore(add(ptError, 0x24), 0xd) + mstore(add(ptError, 0x44), "error pairing") + revert(ptError, 0x64) + } + + function error_verify() { + let ptError := mload(0x40) + mstore(ptError, ERROR_STRING_ID) // selector for function Error(string) + mstore(add(ptError, 0x4), 0x20) + mstore(add(ptError, 0x24), 0xc) + mstore(add(ptError, 0x44), "error verify") + revert(ptError, 0x64) + } + + function error_random_generation() { + let ptError := mload(0x40) + mstore(ptError, ERROR_STRING_ID) // selector for function Error(string) + mstore(add(ptError, 0x4), 0x20) + mstore(add(ptError, 0x24), 0x14) + mstore(add(ptError, 0x44), "error random gen kzg") + revert(ptError, 0x64) + } + // end errors ------------------------------------------------- + + // Beginning checks ------------------------------------------------- + + /// @param s actual number of public inputs + function check_number_of_public_inputs(s) { + if iszero(eq(s, VK_NB_PUBLIC_INPUTS)) { + error_nb_public_inputs() + } + } + + /// Checks that the public inputs are < R_MOD. + /// @param s number of public inputs + /// @param p pointer to the public inputs array + function check_inputs_size(s, p) { + for {let i} lt(i, s) {i:=add(i,1)} + { + if gt(calldataload(p), R_MOD_MINUS_ONE) { + error_inputs_size() + } + p := add(p, 0x20) + } + } + + /// Checks if the proof is of the correct size + /// @param actual_proof_size size of the proof (not the expected size) + function check_proof_size(actual_proof_size) { + let expected_proof_size := add(FIXED_PROOF_SIZE, mul(VK_NB_CUSTOM_GATES,0x80)) + if iszero(eq(actual_proof_size, expected_proof_size)) { + error_proof_size() + } + } + + /// Checks if the multiple openings of the polynomials are < R_MOD. + /// @param aproof pointer to the beginning of the proof + /// @dev the 'a' prepending proof is to have a local name + function check_proof_openings_size(aproof) { + + // PROOF_L_AT_ZETA + let p := add(aproof, PROOF_L_AT_ZETA) + if gt(calldataload(p), R_MOD_MINUS_ONE) { + error_proof_openings_size() + } + + // PROOF_R_AT_ZETA + p := add(aproof, PROOF_R_AT_ZETA) + if gt(calldataload(p), R_MOD_MINUS_ONE) { + error_proof_openings_size() + } + + // PROOF_O_AT_ZETA + p := add(aproof, PROOF_O_AT_ZETA) + if gt(calldataload(p), R_MOD_MINUS_ONE) { + error_proof_openings_size() + } + + // PROOF_S1_AT_ZETA + p := add(aproof, PROOF_S1_AT_ZETA) + if gt(calldataload(p), R_MOD_MINUS_ONE) { + error_proof_openings_size() + } + + // PROOF_S2_AT_ZETA + p := add(aproof, PROOF_S2_AT_ZETA) + if gt(calldataload(p), R_MOD_MINUS_ONE) { + error_proof_openings_size() + } + + // PROOF_GRAND_PRODUCT_AT_ZETA_OMEGA + p := add(aproof, PROOF_GRAND_PRODUCT_AT_ZETA_OMEGA) + if gt(calldataload(p), R_MOD_MINUS_ONE) { + error_proof_openings_size() + } + + // PROOF_QCP_AT_ZETA + + p := add(aproof, PROOF_QCP_AT_ZETA) + for {let i:=0} lt(i, VK_NB_CUSTOM_GATES) {i:=add(i,1)} + { + if gt(calldataload(p), R_MOD_MINUS_ONE) { + error_proof_openings_size() + } + p := add(p, 0x20) + } + + } + // end checks ------------------------------------------------- + + // // Beginning challenges ------------------------------------------------- + + /// Derive gamma as Sha256() + /// @param aproof pointer to the proof + /// @param nb_pi number of public inputs + /// @param pi pointer to the array of public inputs + /// @return the challenge gamma, not reduced + /// @notice The transcript is the concatenation (in this order) of: + /// * the word "gamma" in ascii, equal to [0x67,0x61,0x6d, 0x6d, 0x61] and encoded as a uint256. + /// * the commitments to the permutation polynomials S1, S2, S3, where we concatenate the coordinates of those points + /// * the commitments of Ql, Qr, Qm, Qo, Qk + /// * the public inputs + /// * the commitments of the wires related to the custom gates (commitments_wires_commit_api) + /// * commitments to L, R, O (proof__com_) + /// The data described above is written starting at mPtr. "gamma" lies on 5 bytes, + /// and is encoded as a uint256 number n. In basis b = 256, the number looks like this + /// [0 0 0 .. 0x67 0x61 0x6d, 0x6d, 0x61]. The first non zero entry is at position 27=0x1b + /// Gamma reduced (the actual challenge) is stored at add(state, state_gamma) + function derive_gamma(aproof, nb_pi, pi)->gamma_not_reduced { + + let state := mload(0x40) + let mPtr := add(state, STATE_LAST_MEM) + + {{ $offset = 0x10 }} + mstore(add(mPtr, {{ hex $offset }}), VK_S1_COM_X_hi) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_S1_COM_Y_hi) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_S2_COM_X_hi) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_S2_COM_Y_hi) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_S3_COM_X_hi) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_S3_COM_Y_hi) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QL_COM_X_hi) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QL_COM_Y_hi) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QR_COM_X_hi) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QR_COM_Y_hi) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QM_COM_X_hi) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QM_COM_Y_hi) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QO_COM_X_hi) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QO_COM_Y_hi) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QK_COM_X_hi) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QK_COM_Y_hi) {{ $offset = add $offset 0x30}} + {{ range $index, $element := .Vk.CommitmentConstraintIndexes}} + mstore(add(mPtr, {{ hex $offset }}), VK_QCP_{{ $index }}_X_hi) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QCP_{{ $index }}_Y_hi) {{ $offset = add $offset 0x30}} + {{ end }} + mstore(mPtr, FS_GAMMA) // "gamma" + {{ $offset = 0x30 }} + mstore(add(mPtr, {{ hex $offset }}), VK_S1_COM_X_lo) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_S1_COM_Y_lo) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_S2_COM_X_lo) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_S2_COM_Y_lo) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_S3_COM_X_lo) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_S3_COM_Y_lo) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QL_COM_X_lo) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QL_COM_Y_lo) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QR_COM_X_lo) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QR_COM_Y_lo) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QM_COM_X_lo) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QM_COM_Y_lo) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QO_COM_X_lo) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QO_COM_Y_lo) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QK_COM_X_lo) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QK_COM_Y_lo) {{ $offset = add $offset 0x30}} + {{ range $index, $element := .Vk.CommitmentConstraintIndexes}} + mstore(add(mPtr, {{ hex $offset }}), VK_QCP_{{ $index }}_X_lo) {{ $offset = add $offset 0x30}} + mstore(add(mPtr, {{ hex $offset }}), VK_QCP_{{ $index }}_Y_lo) {{ $offset = add $offset 0x30}} + {{ end }} + // public inputs + let _mPtr := add(mPtr, {{ hex ( sub $offset 0x10 ) }} ) + let size_pi_in_bytes := mul(nb_pi, 0x20) + calldatacopy(_mPtr, pi, size_pi_in_bytes) + _mPtr := add(_mPtr, size_pi_in_bytes) + + // commitments to l, r, o + let size_commitments_lro_in_bytes := {{ hex (mul 0x60 3)}} + calldatacopy(_mPtr, aproof, size_commitments_lro_in_bytes) + _mPtr := add(_mPtr, size_commitments_lro_in_bytes) + + // total size is : + // sizegamma(=0x5) + 11*96(=0x420) + // + nb_public_inputs*0x20 + // + nb_custom gates*0x60 + let size := add({{ hex ( add 5 ( mul 11 96 ))}}, size_pi_in_bytes) + {{ if (gt (len .Vk.CommitmentConstraintIndexes) 0 )}} + size := add(size, mul(VK_NB_CUSTOM_GATES, 0x60)) + {{ end -}} + let l_success := staticcall(gas(), SHA2, add(mPtr, 0x1b), size, mPtr, 0x20) //0x1b -> 000.."gamma" + if iszero(l_success) { + error_verify() + } + gamma_not_reduced := mload(mPtr) + mstore(add(state, STATE_GAMMA), mod(gamma_not_reduced, R_MOD)) + } + + /// derive beta as Sha256 + /// @param gamma_not_reduced the previous challenge (gamma) not reduced + /// @return beta_not_reduced the next challenge, beta, not reduced + /// @notice the transcript consists of the previous challenge only. + /// The reduced version of beta is stored at add(state, state_beta) + function derive_beta(gamma_not_reduced)->beta_not_reduced{ + + let state := mload(0x40) + let mPtr := add(mload(0x40), STATE_LAST_MEM) + + // beta + mstore(mPtr, FS_BETA) // "beta" + mstore(add(mPtr, 0x20), gamma_not_reduced) + let l_success := staticcall(gas(), SHA2, add(mPtr, 0x1c), 0x24, mPtr, 0x20) //0x1b -> 000.."gamma" + if iszero(l_success) { + error_verify() + } + beta_not_reduced := mload(mPtr) + mstore(add(state, STATE_BETA), mod(beta_not_reduced, R_MOD)) + } + + /// derive alpha as sha256 + /// @param aproof pointer to the proof object + /// @param beta_not_reduced the previous challenge (beta) not reduced + /// @return alpha_not_reduced the next challenge, alpha, not reduced + /// @notice the transcript consists of the previous challenge (beta) + /// not reduced, the commitments to the wires associated to the QCP_i, + /// and the commitment to the grand product polynomial + function derive_alpha(aproof, beta_not_reduced)->alpha_not_reduced { + + let state := mload(0x40) + let mPtr := add(mload(0x40), STATE_LAST_MEM) + let full_size := 0x85 // "alpha" || previous challenge || grand product commitment + + // alpha + mstore(mPtr, FS_ALPHA) // "alpha" + let _mPtr := add(mPtr, 0x20) + mstore(_mPtr, beta_not_reduced) + _mPtr := add(_mPtr, 0x20) + {{ if (gt (len .Vk.CommitmentConstraintIndexes) 0 )}} + // Bsb22Commitments + let proof_bsb_commitments := add(aproof, PROOF_BSB_COMMITMENTS) + let size_bsb_commitments := mul(0x60, VK_NB_CUSTOM_GATES) + calldatacopy(_mPtr, proof_bsb_commitments, size_bsb_commitments) + _mPtr := add(_mPtr, size_bsb_commitments) + full_size := add(full_size, size_bsb_commitments) + {{ end }} + // [Z], the commitment to the grand product polynomial + calldatacopy(_mPtr, add(aproof, PROOF_GRAND_PRODUCT_COMMITMENT_X), 0x60) + let l_success := staticcall(gas(), SHA2, add(mPtr, 0x1b), full_size, mPtr, 0x20) + if iszero(l_success) { + error_verify() + } + + alpha_not_reduced := mload(mPtr) + mstore(add(state, STATE_ALPHA), mod(alpha_not_reduced, R_MOD)) + } + + /// derive zeta as sha256 + /// @param aproof pointer to the proof object + /// @param alpha_not_reduced the previous challenge (alpha) not reduced + /// The transcript consists of the previous challenge and the commitment to + /// the quotient polynomial h. + function derive_zeta(aproof, alpha_not_reduced) { + + let state := mload(0x40) + let mPtr := add(mload(0x40), STATE_LAST_MEM) + + // zeta + mstore(mPtr, FS_ZETA) // "zeta" + mstore(add(mPtr, 0x20), alpha_not_reduced) + calldatacopy(add(mPtr, 0x40), add(aproof, PROOF_H_0_COM_X), 0x120) + let l_success := staticcall(gas(), SHA2, add(mPtr, 0x1c), 0x144, mPtr, 0x20) + if iszero(l_success) { + error_verify() + } + let zeta_not_reduced := mload(mPtr) + mstore(add(state, STATE_ZETA), mod(zeta_not_reduced, R_MOD)) + } + // END challenges ------------------------------------------------- + + // // BEGINNING compute_pi ------------------------------------------------- + + /// sum_pi_wo_api_commit computes the public inputs contributions, + /// except for the public inputs coming from the custom gate + /// @param ins pointer to the public inputs + /// @param n number of public inputs + /// @param mPtr free memory + /// @return pi_wo_commit public inputs contribution (except the public inputs coming from the custom gate) + function sum_pi_wo_api_commit(ins, n, mPtr)->pi_wo_commit { + + let state := mload(0x40) + let z := mload(add(state, STATE_ZETA)) + let zpnmo := mload(add(state, STATE_ZETA_POWER_N_MINUS_ONE)) + + let li := mPtr + batch_compute_lagranges_at_z(z, zpnmo, n, li) + + let tmp := 0 + for {let i:=0} lt(i,n) {i:=add(i,1)} + { + tmp := mulmod(mload(li), calldataload(ins), R_MOD) + pi_wo_commit := addmod(pi_wo_commit, tmp, R_MOD) + li := add(li, 0x20) + ins := add(ins, 0x20) + } + + } + + /// batch_compute_lagranges_at_z computes [L_0(z), .., L_{n-1}(z)] + /// @param z point at which the Lagranges are evaluated + /// @param zpnmo ζⁿ-1 + /// @param n_pub number of public inputs (number of Lagranges to compute) + /// @param mPtr pointer to which the results are stored + function batch_compute_lagranges_at_z(z, zpnmo, n_pub, mPtr) { + + let zn := mulmod(zpnmo, VK_INV_DOMAIN_SIZE, R_MOD) // 1/n * (ζⁿ - 1) + + let _w := 1 + let _mPtr := mPtr + for {let i:=0} lt(i,n_pub) {i:=add(i,1)} + { + mstore(_mPtr, addmod(z,sub(R_MOD, _w), R_MOD)) + _w := mulmod(_w, VK_OMEGA, R_MOD) + _mPtr := add(_mPtr, 0x20) + } + batch_invert(mPtr, n_pub, _mPtr) + _mPtr := mPtr + _w := 1 + for {let i:=0} lt(i,n_pub) {i:=add(i,1)} + { + mstore(_mPtr, mulmod(mulmod(mload(_mPtr), zn , R_MOD), _w, R_MOD)) + _mPtr := add(_mPtr, 0x20) + _w := mulmod(_w, VK_OMEGA, R_MOD) + } + } + + /// @notice Montgomery trick for batch inversion mod R_MOD + /// @param ins pointer to the data to batch invert + /// @param number of elements to batch invert + /// @param mPtr free memory + function batch_invert(ins, nb_ins, mPtr) { + mstore(mPtr, 1) + let offset := 0 + for {let i:=0} lt(i, nb_ins) {i:=add(i,1)} + { + let prev := mload(add(mPtr, offset)) + let cur := mload(add(ins, offset)) + cur := mulmod(prev, cur, R_MOD) + offset := add(offset, 0x20) + mstore(add(mPtr, offset), cur) + } + ins := add(ins, sub(offset, 0x20)) + mPtr := add(mPtr, offset) + let inv := pow(mload(mPtr), sub(R_MOD,2), add(mPtr, 0x20)) + for {let i:=0} lt(i, nb_ins) {i:=add(i,1)} + { + mPtr := sub(mPtr, 0x20) + let tmp := mload(ins) + let cur := mulmod(inv, mload(mPtr), R_MOD) + mstore(ins, cur) + inv := mulmod(inv, tmp, R_MOD) + ins := sub(ins, 0x20) + } + } + + {{ if (gt (len .Vk.CommitmentConstraintIndexes) 0 )}} + /// Public inputs (the ones coming from the custom gate) contribution + /// @param aproof pointer to the proof + /// @param nb_public_inputs number of public inputs + /// @param mPtr pointer to free memory + /// @return pi_commit custom gate public inputs contribution + function sum_pi_commit(aproof, nb_public_inputs, mPtr)->pi_commit { + + let state := mload(0x40) + let z := mload(add(state, STATE_ZETA)) + let zpnmo := mload(add(state, STATE_ZETA_POWER_N_MINUS_ONE)) + + let p := add(aproof, PROOF_BSB_COMMITMENTS) + + let h_fr, ith_lagrange + + {{ range $index, $element := .Vk.CommitmentConstraintIndexes}} + h_fr := hash_fr(p, mPtr) + ith_lagrange := compute_ith_lagrange_at_z(z, zpnmo, add(nb_public_inputs, VK_INDEX_COMMIT_API_{{ $index }}), mPtr) + pi_commit := addmod(pi_commit, mulmod(h_fr, ith_lagrange, R_MOD), R_MOD) + {{ if (lt (inc $index) (len $.Vk.CommitmentConstraintIndexes) )}} + p := add(p, 0x60) + {{- end }} + {{- end }} + + } + + /// Computes L_i(zeta) = ωⁱ/n * (ζⁿ-1)/(ζ-ωⁱ) where: + /// @param z zeta + /// @param zpmno ζⁿ-1 + /// @param i i-th lagrange + /// @param mPtr free memory + /// @return res = ωⁱ/n * (ζⁿ-1)/(ζ-ωⁱ) + function compute_ith_lagrange_at_z(z, zpnmo, i, mPtr)->res { + + let w := pow(VK_OMEGA, i, mPtr) // w**i + i := addmod(z, sub(R_MOD, w), R_MOD) // z-w**i + w := mulmod(w, VK_INV_DOMAIN_SIZE, R_MOD) // w**i/n + i := pow(i, sub(R_MOD,2), mPtr) // (z-w**i)**-1 + w := mulmod(w, i, R_MOD) // w**i/n*(z-w)**-1 + res := mulmod(w, zpnmo, R_MOD) + + } + + /// @dev https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-5.2 + /// @param p pointer to a marshalled point on BLS12-381(𝔽_p) + /// @param mPtr free memory + /// @return res an element mod R_MOD + function hash_fr(p, mPtr)->res { + + // [0x00, .. , 0x00 || x, y, || 0, 48, 0, dst, HASH_FR_SIZE_DOMAIN] + // <- 64 bytes -> <-64b -> <- 1 bytes each -> + + // [0x00, .., 0x00] 64 bytes of zero + mstore(mPtr, HASH_FR_ZERO_UINT256) + mstore(add(mPtr, 0x20), HASH_FR_ZERO_UINT256) + + // msg = p , both on 96 bytes + calldatacopy(add(mPtr, 0x40), p, 0x60) + + // 0 || 48 || 0 all on 1 byte + mstore8(add(mPtr, 0xa0), 0) + mstore8(add(mPtr, 0xa1), HASH_FR_LEN_IN_BYTES) + mstore8(add(mPtr, 0xa2), 0) + + // "BSB22-Plonk" = [42, 53, 42, 32, 32, 2d, 50, 6c, 6f, 6e, 6b,] + mstore8(add(mPtr, 0xa3), 0x42) + mstore8(add(mPtr, 0xa4), 0x53) + mstore8(add(mPtr, 0xa5), 0x42) + mstore8(add(mPtr, 0xa6), 0x32) + mstore8(add(mPtr, 0xa7), 0x32) + mstore8(add(mPtr, 0xa8), 0x2d) + mstore8(add(mPtr, 0xa9), 0x50) + mstore8(add(mPtr, 0xaa), 0x6c) + mstore8(add(mPtr, 0xab), 0x6f) + mstore8(add(mPtr, 0xac), 0x6e) + mstore8(add(mPtr, 0xad), 0x6b) + + // size domain + mstore8(add(mPtr, 0xae), HASH_FR_SIZE_DOMAIN) + + let l_success := staticcall(gas(), SHA2, mPtr, 0xaf, mPtr, 0x20) + if iszero(l_success) { + error_verify() + } + + let b0 := mload(mPtr) + + // [b0 || one || dst || HASH_FR_SIZE_DOMAIN] + // <-64bytes -> <- 1 byte each -> + mstore8(add(mPtr, 0x20), HASH_FR_ONE) // 1 + + mstore8(add(mPtr, 0x21), 0x42) // dst + mstore8(add(mPtr, 0x22), 0x53) + mstore8(add(mPtr, 0x23), 0x42) + mstore8(add(mPtr, 0x24), 0x32) + mstore8(add(mPtr, 0x25), 0x32) + mstore8(add(mPtr, 0x26), 0x2d) + mstore8(add(mPtr, 0x27), 0x50) + mstore8(add(mPtr, 0x28), 0x6c) + mstore8(add(mPtr, 0x29), 0x6f) + mstore8(add(mPtr, 0x2a), 0x6e) + mstore8(add(mPtr, 0x2b), 0x6b) + + mstore8(add(mPtr, 0x2c), HASH_FR_SIZE_DOMAIN) // size domain + l_success := staticcall(gas(), SHA2, mPtr, 0x2d, mPtr, 0x20) + if iszero(l_success) { + error_verify() + } + + // b1 is located at mPtr. We store b2 at add(mPtr, 0x20) + + // [b0^b1 || two || dst || HASH_FR_SIZE_DOMAIN] + // <-64bytes -> <- 1 byte each -> + mstore(add(mPtr, 0x20), xor(mload(mPtr), b0)) + mstore8(add(mPtr, 0x40), HASH_FR_TWO) + + mstore8(add(mPtr, 0x41), 0x42) // dst + mstore8(add(mPtr, 0x42), 0x53) + mstore8(add(mPtr, 0x43), 0x42) + mstore8(add(mPtr, 0x44), 0x32) + mstore8(add(mPtr, 0x45), 0x32) + mstore8(add(mPtr, 0x46), 0x2d) + mstore8(add(mPtr, 0x47), 0x50) + mstore8(add(mPtr, 0x48), 0x6c) + mstore8(add(mPtr, 0x49), 0x6f) + mstore8(add(mPtr, 0x4a), 0x6e) + mstore8(add(mPtr, 0x4b), 0x6b) + + mstore8(add(mPtr, 0x4c), HASH_FR_SIZE_DOMAIN) // size domain + + let offset := add(mPtr, 0x20) + l_success := staticcall(gas(), SHA2, offset, 0x2d, offset, 0x20) + if iszero(l_success) { + error_verify() + } + + // at this point we have mPtr = [ b1 || b2] where b1 is on 32byes and b2 in 16bytes. + // we interpret it as a big integer mod r in big endian (similar to regular decimal notation) + // the result is then 2**(8*16)*mPtr[:32] + mPtr[32:48] + res := mulmod(mload(mPtr), HASH_FR_BB, R_MOD) // <- res = 2**128 * mPtr[:32] + let b1 := shr(128, mload(add(mPtr, 0x20))) // b1 <- [0, 0, .., 0 || b2[:16] ] + res := addmod(res, b1, R_MOD) + + } + {{ end }} + // END compute_pi ------------------------------------------------- + + /// @notice compute α² * 1/n * (ζ{n}-1)/(ζ - 1) where + /// * α = challenge derived in derive_gamma_beta_alpha_zeta + /// * n = vk_domain_size + /// * ω = vk_omega (generator of the multiplicative cyclic group of order n in (ℤ/rℤ)*) + /// * ζ = zeta (challenge derived with Fiat Shamir) + function compute_alpha_square_lagrange_0() { + let state := mload(0x40) + let mPtr := add(mload(0x40), STATE_LAST_MEM) + + let res := mload(add(state, STATE_ZETA_POWER_N_MINUS_ONE)) + let den := addmod(mload(add(state, STATE_ZETA)), sub(R_MOD, 1), R_MOD) + den := pow(den, sub(R_MOD, 2), mPtr) + den := mulmod(den, VK_INV_DOMAIN_SIZE, R_MOD) + res := mulmod(den, res, R_MOD) + + let l_alpha := mload(add(state, STATE_ALPHA)) + res := mulmod(res, l_alpha, R_MOD) + res := mulmod(res, l_alpha, R_MOD) + mstore(add(state, STATE_ALPHA_SQUARE_LAGRANGE_0), res) + } + + /// @notice follows alg. p.13 of https://eprint.iacr.org/2019/953.pdf + /// with t₁ = t₂ = 1, and the proofs are ([digest] + [quotient] +purported evaluation): + /// * [state_folded_state_digests], [proof_batch_opening_at_zeta_x], state_folded_evals + /// * [proof_grand_product_commitment], [proof_opening_at_zeta_omega_x], [proof_grand_product_at_zeta_omega] + /// @param aproof pointer to the proof + function batch_verify_multi_points(aproof) { + + let state := mload(0x40) + let mPtr := add(state, STATE_LAST_MEM) + + // derive a random number. As there is no random generator, we + // do an FS like challenge derivation, depending on both digests and + // ζ to ensure that the prover cannot control the random number. + // Note: adding the other point ζω is not needed, as ω is known beforehand. + {{ $offset = 0 }} + mcopy(mPtr, add(state, STATE_FOLDED_DIGESTS), 0x80){{ $offset = add $offset 0x60 }} + calldatacopy(add(mPtr, {{ hex $offset }}), add(aproof, PROOF_BATCH_OPENING_AT_ZETA_X), 0x60){{ $offset = add $offset 0x60 }} + calldatacopy(add(mPtr, {{ hex $offset }}), add(aproof, PROOF_GRAND_PRODUCT_COMMITMENT_X), 0x60){{ $offset = add $offset 0x60 }} + calldatacopy(add(mPtr, {{ hex $offset }}), add(aproof, PROOF_OPENING_AT_ZETA_OMEGA_X), 0x60){{ $offset = add $offset 0x60 }} + mstore(add(mPtr, {{ hex $offset }}), mload(add(state, STATE_ZETA))){{ $offset = add $offset 0x20 }} + mstore(add(mPtr, {{ hex $offset }}), mload(add(state, STATE_GAMMA_KZG))){{ $offset = add $offset 0x20 }} + let random := staticcall(gas(), SHA2, mPtr, {{ $offset }}, mPtr, 0x20){{ $offset = add $offset 0x20 }} + if iszero(random){ + error_random_generation() + } + random := mod(mload(mPtr), R_MOD) + + let folded_evals := add(state, STATE_FOLDED_CLAIMED_VALUES) + fr_acc_mul_calldata(folded_evals, add(aproof, PROOF_GRAND_PRODUCT_AT_ZETA_OMEGA), random) + + let folded_digests := mPtr + mcopy(folded_digests, add(state, STATE_FOLDED_DIGESTS), 0x80) + mstore(add(folded_digests, 0x80), 1) + store_point_calldata(add(folded_digests, 0xa0), add(aproof, PROOF_GRAND_PRODUCT_COMMITMENT_X)) + mstore(add(folded_digests, 0x120), random) + store_point(add(folded_digests, 0x140), G1_SRS_X_hi, G1_SRS_X_lo, G1_SRS_Y_hi, G1_SRS_Y_lo) + mstore(add(folded_digests, 0x1c0), sub(R_MOD, mload(folded_evals))) + store_point_calldata(add(folded_digests, 0x1e0), add(aproof, PROOF_BATCH_OPENING_AT_ZETA_X)) + mstore(add(folded_digests, 0x260), mload(add(state, STATE_ZETA))) + store_point_calldata(add(folded_digests, 0x280), add(aproof, PROOF_OPENING_AT_ZETA_OMEGA_X)) + let zeta_omega := mulmod(mload(add(state, STATE_ZETA)), VK_OMEGA, R_MOD) + mstore(add(folded_digests, 0x300), mulmod(random, zeta_omega, R_MOD)) + let l_success := staticcall(gas(), BLS12_MSM_G1, folded_digests, 0x320, folded_digests, 0x80) + if iszero(l_success){ + error_ec_op() + } + + let folded_quotients := add(mPtr, 0x180) + store_point_calldata(folded_quotients, add(aproof, PROOF_BATCH_OPENING_AT_ZETA_X)) + mstore(add(folded_quotients, 0x80), sub(R_MOD, 1)) + store_point_calldata(add(folded_quotients, 0xa0), add(aproof, PROOF_OPENING_AT_ZETA_OMEGA_X)) + mstore(add(folded_quotients, 0x120), sub(R_MOD, random)) + l_success := staticcall(gas(), BLS12_MSM_G1, folded_quotients, 0x140, folded_quotients, 0x80) + if iszero(l_success){ + error_ec_op() + } + + store_point(add(mPtr, 0x80), G2_SRS_0_X_0_hi, G2_SRS_0_X_0_lo, G2_SRS_0_X_1_hi, G2_SRS_0_X_1_lo) + store_point(add(mPtr, 0x100), G2_SRS_0_Y_0_hi, G2_SRS_0_Y_0_lo, G2_SRS_0_Y_1_hi, G2_SRS_0_Y_1_lo) + store_point(add(mPtr, 0x200), G2_SRS_1_X_0_hi, G2_SRS_1_X_0_lo, G2_SRS_1_X_1_hi, G2_SRS_1_X_1_lo) + store_point(add(mPtr, 0x280), G2_SRS_1_Y_0_hi, G2_SRS_1_Y_0_lo, G2_SRS_1_Y_1_hi, G2_SRS_1_Y_1_lo) + l_success := staticcall(gas(), BLS12_PAIR, mPtr, 0x300, mPtr, 0x20) + if iszero(l_success) { + error_pairing() + } + mstore(add(state, STATE_SUCCESS), mload(mPtr)) + } + + /// @notice Fold the opening proofs at ζ: + /// * at state+STATE_FOLDED_DIGEST we store: [Linearised_polynomial]+γ[L] + γ²[R] + γ³[O] + γ⁴[S₁] +γ⁵[S₂] + ∑ᵢγ⁵⁺ⁱ[Pi_{i}] + /// * at state+STATE_FOLDED_CLAIMED_VALUE we store: Linearised_polynomial(ζ)+γL(ζ) + γ²R(ζ)+ γ³O(ζ) + γ⁴S₁(ζ) +γ⁵S₂(ζ) + ∑ᵢγ⁵⁺ⁱPi_{i}(ζ) + /// @param aproof pointer to the proof + /// acc_gamma stores the γⁱ + function fold_state(aproof) { + + let state := mload(0x40) + let mPtr := add(mload(0x40), STATE_LAST_MEM) + + let l_gamma_kzg := mload(add(state, STATE_GAMMA_KZG)) + let acc_gamma := l_gamma_kzg + + {{ $offset = 0x00 }} + mstore(add(state, STATE_FOLDED_CLAIMED_VALUES), mload(add(state, STATE_OPENING_LINEARISED_POLYNOMIAL_ZETA))) + mcopy(mPtr, add(state, STATE_LINEARISED_POLYNOMIAL), 0x80){{ $offset = add $offset 0x80 }} + mstore(add(mPtr, {{ hex $offset }}), 1){{ $offset = add $offset 0x20 }} + + fr_acc_mul_calldata(add(state, STATE_FOLDED_CLAIMED_VALUES), add(aproof, PROOF_L_AT_ZETA), acc_gamma) + store_point_calldata(add(mPtr, {{ hex $offset }}), add(aproof, PROOF_L_COM_X)){{ $offset = add $offset 0x80 }} + mstore(add(mPtr, {{ hex $offset }}), acc_gamma){{ $offset = add $offset 0x20 }} + + acc_gamma := mulmod(acc_gamma, l_gamma_kzg, R_MOD) + fr_acc_mul_calldata(add(state, STATE_FOLDED_CLAIMED_VALUES), add(aproof, PROOF_R_AT_ZETA), acc_gamma) + store_point_calldata(add(mPtr, {{ hex $offset }}), add(aproof, PROOF_R_COM_X)){{ $offset = add $offset 0x80 }} + mstore(add(mPtr, {{ hex $offset }}), acc_gamma){{ $offset = add $offset 0x20 }} + + acc_gamma := mulmod(acc_gamma, l_gamma_kzg, R_MOD) + fr_acc_mul_calldata(add(state, STATE_FOLDED_CLAIMED_VALUES), add(aproof, PROOF_O_AT_ZETA), acc_gamma) + store_point_calldata(add(mPtr, {{ hex $offset }}), add(aproof, PROOF_O_COM_X)){{ $offset = add $offset 0x80 }} + mstore(add(mPtr, {{ hex $offset }}), acc_gamma){{ $offset = add $offset 0x20 }} + + acc_gamma := mulmod(acc_gamma, l_gamma_kzg, R_MOD) + fr_acc_mul_calldata(add(state, STATE_FOLDED_CLAIMED_VALUES), add(aproof, PROOF_S1_AT_ZETA), acc_gamma) + store_point(add(mPtr, {{ hex $offset }}), VK_S1_COM_X_hi, VK_S1_COM_X_lo, VK_S1_COM_Y_hi, VK_S1_COM_Y_lo){{ $offset = add $offset 0x80 }} + mstore(add(mPtr, {{ hex $offset }}), acc_gamma){{ $offset = add $offset 0x20 }} + + acc_gamma := mulmod(acc_gamma, l_gamma_kzg, R_MOD) + fr_acc_mul_calldata(add(state, STATE_FOLDED_CLAIMED_VALUES), add(aproof, PROOF_S2_AT_ZETA), acc_gamma) + store_point(add(mPtr, {{ hex $offset }}), VK_S2_COM_X_hi, VK_S2_COM_X_lo, VK_S2_COM_Y_hi, VK_S2_COM_Y_lo){{ $offset = add $offset 0x80 }} + mstore(add(mPtr, {{ hex $offset }}), acc_gamma){{ $offset = add $offset 0x20 }} + + {{ if (gt (len .Vk.CommitmentConstraintIndexes ) 1 ) }} + {{- $offsetValues := 0x00 }} + {{- $offsetComs := 0x00 }} + {{- range $index, $element := .Vk.CommitmentConstraintIndexes }} + acc_gamma := mulmod(acc_gamma, l_gamma_kzg, R_MOD) + fr_acc_mul_calldata(add(state, STATE_FOLDED_CLAIMED_VALUES), add(aproof, add(PROOF_QCP_AT_ZETA, {{ hex $offsetValues }})), acc_gamma){{ $offsetValues = add $offsetValues 0x20 }} + store_point(add(mPtr, {{ hex $offset }}), VK_QCP_{{ $index }}_X_hi, VK_QCP_{{ $index }}_X_lo, VK_QCP_{{ $index }}_Y_hi, VK_QCP_{{ $index }}_Y_lo){{ $offset = add $offset 0x80 }} + mstore(add(mPtr, {{ hex $offset }}), acc_gamma){{ $offset = add $offset 0x20 }} + {{ end -}} + {{ end }} + + let l_success := staticcall(gas(), BLS12_MSM_G1, mPtr, {{ hex $offset }}, add(state, STATE_FOLDED_DIGESTS), 0x80) + if iszero(l_success){ + error_ec_op() + } + + } + + /// @notice generate the challenge (using Fiat Shamir) to fold the opening proofs + /// at ζ. + /// The process for deriving γ is the same as in derive_gamma but this time the inputs are + /// in this order (the [] means it's a commitment): + /// * ζ + /// * [Linearised polynomial] + /// * [L], [R], [O] + /// * [S₁] [S₂] + /// * [Pi_{i}] (wires associated to custom gates) + /// Then there are the purported evaluations of the previous committed polynomials: + /// * Linearised_polynomial(ζ) + /// * L(ζ), R(ζ), O(ζ), S₁(ζ), S₂(ζ) + /// * Pi_{i}(ζ) + /// * Z(ζω) + /// @param aproof pointer to the proof + function compute_gamma_kzg(aproof) { + + let state := mload(0x40) + let mPtr := add(mload(0x40), STATE_LAST_MEM) + + {{ $offset = 0x1b0 }} + mstore(add(mPtr, {{ hex $offset }}), VK_S1_COM_X_hi){{ $offset = add $offset 0x30 }} + mstore(add(mPtr, {{ hex $offset }}), VK_S1_COM_Y_hi){{ $offset = add $offset 0x30 }} + mstore(add(mPtr, {{ hex $offset }}), VK_S2_COM_X_hi){{ $offset = add $offset 0x30 }} + mstore(add(mPtr, {{ hex $offset }}), VK_S2_COM_Y_hi){{ $offset = add $offset 0x30 }} + {{- range $index, $element := .Vk.CommitmentConstraintIndexes }} + mstore(add(mPtr, {{ hex $offset }}), VK_QCP_{{ $index }}_X_hi){{ $offset = add $offset 0x30 }} + mstore(add(mPtr, {{ hex $offset }}), VK_QCP_{{ $index }}_Y_hi){{ $offset = add $offset 0x30 }} + {{ end -}} + {{ $offset = 0x1d0 }} + mstore(add(mPtr, {{ hex $offset }}), VK_S1_COM_X_lo){{ $offset = add $offset 0x30 }} + mstore(add(mPtr, {{ hex $offset }}), VK_S1_COM_Y_lo){{ $offset = add $offset 0x30 }} + mstore(add(mPtr, {{ hex $offset }}), VK_S2_COM_X_lo){{ $offset = add $offset 0x30 }} + mstore(add(mPtr, {{ hex $offset }}), VK_S2_COM_Y_lo){{ $offset = add $offset 0x30 }} + {{- range $index, $element := .Vk.CommitmentConstraintIndexes }} + mstore(add(mPtr, {{ hex $offset }}), VK_QCP_{{ $index }}_X_lo){{ $offset = add $offset 0x30 }} + mstore(add(mPtr, {{ hex $offset }}), VK_QCP_{{ $index }}_Y_lo){{ $offset = add $offset 0x30 }} + {{ end -}} + {{ $tmp := sub $offset 0x10 }} + + {{ $offset = 0 }} + mstore(mPtr, FS_GAMMA_KZG) // "gamma" {{ $offset = add $offset 0x20 }} + mstore(add(mPtr, {{ hex $offset }}), mload(add(state, STATE_ZETA))){{ $offset = add $offset 0x20 }} + mcopy(add(mPtr, {{ hex $offset }}), add(state, add(STATE_LINEARISED_POLYNOMIAL, 0x10)), 0x30){{ $offset = add $offset 0x30 }} + mcopy(add(mPtr, {{ hex $offset }}), add(state, add(STATE_LINEARISED_POLYNOMIAL, 0x50)), 0x30){{ $offset = add $offset 0x30 }} + calldatacopy(add(mPtr, {{ hex $offset }}), add(aproof, PROOF_L_COM_X), 0x120) + + {{ $offset = $tmp }} + mstore(add(mPtr, {{ hex $offset }}), mload(add(state, STATE_OPENING_LINEARISED_POLYNOMIAL_ZETA))) {{ $offset = add $offset 0x20 }} + mstore(add(mPtr, {{ hex $offset }}), calldataload(add(aproof, PROOF_L_AT_ZETA))){{ $offset = add $offset 0x20 }} + mstore(add(mPtr, {{ hex $offset }}), calldataload(add(aproof, PROOF_R_AT_ZETA))){{ $offset = add $offset 0x20 }} + mstore(add(mPtr, {{ hex $offset }}), calldataload(add(aproof, PROOF_O_AT_ZETA))){{ $offset = add $offset 0x20 }} + mstore(add(mPtr, {{ hex $offset }}), calldataload(add(aproof, PROOF_S1_AT_ZETA))){{ $offset = add $offset 0x20 }} + mstore(add(mPtr, {{ hex $offset }}), calldataload(add(aproof, PROOF_S2_AT_ZETA))){{ $offset = add $offset 0x20 }} + + {{- if (gt (len .Vk.CommitmentConstraintIndexes) 0 )}} + let _poqaz := add(aproof, PROOF_QCP_AT_ZETA) + calldatacopy(add(mPtr, {{ hex $offset }}), _poqaz, mul(VK_NB_CUSTOM_GATES, 0x20)) + {{ $offset = add $offset ( mul (len .Vk.CommitmentConstraintIndexes) 0x20 ) }} + {{ end -}} + + mstore(add(mPtr, {{ hex $offset }}), calldataload(add(aproof, PROOF_GRAND_PRODUCT_AT_ZETA_OMEGA))){{ $offset = add $offset 0x20 }} + + let start_input := 0x1b // 00.."gamma" + {{ $offset = add (sub $offset 0x20 ) 0x5 }} + let check_staticcall := staticcall(gas(), SHA2, add(mPtr,start_input), {{ hex $offset }}, add(state, STATE_GAMMA_KZG), 0x20) + if iszero(check_staticcall) { + error_verify() + } + mstore(add(state, STATE_GAMMA_KZG), mod(mload(add(state, STATE_GAMMA_KZG)), R_MOD)) + } + + /// @notice Compute the commitment to the linearized polynomial equal to + /// L(ζ)[Qₗ]+r(ζ)[Qᵣ]+R(ζ)L(ζ)[Qₘ]+O(ζ)[Qₒ]+[Qₖ]+Σᵢqc'ᵢ(ζ)[BsbCommitmentᵢ] + + /// α*( Z(μζ)(L(ζ)+β*S₁(ζ)+γ)*(R(ζ)+β*S₂(ζ)+γ)[S₃]-[Z](L(ζ)+β*id_{1}(ζ)+γ)*(R(ζ)+β*id_{2}(ζ)+γ)*(O(ζ)+β*id_{3}(ζ)+γ) ) + + /// α²*L₁(ζ)[Z] - Z_{H}(ζ)*(([H₀] + ζᵐ⁺²*[H₁] + ζ²⁽ᵐ⁺²⁾*[H₂]) + /// where + /// * id_1 = id, id_2 = vk_coset_shift*id, id_3 = vk_coset_shift^{2}*id + /// * the [] means that it's a commitment (i.e. a point on Bn254(F_p)) + /// * Z_{H}(ζ) = ζ^n-1 + /// @param aproof pointer to the proof + function compute_commitment_linearised_polynomial(aproof) { + + let state := mload(0x40) + let l_beta := mload(add(state, STATE_BETA)) + let l_gamma := mload(add(state, STATE_GAMMA)) + let l_zeta := mload(add(state, STATE_ZETA)) + let l_alpha := mload(add(state, STATE_ALPHA)) + + let u := mulmod(calldataload(add(aproof, PROOF_GRAND_PRODUCT_AT_ZETA_OMEGA)), l_beta, R_MOD) + let v := mulmod(l_beta, calldataload(add(aproof, PROOF_S1_AT_ZETA)), R_MOD) + v := addmod(v, calldataload(add(aproof, PROOF_L_AT_ZETA)), R_MOD) + v := addmod(v, l_gamma, R_MOD) + + let w := mulmod(l_beta, calldataload(add(aproof, PROOF_S2_AT_ZETA)), R_MOD) + w := addmod(w, calldataload(add(aproof, PROOF_R_AT_ZETA)), R_MOD) + w := addmod(w, l_gamma, R_MOD) + + let s1 := mulmod(u, v, R_MOD) + s1 := mulmod(s1, w, R_MOD) + s1 := mulmod(s1, l_alpha, R_MOD) + + let coset_square := mulmod(VK_COSET_SHIFT, VK_COSET_SHIFT, R_MOD) + let betazeta := mulmod(l_beta, l_zeta, R_MOD) + u := addmod(betazeta, calldataload(add(aproof, PROOF_L_AT_ZETA)), R_MOD) + u := addmod(u, l_gamma, R_MOD) + + v := mulmod(betazeta, VK_COSET_SHIFT, R_MOD) + v := addmod(v, calldataload(add(aproof, PROOF_R_AT_ZETA)), R_MOD) + v := addmod(v, l_gamma, R_MOD) + + w := mulmod(betazeta, coset_square, R_MOD) + w := addmod(w, calldataload(add(aproof, PROOF_O_AT_ZETA)), R_MOD) + w := addmod(w, l_gamma, R_MOD) + + let coeff_z := mulmod(u, v, R_MOD) + coeff_z := mulmod(coeff_z, w, R_MOD) + coeff_z := sub(R_MOD, coeff_z) + coeff_z := mulmod(coeff_z, l_alpha, R_MOD) + mstore(add(state, STATE_CHECK_VAR), coeff_z) + coeff_z := addmod(coeff_z, mload(add(state, STATE_ALPHA_SQUARE_LAGRANGE_0)), R_MOD) + + // at this stage: + // * s₁ = α*Z(μζ)(l(ζ)+β*s₁(ζ)+γ)*(r(ζ)+β*s₂(ζ)+γ)*β + // * s₂ = -α*(l(ζ)+β*ζ+γ)*(r(ζ)+β*u*ζ+γ)*(o(ζ)+β*u²*ζ+γ) + α²*L₁(ζ) + + compute_commitment_linearised_polynomial_ec(aproof, s1, coeff_z) + } + + function compute_commitment_linearised_polynomial_ec(aproof, s1, coeff_z) { + + let state := mload(0x40) + let mPtr := add(mload(0x40), STATE_LAST_MEM) + {{ $offset = 0 }} + store_point(add(mPtr, {{ hex $offset }}), VK_QL_COM_X_hi, VK_QL_COM_X_lo, VK_QL_COM_Y_hi, VK_QL_COM_Y_lo){{ $offset = add $offset 0xa0 }} + store_point(add(mPtr, {{ hex $offset }}), VK_QR_COM_X_hi, VK_QR_COM_X_lo, VK_QR_COM_Y_hi, VK_QR_COM_Y_lo){{ $offset = add $offset 0xa0 }} + store_point(add(mPtr, {{ hex $offset }}), VK_QM_COM_X_hi, VK_QM_COM_X_lo, VK_QM_COM_Y_hi, VK_QM_COM_Y_lo){{ $offset = add $offset 0xa0 }} + store_point(add(mPtr, {{ hex $offset }}), VK_QO_COM_X_hi, VK_QO_COM_X_lo, VK_QO_COM_Y_hi, VK_QO_COM_Y_lo){{ $offset = add $offset 0xa0 }} + store_point(add(mPtr, {{ hex $offset }}), VK_QK_COM_X_hi, VK_QK_COM_X_lo, VK_QK_COM_Y_hi, VK_QK_COM_Y_lo){{ $offset = add $offset 0xa0 }} + store_point(add(mPtr, {{ hex $offset }}), VK_S3_COM_X_hi, VK_S3_COM_X_lo, VK_S3_COM_Y_hi, VK_S3_COM_Y_lo){{ $offset = add $offset 0xa0 }} + store_point_calldata(add(mPtr, {{ hex $offset }}), add(aproof, PROOF_GRAND_PRODUCT_COMMITMENT_X)){{ $offset = add $offset 0xa0 }} + store_point_calldata(add(mPtr, {{ hex $offset }}), add(aproof, PROOF_H_0_COM_X)){{ $offset = add $offset 0xa0 }} + store_point_calldata(add(mPtr, {{ hex $offset }}), add(aproof, PROOF_H_1_COM_X)){{ $offset = add $offset 0xa0 }} + store_point_calldata(add(mPtr, {{ hex $offset }}), add(aproof, PROOF_H_2_COM_X)){{ $offset = add $offset 0xa0 }} + {{- if (gt (len .Vk.CommitmentConstraintIndexes) 0 )}} + {{- $tmp = 0 }} + {{- range .Vk.Qcp}} + store_point_calldata(add(mPtr, {{ hex $offset }}), add(aproof, add(PROOF_BSB_COMMITMENTS, {{ hex $tmp }}))){{ $offset = add $offset 0xa0 }}{{ $tmp = add $tmp 0x60 }} + {{- end }} + {{- end }} + + let l := calldataload(add(aproof, PROOF_L_AT_ZETA)) + let r := calldataload(add(aproof, PROOF_R_AT_ZETA)) + let rl := mulmod(l, r, R_MOD) + let o := calldataload(add(aproof, PROOF_O_AT_ZETA)) + let h_zeta := mload(add(state, STATE_ZETA_POWER_N_MINUS_ONE)) + h_zeta := sub(R_MOD, h_zeta) + {{ $offset = 0x80 }} + mstore(add(mPtr, {{ hex $offset }}), l){{ $offset = add $offset 0xa0 }} + mstore(add(mPtr, {{ hex $offset }}), r){{ $offset = add $offset 0xa0 }} + mstore(add(mPtr, {{ hex $offset }}), rl){{ $offset = add $offset 0xa0 }} + mstore(add(mPtr, {{ hex $offset }}), o){{ $offset = add $offset 0xa0 }} + mstore(add(mPtr, {{ hex $offset }}), 1){{ $offset = add $offset 0xa0 }} + mstore(add(mPtr, {{ hex $offset }}), s1){{ $offset = add $offset 0xa0 }} + mstore(add(mPtr, {{ hex $offset }}), coeff_z){{ $offset = add $offset 0xa0 }} + mstore(add(mPtr, {{ hex $offset }}), h_zeta){{ $offset = add $offset 0xa0 }} + mstore(add(mPtr, {{ hex $offset }}), mload(add(state, STATE_ZETA_POWER_N_PLUS_TWO))){{ $offset = add $offset 0xa0 }} + mstore(add(mPtr, {{ hex $offset }}), mload(add(state, STATE_ZETA_POWER_N_PLUS_TWO_SQUARE))){{ $offset = add $offset 0xa0 }} + {{- if (gt (len .Vk.CommitmentConstraintIndexes) 0 )}} + {{- $tmp := 0 }} + {{- range .Vk.Qcp}} + calldatacopy(add(mPtr, {{ hex $offset }}), add(aproof, add(PROOF_QCP_AT_ZETA, {{ hex $tmp }})), 0x20){{ $offset = add $offset 0xa0 }}{{ $tmp = add $tmp 0x20 }} + {{- end }} + {{- end }} + {{ $offset = sub $offset 0x80 }} + let l_success := staticcall(gas(), BLS12_MSM_G1, mPtr, {{ hex $offset }}, add(state, STATE_LINEARISED_POLYNOMIAL), 0x80) + if iszero(l_success){ + error_ec_op() + } + + } + + /// @notice stores a point at dst, from src. The point in src is stored + /// as [x || y] where x and y are on 0x30 bytes, and in dst the point is stored + /// as [x || y] where x and y are on 0x40 bytes, the top 0x10 bytes being 0. + /// @param dst destination pointer storing the new point + /// @param src source pointer (calldata) + function store_point_calldata(dst, src) { + mstore(dst, 0x00) + calldatacopy(add(dst, 0x10), src, 0x30) + mstore(add(dst, 0x40), 0x00) + calldatacopy(add(dst, 0x50), add(src, 0x30), 0x30) + } + + /// @param dst destination pointer storing the new point + /// @param x_hi MSB of x + /// @param x_lo LSB of x + /// @param y_hi MSB of y + /// @param y_hi LSB of y + function store_point(dst, x_hi, x_lo, y_hi, y_lo) { + mstore(dst, x_hi) + mstore(add(dst, 0x20), x_lo) + mstore(add(dst, 0x40), y_hi) + mstore(add(dst, 0x60), y_lo) + } + + /// @notice computes ζⁿ⁺² and ζ²⁽ⁿ⁺²⁾ + function compute_zeta_powers_n_plus_two() { + let state := mload(0x40) + let n_plus_two := add(VK_DOMAIN_SIZE, 2) + let mPtr := add(mload(0x40), STATE_LAST_MEM) + let zeta_power_n_plus_two := pow(mload(add(state, STATE_ZETA)), n_plus_two, mPtr) + let zeta_power_n_plus_two_square := mulmod(zeta_power_n_plus_two, zeta_power_n_plus_two, R_MOD) + let h_zeta := mload(add(state, STATE_ZETA_POWER_N_MINUS_ONE)) + h_zeta := sub(R_MOD, h_zeta) + zeta_power_n_plus_two := mulmod(zeta_power_n_plus_two, h_zeta, R_MOD) + zeta_power_n_plus_two_square := mulmod(zeta_power_n_plus_two_square, h_zeta, R_MOD) + mstore(add(state, STATE_ZETA_POWER_N_PLUS_TWO), zeta_power_n_plus_two) + mstore(add(state, STATE_ZETA_POWER_N_PLUS_TWO_SQUARE), zeta_power_n_plus_two_square) + } + + /// @notice check that the opening of the linearised polynomial at zeta is equal to + /// - [ PI(ζ) - α²*L₁(ζ) + α(l(ζ)+β*s1(ζ)+γ)(r(ζ)+β*s2(ζ)+γ)(o(ζ)+γ)*z(ωζ) ] + /// @param aproof pointer to the proof + function compute_opening_linearised_polynomial(aproof) { + + let state := mload(0x40) + + // (l(ζ)+β*s1(ζ)+γ) + let s1 + s1 := mulmod(calldataload(add(aproof, PROOF_S1_AT_ZETA)), mload(add(state, STATE_BETA)), R_MOD) + s1 := addmod(s1, mload(add(state, STATE_GAMMA)), R_MOD) + s1 := addmod(s1, calldataload(add(aproof, PROOF_L_AT_ZETA)), R_MOD) + + // (r(ζ)+β*s2(ζ)+γ) + let s2 + s2 := mulmod(calldataload(add(aproof, PROOF_S2_AT_ZETA)), mload(add(state, STATE_BETA)), R_MOD) + s2 := addmod(s2, mload(add(state, STATE_GAMMA)), R_MOD) + s2 := addmod(s2, calldataload(add(aproof, PROOF_R_AT_ZETA)), R_MOD) + + // (o(ζ)+γ) + let o + o := addmod(calldataload(add(aproof, PROOF_O_AT_ZETA)), mload(add(state, STATE_GAMMA)), R_MOD) + + // α*Z(μζ)*(l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*(o(ζ)+γ) + s1 := mulmod(s1, s2, R_MOD) + s1 := mulmod(s1, o, R_MOD) + s1 := mulmod(s1, mload(add(state, STATE_ALPHA)), R_MOD) + s1 := mulmod(s1, calldataload(add(aproof, PROOF_GRAND_PRODUCT_AT_ZETA_OMEGA)), R_MOD) + + // PI(ζ) - α²*L₁(ζ) + α(l(ζ)+β*s1(ζ)+γ)(r(ζ)+β*s2(ζ)+γ)(o(ζ)+γ)*z(ωζ) + s1 := addmod(s1, mload(add(state, STATE_PI)), R_MOD) + s2 := mload(add(state, STATE_ALPHA_SQUARE_LAGRANGE_0)) + s2 := sub(R_MOD, s2) + s1 := addmod(s1, s2, R_MOD) + s1 := sub(R_MOD, s1) + + mstore(add(state, STATE_OPENING_LINEARISED_POLYNOMIAL_ZETA), s1) + } + + // BEGINNING utils math functions ------------------------------------------------- + + /// @notice dst <- dst + src*s (Fr) dst,src are addresses, s is a value + /// @param dst pointer storing the result + /// @param src pointer to the scalar to multiply and add (on calldata) + /// @param s scalar + function fr_acc_mul_calldata(dst, src, s) { + let tmp := mulmod(calldataload(src), s, R_MOD) + mstore(dst, addmod(mload(dst), tmp, R_MOD)) + } + + /// @param x element to exponentiate + /// @param e exponent + /// @param mPtr free memory + /// @return res x ** e mod r + function pow(x, e, mPtr)->res { + mstore(mPtr, 0x20) + mstore(add(mPtr, 0x20), 0x20) + mstore(add(mPtr, 0x40), 0x20) + mstore(add(mPtr, 0x60), x) + mstore(add(mPtr, 0x80), e) + mstore(add(mPtr, 0xa0), R_MOD) + let check_staticcall := staticcall(gas(),MOD_EXP,mPtr,0xc0,mPtr,0x20) + if eq(check_staticcall, 0) { + error_mod_exp() + } + res := mload(mPtr) + } + } + return success; + } +} +` + +// MarshalSolidity converts a proof to a byte array that can be used in a +// Solidity contract. +// The marshalling of the Fp points follows https://eips.ethereum.org/EIPS/eip-2537#fine-points-and-encoding-of-base-elements +// That is a Fp point is encoded as +// [ uint256 || uint256 ] +// [ lo || hi ] , where lo, hi are in big endian. hi is on 16 bytes. +func (proof *Proof) MarshalSolidity() []byte { + + res := make([]byte, 0, 1024) + + // uint256 l_com_x; + // uint256 l_com_y; + // uint256 r_com_x; + // uint256 r_com_y; + // uint256 o_com_x; + // uint256 o_com_y; + var tmp96 [96]byte + for i := 0; i < 3; i++ { + tmp96 = proof.LRO[i].RawBytes() + res = append(res, tmp96[:]...) + } + + // uint256 h_0_x; + // uint256 h_0_y; + // uint256 h_1_x; + // uint256 h_1_y; + // uint256 h_2_x; + // uint256 h_2_y; + for i := 0; i < 3; i++ { + tmp96 = proof.H[i].RawBytes() + res = append(res, tmp96[:]...) + } + var tmp32 [32]byte + + // uint256 l_at_zeta; + // uint256 r_at_zeta; + // uint256 o_at_zeta; + // uint256 s1_at_zeta; + // uint256 s2_at_zeta; + for i := 1; i < 6; i++ { + tmp32 = proof.BatchedProof.ClaimedValues[i].Bytes() + res = append(res, tmp32[:]...) + } + + // uint256 grand_product_commitment_x; + // uint256 grand_product_commitment_y; + tmp96 = proof.Z.RawBytes() + res = append(res, tmp96[:]...) + + // uint256 grand_product_at_zeta_omega; + tmp32 = proof.ZShiftedOpening.ClaimedValue.Bytes() + res = append(res, tmp32[:]...) + + // we skip the claimed value of the linearised polynomial at zeta because it + // is recomputed by the verifier and plugged in the batch opening proof directly + + // uint256 opening_at_zeta_proof_x; + // uint256 opening_at_zeta_proof_y; + tmp96 = proof.BatchedProof.H.RawBytes() + res = append(res, tmp96[:]...) + + // uint256 opening_at_zeta_omega_proof_x; + // uint256 opening_at_zeta_omega_proof_y; + tmp96 = proof.ZShiftedOpening.H.RawBytes() + res = append(res, tmp96[:]...) + + // uint256[] selector_commit_api_at_zeta; + // uint256[] wire_committed_commitments; + for i := 0; i < len(proof.Bsb22Commitments); i++ { + tmp32 = proof.BatchedProof.ClaimedValues[6+i].Bytes() + res = append(res, tmp32[:]...) + } + + for _, bc := range proof.Bsb22Commitments { + tmp96 = bc.RawBytes() + res = append(res, tmp96[:]...) + } + + return res +} diff --git a/backend/plonk/bls12-381/verify.go b/backend/plonk/bls12-381/verify.go index edbbfa8679..6f218eb6fe 100644 --- a/backend/plonk/bls12-381/verify.go +++ b/backend/plonk/bls12-381/verify.go @@ -1,8 +1,6 @@ // Copyright 2020-2025 Consensys Software Inc. // Licensed under the Apache License, Version 2.0. See the LICENSE file for details. -// Code generated by gnark DO NOT EDIT - package plonk import ( @@ -10,6 +8,7 @@ import ( "fmt" "io" "math/big" + "text/template" "time" @@ -19,6 +18,7 @@ import ( curve "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/hash_to_field" @@ -382,7 +382,69 @@ func deriveRandomness(fs *fiatshamir.Transcript, challenge string, points ...*cu return r, nil } -// ExportSolidity not implemented for BLS12-381 +// ExportSolidity exports the verifying key to a solidity smart contract. +// +// See https://github.com/Consensys/gnark-tests for example usage. +// +// Code has not been audited and is provided as-is, we make no guarantees or warranties to its safety and reliability. func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.ExportOption) error { - return errors.New("not implemented") + funcMap := template.FuncMap{ + "hex": func(i int) string { + return fmt.Sprintf("0x%x", i) + }, + "mul": func(a, b int) int { + return a * b + }, + "inc": func(i int) int { + return i + 1 + }, + "frstr": func(x fr.Element) string { + // we use big.Int to always get a positive string. + // not the most efficient hack, but it works better for .sol generation. + bv := new(big.Int) + x.BigInt(bv) + return bv.String() + }, + "fpstr_lo": func(x fp.Element) string { + bv := new(big.Int) + twoTo256 := new(big.Int) + twoTo256.SetString("115792089237316195423570985008687907853269984665640564039457584007913129639936", 10) + x.BigInt(bv) + bv.Mod(bv, twoTo256) + return bv.String() + }, + "fpstr_hi": func(x fp.Element) string { + bv := new(big.Int) + x.BigInt(bv) + bv.Rsh(bv, 256) + return bv.String() + }, + "add": func(i, j int) int { + return i + j + }, + "sub": func(i, j int) int { + return i - j + }, + } + + t, err := template.New("t").Funcs(funcMap).Parse(tmplSolidityVerifier) + if err != nil { + return err + } + + cfg, err := solidity.NewExportConfig(exportOpts...) + if err != nil { + return err + } + if cfg.HashToFieldFn != nil { + return fmt.Errorf("setting hash to field function is not supported for PLONK Solidity export. Hash function is hardcoded to RFC9380") + } + + return t.Execute(w, struct { + Cfg solidity.ExportConfig + Vk VerifyingKey + }{ + Cfg: cfg, + Vk: *vk, + }) } diff --git a/go.mod b/go.mod index 0cdda984c5..ef8d9460e4 100644 --- a/go.mod +++ b/go.mod @@ -34,4 +34,4 @@ require ( golang.org/x/sys v0.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect -) +) \ No newline at end of file diff --git a/go.sum b/go.sum index 61591bf151..aca790bda2 100644 --- a/go.sum +++ b/go.sum @@ -669,4 +669,4 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= -rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= \ No newline at end of file diff --git a/internal/generator/backend/template/zkpschemes/plonk/plonk.verify.go.tmpl b/internal/generator/backend/template/zkpschemes/plonk/plonk.verify.go.tmpl index 8cae28969e..43ebf3e3a1 100644 --- a/internal/generator/backend/template/zkpschemes/plonk/plonk.verify.go.tmpl +++ b/internal/generator/backend/template/zkpschemes/plonk/plonk.verify.go.tmpl @@ -432,6 +432,73 @@ func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.Expor }) } +{{else if eq .Curve "BLS12-381"}} + +// ExportSolidity exports the verifying key to a solidity smart contract. +// +// Code has not been audited and is provided as-is, we make no guarantees or warranties to its safety and reliability. +func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.ExportOption) error { + funcMap := template.FuncMap{ + "hex": func(i int) string { + return fmt.Sprintf("0x%x", i) + }, + "mul": func(a, b int) int { + return a * b + }, + "inc": func(i int) int { + return i + 1 + }, + "frstr": func(x fr.Element) string { + // we use big.Int to always get a positive string. + // not the most efficient hack, but it works better for .sol generation. + bv := new(big.Int) + x.BigInt(bv) + return bv.String() + }, + "fpstr_lo": func(x fp.Element) string { + bv := new(big.Int) + twoTo256 := new(big.Int) + twoTo256.SetString("115792089237316195423570985008687907853269984665640564039457584007913129639936", 10) + x.BigInt(bv) + bv.Mod(bv, twoTo256) + return bv.String() + }, + "fpstr_hi": func(x fp.Element) string { + bv := new(big.Int) + x.BigInt(bv) + bv.Rsh(bv, 256) + return bv.String() + }, + "add": func(i, j int) int { + return i + j + }, + "sub": func(i, j int) int { + return i - j + }, + } + + t, err := template.New("t").Funcs(funcMap).Parse(tmplSolidityVerifier) + if err != nil { + return err + } + + cfg, err := solidity.NewExportConfig(exportOpts...) + if err != nil { + return err + } + if cfg.HashToFieldFn != nil { + return fmt.Errorf("setting hash to field function is not supported for PLONK Solidity export. Hash function is hardcoded to RFC9380") + } + + return t.Execute(w, struct { + Cfg solidity.ExportConfig + Vk VerifyingKey + }{ + Cfg: cfg, + Vk: *vk, + }) +} + {{else}} // ExportSolidity not implemented for {{.Curve}} func (vk *VerifyingKey) ExportSolidity(w io.Writer, exportOpts ...solidity.ExportOption) error { diff --git a/test/assert_checkcircuit.go b/test/assert_checkcircuit.go index ba44ab6bc3..813a8be543 100644 --- a/test/assert_checkcircuit.go +++ b/test/assert_checkcircuit.go @@ -122,7 +122,7 @@ func (assert *Assert) CheckCircuit(circuit frontend.Circuit, opts ...TestingOpti for _, w := range validWitnesses { w := w assert.Run(func(assert *Assert) { - checkSolidity := opt.checkSolidity && curve == ecc.BN254 + checkSolidity := opt.checkSolidity && (curve == ecc.BN254 || curve == ecc.BLS12_381) proverOpts := opt.proverOpts verifierOpts := opt.verifierOpts if b == backend.GROTH16 {