diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..12b8b48e9930187686ca43cfb9a079dfc16cd80c --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +.env +.tmp +yarn.lock + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + + +# artifacts +proofs + +# other +.vscode +crs +.DS_Store +artifacts +cache +.next + +# cached local data +outputs.json +batch.json \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000000000000000000000000000000000..d0b804da2a462044bb1c63364440b2c2164e86ad --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +# Add files here to ignore them from prettier formatting + +/dist +/coverage diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000000000000000000000000000000000..23246c1e5be893b0f44918608bf0f0f9c9ec276c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "arrowParens": "avoid", + "singleQuote": true, + "trailingComma": "all", + "printWidth": 100, + "proseWrap": "always" +} diff --git a/README.md b/README.md index 565b4ed38317f1643cf60400a43eb25bafd1a7e8..d15cfbf3a1924fe9edfb254bc27ec803dd3d6a02 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,34 @@ -# Momiji Batching Test 1 +# Momiji Testnet v2.0 +In the second release of the Momiji testnet, we have made the following upgrades +- Circuits built using Nargo 11.0 and bb.js 0.7.2 +- Separate transaction batching and publishing in `TechnicalPreview.sol` +- Recursive proof generation for batched transactions, with final verification done on-chain using `plonk_vk.sol` +- Expansion of withdrawals up to 16 recipient addresses +## Recursive proofs with Noir + +This testnet update features recursive proving in Noir, below is a general outline of the steps taken in generating and verifying recursive proofs on chain + +- The user creates an inner proof using the compiled transaction circuit in `./circuits/main/` +- This proof is then serialized into field elements and passed as an input to the recursive prover in `./circuits/recursion` +- The outer proof and the aggregation object generated by the recursive circuit are passed to `plonk_vk.sol` for final verification + +## Getting Started + +1. [Install nargo](https://noir-lang.org/getting_started/nargo_installation#option-1-noirup) version 0.11.0 with `noirup -v 0.11.0` + +2. Install dependencies with + +```bash +yarn +``` + +## Testing + +The [example test file](./test/index.ts) executes a deposit and withdrawal on-chain in a typescript `node.js` environment. +You can run the tests with: + +```sh +yarn bbjs +yarn test +``` diff --git a/circuits/main/Nargo.toml b/circuits/main/Nargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..664e817cc84695ed5b834618ccc70fc151ee6313 --- /dev/null +++ b/circuits/main/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "main" +type = "bin" +authors = [""] +compiler_version = "0.9.0" + +[dependencies] diff --git a/circuits/main/Prover.toml b/circuits/main/Prover.toml new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/circuits/main/Verifier.toml b/circuits/main/Verifier.toml new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/circuits/main/contract/main/plonk_vk.sol b/circuits/main/contract/main/plonk_vk.sol new file mode 100644 index 0000000000000000000000000000000000000000..1e9a120a169d3233f91b29f70cf994cdfc5f47d0 --- /dev/null +++ b/circuits/main/contract/main/plonk_vk.sol @@ -0,0 +1,2611 @@ +// Verification Key Hash: 99943948f332e49cace9566d62bb20479ffec6a9fdbffa30aff44c5630aa801b +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2022 Aztec +pragma solidity >=0.8.4; + +library UltraVerificationKey { + function verificationKeyHash() internal pure returns(bytes32) { + return 0x99943948f332e49cace9566d62bb20479ffec6a9fdbffa30aff44c5630aa801b; + } + + function loadVerificationKey(uint256 _vk, uint256 _omegaInverseLoc) internal pure { + assembly { + mstore(add(_vk, 0x00), 0x0000000000000000000000000000000000000000000000000000000000020000) // vk.circuit_size + mstore(add(_vk, 0x20), 0x0000000000000000000000000000000000000000000000000000000000000044) // vk.num_inputs + mstore(add(_vk, 0x40), 0x1bf82deba7d74902c3708cc6e70e61f30512eca95655210e276e5858ce8f58e5) // vk.work_root + mstore(add(_vk, 0x60), 0x30643640b9f82f90e83b698e5ea6179c7c05542e859533b48b9953a2f5360801) // vk.domain_inverse + mstore(add(_vk, 0x80), 0x300a360ca4e53d6c1a0499684d2107b4af8e1793f4ba24ebbbd603fc0c592be2) // vk.Q1.x + mstore(add(_vk, 0xa0), 0x2c633207cc312c943885a56df7167a24cde5e1661739124a62f71a8df6f61b9d) // vk.Q1.y + mstore(add(_vk, 0xc0), 0x0b4b3baaf0cac647474264b5436ff692c89795526930bfa2a3993dbd1c60d8ca) // vk.Q2.x + mstore(add(_vk, 0xe0), 0x24b3be4ca1a22ade279205a6b62ad2f399e1fba855b9663a7e2b8d963b20bd4a) // vk.Q2.y + mstore(add(_vk, 0x100), 0x2219d241a2b6a83b521809e4796dafe503c7a0f3ddb76761bd9ae8cf8098ec26) // vk.Q3.x + mstore(add(_vk, 0x120), 0x12dc4b43eedc359ef67f9f663e0fc2f06cf95cd64957bf5c63ea842ccbb9c964) // vk.Q3.y + mstore(add(_vk, 0x140), 0x19e4ba6410ab87f928ac3da3876ba56d7fcc98166f7692e673d3c1072b8601c8) // vk.Q4.x + mstore(add(_vk, 0x160), 0x17ef971e38cc7946952a29c7fde99696975cc3a59efbfbe5e996dc06feaffe68) // vk.Q4.y + mstore(add(_vk, 0x180), 0x12e6a815446f79314ee65b2387997c5d1636d79b87bb42273d4d2397d8d81fe7) // vk.Q_M.x + mstore(add(_vk, 0x1a0), 0x00b29031b47be0da685f99629a69c24d8ea46041496d47a1d7f1a0d1e37f86d1) // vk.Q_M.y + mstore(add(_vk, 0x1c0), 0x1ca0b61212f09301fe93baf74b3f85d60fb48d519eb224de0c6854c4e64f04e2) // vk.Q_C.x + mstore(add(_vk, 0x1e0), 0x10a318161aa0128a7d16cf8bdeee458e561c37f66e2dd7d09679ba31bf29a6ee) // vk.Q_C.y + mstore(add(_vk, 0x200), 0x260ddd6b5aa1dfceefc65322c32a31d34519485725f13b49c1daab1ec28a09cb) // vk.Q_ARITHMETIC.x + mstore(add(_vk, 0x220), 0x077a65644486f2e7a62eae7d6d1a097c94b818b6172bfc7b86411d5ae0591fd7) // vk.Q_ARITHMETIC.y + mstore(add(_vk, 0x240), 0x0c317a8407eee9ce23b55012759392f46ae927ac2e24bb15790aa8eb06c8e23f) // vk.QSORT.x + mstore(add(_vk, 0x260), 0x05bb39df7cfa6b82be965a85ab7ec3022a4c8ec50b52e97cf424f48fe4728034) // vk.QSORT.y + mstore(add(_vk, 0x280), 0x07b8fa93974c69c95e789d4035102ce1c0acc919f546abce590280c402c3351d) // vk.Q_ELLIPTIC.x + mstore(add(_vk, 0x2a0), 0x1921b1a2deb9808fd4dcd8518bdd95b4f9ea70b4e3f17c6f8369444ed17cd521) // vk.Q_ELLIPTIC.y + mstore(add(_vk, 0x2c0), 0x1414af76247139fa9e8fef8b393a3e03227ee3a6fedb1e55f5db82cb2352782a) // vk.Q_AUX.x + mstore(add(_vk, 0x2e0), 0x2c7895a68d2fab5b2bce4d7703daebf9011e63d675bc6898c7f06087d6d83d99) // vk.Q_AUX.y + mstore(add(_vk, 0x300), 0x179593d8d9f247a3f3323ad84cd49bee54b08d8808ef60b7186bc9fdb690c0df) // vk.SIGMA1.x + mstore(add(_vk, 0x320), 0x067d909267da6c15daaa09476468bcc8c41a20d61a62cbee7d9779d173fe7254) // vk.SIGMA1.y + mstore(add(_vk, 0x340), 0x284fddea7e6337e6780e936f412c16c079dadd92143f46bf777cc7acebeb7ee7) // vk.SIGMA2.x + mstore(add(_vk, 0x360), 0x19d7b4f8b6200f4de709e8f9ecebee7509f8a9bde1db9f9c17775f7a1d753246) // vk.SIGMA2.y + mstore(add(_vk, 0x380), 0x0e440bf9afabf027a86f582d7078ceae37fb30a28c533b556a4e1cb5949bf8b0) // vk.SIGMA3.x + mstore(add(_vk, 0x3a0), 0x0a062a059217e5a90702bb809f23e6a4e06c75ba0557ae05dc33725331debeaf) // vk.SIGMA3.y + mstore(add(_vk, 0x3c0), 0x2ae281dd7401738fd3f019bacf215b44490e16498ed42f34c993990fe915d2b3) // vk.SIGMA4.x + mstore(add(_vk, 0x3e0), 0x17460a29f7f66370fcee90b4402bca4cf0ecaff24eb14b25197b93ba07f8cf98) // vk.SIGMA4.y + mstore(add(_vk, 0x400), 0x1f085b2095ee26145e167f353ef9a02dde005b3ba11bf60add10bdd9c00e7643) // vk.TABLE1.x + mstore(add(_vk, 0x420), 0x0748759c43af1624acf605b6a23ebba01173cd346730c934d5fb7edd48413f94) // vk.TABLE1.y + mstore(add(_vk, 0x440), 0x2c8d2b9623692ab46021a60209f7148458ecb2ccdbbd766251f463ca7c53489a) // vk.TABLE2.x + mstore(add(_vk, 0x460), 0x1aefa543256fc4b49dc193383f0156f7c331a2b89d0a0b5938a2fe2abe136d19) // vk.TABLE2.y + mstore(add(_vk, 0x480), 0x058ea36a362c899e3304278eca9a52fa74b07588d35cc5583278237cbbb53d68) // vk.TABLE3.x + mstore(add(_vk, 0x4a0), 0x2afc5a5c85ca7b8fdac8a2b082096621e184e5a00a5cf1d481e28ad8071a691a) // vk.TABLE3.y + mstore(add(_vk, 0x4c0), 0x14864cbfc194228094a325c4abf8260dbdca65e4883fce1425ec2933843b6737) // vk.TABLE4.x + mstore(add(_vk, 0x4e0), 0x1489390b9b42c540c675cfea8acb88ceebe8c678f21ee8e8bee12af884fc1c3d) // vk.TABLE4.y + mstore(add(_vk, 0x500), 0x2bcb5c4b6ffdde3554c9b2b0cdd986521c13704f20cfa90410567a6eeb38ae42) // vk.TABLE_TYPE.x + mstore(add(_vk, 0x520), 0x2f883fb0125bcb80ae1a0457ceca1b1ab79ddfc1fecea5076d44e10c2b0fbd27) // vk.TABLE_TYPE.y + mstore(add(_vk, 0x540), 0x0fe1c1d62eec15cdb8d9b0b33eadfa8cfe278dc1c9f1f3b33f7d6dce4c43a9a3) // vk.ID1.x + mstore(add(_vk, 0x560), 0x23fd6019498eb968c0fae087786ce54f297527574a0775266e2ba7f0ed23642a) // vk.ID1.y + mstore(add(_vk, 0x580), 0x101ad1d0289110de5ff6aef9c5b109dfd5a21ade6bd5f45543e31135753a480e) // vk.ID2.x + mstore(add(_vk, 0x5a0), 0x246241f04234bc747304f8d24da948c44b76e0bae7f7e01b693afab2d83aff06) // vk.ID2.y + mstore(add(_vk, 0x5c0), 0x2a7740904752b38667ba7eace72476903c33304dcf9674b13e6f955b07613aef) // vk.ID3.x + mstore(add(_vk, 0x5e0), 0x187ca424a139cddc4149fa59489b9d3dd530c2bae99a69a1ad1c0e299e12fb2e) // vk.ID3.y + mstore(add(_vk, 0x600), 0x295d4b4b16283a4a0853420e9aa91a1bac773af0c009e02a3a3b72ce0a4c9679) // vk.ID4.x + mstore(add(_vk, 0x620), 0x0e7e35816cb5aebc6c5a8a4a7ffabb95875c920c58d30eaa76e253b7d82400d7) // vk.ID4.y + mstore(add(_vk, 0x640), 0x00) // vk.contains_recursive_proof + mstore(add(_vk, 0x660), 0) // vk.recursive_proof_public_input_indices + mstore(add(_vk, 0x680), 0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1) // vk.g2_x.X.c1 + mstore(add(_vk, 0x6a0), 0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0) // vk.g2_x.X.c0 + mstore(add(_vk, 0x6c0), 0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4) // vk.g2_x.Y.c1 + mstore(add(_vk, 0x6e0), 0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55) // vk.g2_x.Y.c0 + mstore(_omegaInverseLoc, 0x244cf010c43ca87237d8b00bf9dd50c4c01c7f086bd4e8c920e75251d96f0d22) // vk.work_root_inverse + } + } +} + +/** + * @title Ultra Plonk proof verification contract + * @dev Top level Plonk proof verification contract, which allows Plonk proof to be verified + */ +abstract contract BaseUltraVerifier { + // VERIFICATION KEY MEMORY LOCATIONS + uint256 internal constant N_LOC = 0x380; + uint256 internal constant NUM_INPUTS_LOC = 0x3a0; + uint256 internal constant OMEGA_LOC = 0x3c0; + uint256 internal constant DOMAIN_INVERSE_LOC = 0x3e0; + uint256 internal constant Q1_X_LOC = 0x400; + uint256 internal constant Q1_Y_LOC = 0x420; + uint256 internal constant Q2_X_LOC = 0x440; + uint256 internal constant Q2_Y_LOC = 0x460; + uint256 internal constant Q3_X_LOC = 0x480; + uint256 internal constant Q3_Y_LOC = 0x4a0; + uint256 internal constant Q4_X_LOC = 0x4c0; + uint256 internal constant Q4_Y_LOC = 0x4e0; + uint256 internal constant QM_X_LOC = 0x500; + uint256 internal constant QM_Y_LOC = 0x520; + uint256 internal constant QC_X_LOC = 0x540; + uint256 internal constant QC_Y_LOC = 0x560; + uint256 internal constant QARITH_X_LOC = 0x580; + uint256 internal constant QARITH_Y_LOC = 0x5a0; + uint256 internal constant QSORT_X_LOC = 0x5c0; + uint256 internal constant QSORT_Y_LOC = 0x5e0; + uint256 internal constant QELLIPTIC_X_LOC = 0x600; + uint256 internal constant QELLIPTIC_Y_LOC = 0x620; + uint256 internal constant QAUX_X_LOC = 0x640; + uint256 internal constant QAUX_Y_LOC = 0x660; + uint256 internal constant SIGMA1_X_LOC = 0x680; + uint256 internal constant SIGMA1_Y_LOC = 0x6a0; + uint256 internal constant SIGMA2_X_LOC = 0x6c0; + uint256 internal constant SIGMA2_Y_LOC = 0x6e0; + uint256 internal constant SIGMA3_X_LOC = 0x700; + uint256 internal constant SIGMA3_Y_LOC = 0x720; + uint256 internal constant SIGMA4_X_LOC = 0x740; + uint256 internal constant SIGMA4_Y_LOC = 0x760; + uint256 internal constant TABLE1_X_LOC = 0x780; + uint256 internal constant TABLE1_Y_LOC = 0x7a0; + uint256 internal constant TABLE2_X_LOC = 0x7c0; + uint256 internal constant TABLE2_Y_LOC = 0x7e0; + uint256 internal constant TABLE3_X_LOC = 0x800; + uint256 internal constant TABLE3_Y_LOC = 0x820; + uint256 internal constant TABLE4_X_LOC = 0x840; + uint256 internal constant TABLE4_Y_LOC = 0x860; + uint256 internal constant TABLE_TYPE_X_LOC = 0x880; + uint256 internal constant TABLE_TYPE_Y_LOC = 0x8a0; + uint256 internal constant ID1_X_LOC = 0x8c0; + uint256 internal constant ID1_Y_LOC = 0x8e0; + uint256 internal constant ID2_X_LOC = 0x900; + uint256 internal constant ID2_Y_LOC = 0x920; + uint256 internal constant ID3_X_LOC = 0x940; + uint256 internal constant ID3_Y_LOC = 0x960; + uint256 internal constant ID4_X_LOC = 0x980; + uint256 internal constant ID4_Y_LOC = 0x9a0; + uint256 internal constant CONTAINS_RECURSIVE_PROOF_LOC = 0x9c0; + uint256 internal constant RECURSIVE_PROOF_PUBLIC_INPUT_INDICES_LOC = 0x9e0; + uint256 internal constant G2X_X0_LOC = 0xa00; + uint256 internal constant G2X_X1_LOC = 0xa20; + uint256 internal constant G2X_Y0_LOC = 0xa40; + uint256 internal constant G2X_Y1_LOC = 0xa60; + + // ### PROOF DATA MEMORY LOCATIONS + uint256 internal constant W1_X_LOC = 0x1200; + uint256 internal constant W1_Y_LOC = 0x1220; + uint256 internal constant W2_X_LOC = 0x1240; + uint256 internal constant W2_Y_LOC = 0x1260; + uint256 internal constant W3_X_LOC = 0x1280; + uint256 internal constant W3_Y_LOC = 0x12a0; + uint256 internal constant W4_X_LOC = 0x12c0; + uint256 internal constant W4_Y_LOC = 0x12e0; + uint256 internal constant S_X_LOC = 0x1300; + uint256 internal constant S_Y_LOC = 0x1320; + uint256 internal constant Z_X_LOC = 0x1340; + uint256 internal constant Z_Y_LOC = 0x1360; + uint256 internal constant Z_LOOKUP_X_LOC = 0x1380; + uint256 internal constant Z_LOOKUP_Y_LOC = 0x13a0; + uint256 internal constant T1_X_LOC = 0x13c0; + uint256 internal constant T1_Y_LOC = 0x13e0; + uint256 internal constant T2_X_LOC = 0x1400; + uint256 internal constant T2_Y_LOC = 0x1420; + uint256 internal constant T3_X_LOC = 0x1440; + uint256 internal constant T3_Y_LOC = 0x1460; + uint256 internal constant T4_X_LOC = 0x1480; + uint256 internal constant T4_Y_LOC = 0x14a0; + + uint256 internal constant W1_EVAL_LOC = 0x1600; + uint256 internal constant W2_EVAL_LOC = 0x1620; + uint256 internal constant W3_EVAL_LOC = 0x1640; + uint256 internal constant W4_EVAL_LOC = 0x1660; + uint256 internal constant S_EVAL_LOC = 0x1680; + uint256 internal constant Z_EVAL_LOC = 0x16a0; + uint256 internal constant Z_LOOKUP_EVAL_LOC = 0x16c0; + uint256 internal constant Q1_EVAL_LOC = 0x16e0; + uint256 internal constant Q2_EVAL_LOC = 0x1700; + uint256 internal constant Q3_EVAL_LOC = 0x1720; + uint256 internal constant Q4_EVAL_LOC = 0x1740; + uint256 internal constant QM_EVAL_LOC = 0x1760; + uint256 internal constant QC_EVAL_LOC = 0x1780; + uint256 internal constant QARITH_EVAL_LOC = 0x17a0; + uint256 internal constant QSORT_EVAL_LOC = 0x17c0; + uint256 internal constant QELLIPTIC_EVAL_LOC = 0x17e0; + uint256 internal constant QAUX_EVAL_LOC = 0x1800; + uint256 internal constant TABLE1_EVAL_LOC = 0x1840; + uint256 internal constant TABLE2_EVAL_LOC = 0x1860; + uint256 internal constant TABLE3_EVAL_LOC = 0x1880; + uint256 internal constant TABLE4_EVAL_LOC = 0x18a0; + uint256 internal constant TABLE_TYPE_EVAL_LOC = 0x18c0; + uint256 internal constant ID1_EVAL_LOC = 0x18e0; + uint256 internal constant ID2_EVAL_LOC = 0x1900; + uint256 internal constant ID3_EVAL_LOC = 0x1920; + uint256 internal constant ID4_EVAL_LOC = 0x1940; + uint256 internal constant SIGMA1_EVAL_LOC = 0x1960; + uint256 internal constant SIGMA2_EVAL_LOC = 0x1980; + uint256 internal constant SIGMA3_EVAL_LOC = 0x19a0; + uint256 internal constant SIGMA4_EVAL_LOC = 0x19c0; + uint256 internal constant W1_OMEGA_EVAL_LOC = 0x19e0; + uint256 internal constant W2_OMEGA_EVAL_LOC = 0x2000; + uint256 internal constant W3_OMEGA_EVAL_LOC = 0x2020; + uint256 internal constant W4_OMEGA_EVAL_LOC = 0x2040; + uint256 internal constant S_OMEGA_EVAL_LOC = 0x2060; + uint256 internal constant Z_OMEGA_EVAL_LOC = 0x2080; + uint256 internal constant Z_LOOKUP_OMEGA_EVAL_LOC = 0x20a0; + uint256 internal constant TABLE1_OMEGA_EVAL_LOC = 0x20c0; + uint256 internal constant TABLE2_OMEGA_EVAL_LOC = 0x20e0; + uint256 internal constant TABLE3_OMEGA_EVAL_LOC = 0x2100; + uint256 internal constant TABLE4_OMEGA_EVAL_LOC = 0x2120; + + uint256 internal constant PI_Z_X_LOC = 0x2300; + uint256 internal constant PI_Z_Y_LOC = 0x2320; + uint256 internal constant PI_Z_OMEGA_X_LOC = 0x2340; + uint256 internal constant PI_Z_OMEGA_Y_LOC = 0x2360; + + // Used for elliptic widget. These are alias names for wire + shifted wire evaluations + uint256 internal constant X1_EVAL_LOC = W2_EVAL_LOC; + uint256 internal constant X2_EVAL_LOC = W1_OMEGA_EVAL_LOC; + uint256 internal constant X3_EVAL_LOC = W2_OMEGA_EVAL_LOC; + uint256 internal constant Y1_EVAL_LOC = W3_EVAL_LOC; + uint256 internal constant Y2_EVAL_LOC = W4_OMEGA_EVAL_LOC; + uint256 internal constant Y3_EVAL_LOC = W3_OMEGA_EVAL_LOC; + uint256 internal constant QBETA_LOC = Q3_EVAL_LOC; + uint256 internal constant QBETA_SQR_LOC = Q4_EVAL_LOC; + uint256 internal constant QSIGN_LOC = Q1_EVAL_LOC; + + // ### CHALLENGES MEMORY OFFSETS + + uint256 internal constant C_BETA_LOC = 0x2600; + uint256 internal constant C_GAMMA_LOC = 0x2620; + uint256 internal constant C_ALPHA_LOC = 0x2640; + uint256 internal constant C_ETA_LOC = 0x2660; + uint256 internal constant C_ETA_SQR_LOC = 0x2680; + uint256 internal constant C_ETA_CUBE_LOC = 0x26a0; + + uint256 internal constant C_ZETA_LOC = 0x26c0; + uint256 internal constant C_CURRENT_LOC = 0x26e0; + uint256 internal constant C_V0_LOC = 0x2700; + uint256 internal constant C_V1_LOC = 0x2720; + uint256 internal constant C_V2_LOC = 0x2740; + uint256 internal constant C_V3_LOC = 0x2760; + uint256 internal constant C_V4_LOC = 0x2780; + uint256 internal constant C_V5_LOC = 0x27a0; + uint256 internal constant C_V6_LOC = 0x27c0; + uint256 internal constant C_V7_LOC = 0x27e0; + uint256 internal constant C_V8_LOC = 0x2800; + uint256 internal constant C_V9_LOC = 0x2820; + uint256 internal constant C_V10_LOC = 0x2840; + uint256 internal constant C_V11_LOC = 0x2860; + uint256 internal constant C_V12_LOC = 0x2880; + uint256 internal constant C_V13_LOC = 0x28a0; + uint256 internal constant C_V14_LOC = 0x28c0; + uint256 internal constant C_V15_LOC = 0x28e0; + uint256 internal constant C_V16_LOC = 0x2900; + uint256 internal constant C_V17_LOC = 0x2920; + uint256 internal constant C_V18_LOC = 0x2940; + uint256 internal constant C_V19_LOC = 0x2960; + uint256 internal constant C_V20_LOC = 0x2980; + uint256 internal constant C_V21_LOC = 0x29a0; + uint256 internal constant C_V22_LOC = 0x29c0; + uint256 internal constant C_V23_LOC = 0x29e0; + uint256 internal constant C_V24_LOC = 0x2a00; + uint256 internal constant C_V25_LOC = 0x2a20; + uint256 internal constant C_V26_LOC = 0x2a40; + uint256 internal constant C_V27_LOC = 0x2a60; + uint256 internal constant C_V28_LOC = 0x2a80; + uint256 internal constant C_V29_LOC = 0x2aa0; + uint256 internal constant C_V30_LOC = 0x2ac0; + + uint256 internal constant C_U_LOC = 0x2b00; + + // ### LOCAL VARIABLES MEMORY OFFSETS + uint256 internal constant DELTA_NUMERATOR_LOC = 0x3000; + uint256 internal constant DELTA_DENOMINATOR_LOC = 0x3020; + uint256 internal constant ZETA_POW_N_LOC = 0x3040; + uint256 internal constant PUBLIC_INPUT_DELTA_LOC = 0x3060; + uint256 internal constant ZERO_POLY_LOC = 0x3080; + uint256 internal constant L_START_LOC = 0x30a0; + uint256 internal constant L_END_LOC = 0x30c0; + uint256 internal constant R_ZERO_EVAL_LOC = 0x30e0; + + uint256 internal constant PLOOKUP_DELTA_NUMERATOR_LOC = 0x3100; + uint256 internal constant PLOOKUP_DELTA_DENOMINATOR_LOC = 0x3120; + uint256 internal constant PLOOKUP_DELTA_LOC = 0x3140; + + uint256 internal constant ACCUMULATOR_X_LOC = 0x3160; + uint256 internal constant ACCUMULATOR_Y_LOC = 0x3180; + uint256 internal constant ACCUMULATOR2_X_LOC = 0x31a0; + uint256 internal constant ACCUMULATOR2_Y_LOC = 0x31c0; + uint256 internal constant PAIRING_LHS_X_LOC = 0x31e0; + uint256 internal constant PAIRING_LHS_Y_LOC = 0x3200; + uint256 internal constant PAIRING_RHS_X_LOC = 0x3220; + uint256 internal constant PAIRING_RHS_Y_LOC = 0x3240; + + // ### SUCCESS FLAG MEMORY LOCATIONS + uint256 internal constant GRAND_PRODUCT_SUCCESS_FLAG = 0x3300; + uint256 internal constant ARITHMETIC_TERM_SUCCESS_FLAG = 0x3020; + uint256 internal constant BATCH_OPENING_SUCCESS_FLAG = 0x3340; + uint256 internal constant OPENING_COMMITMENT_SUCCESS_FLAG = 0x3360; + uint256 internal constant PAIRING_PREAMBLE_SUCCESS_FLAG = 0x3380; + uint256 internal constant PAIRING_SUCCESS_FLAG = 0x33a0; + uint256 internal constant RESULT_FLAG = 0x33c0; + + // misc stuff + uint256 internal constant OMEGA_INVERSE_LOC = 0x3400; + uint256 internal constant C_ALPHA_SQR_LOC = 0x3420; + uint256 internal constant C_ALPHA_CUBE_LOC = 0x3440; + uint256 internal constant C_ALPHA_QUAD_LOC = 0x3460; + uint256 internal constant C_ALPHA_BASE_LOC = 0x3480; + + // ### RECURSION VARIABLE MEMORY LOCATIONS + uint256 internal constant RECURSIVE_P1_X_LOC = 0x3500; + uint256 internal constant RECURSIVE_P1_Y_LOC = 0x3520; + uint256 internal constant RECURSIVE_P2_X_LOC = 0x3540; + uint256 internal constant RECURSIVE_P2_Y_LOC = 0x3560; + + uint256 internal constant PUBLIC_INPUTS_HASH_LOCATION = 0x3580; + + // sub-identity storage + uint256 internal constant PERMUTATION_IDENTITY = 0x3600; + uint256 internal constant PLOOKUP_IDENTITY = 0x3620; + uint256 internal constant ARITHMETIC_IDENTITY = 0x3640; + uint256 internal constant SORT_IDENTITY = 0x3660; + uint256 internal constant ELLIPTIC_IDENTITY = 0x3680; + uint256 internal constant AUX_IDENTITY = 0x36a0; + uint256 internal constant AUX_NON_NATIVE_FIELD_EVALUATION = 0x36c0; + uint256 internal constant AUX_LIMB_ACCUMULATOR_EVALUATION = 0x36e0; + uint256 internal constant AUX_RAM_CONSISTENCY_EVALUATION = 0x3700; + uint256 internal constant AUX_ROM_CONSISTENCY_EVALUATION = 0x3720; + uint256 internal constant AUX_MEMORY_EVALUATION = 0x3740; + + uint256 internal constant QUOTIENT_EVAL_LOC = 0x3760; + uint256 internal constant ZERO_POLY_INVERSE_LOC = 0x3780; + + // when hashing public inputs we use memory at NU_CHALLENGE_INPUT_LOC_A, as the hash input size is unknown at compile time + uint256 internal constant NU_CHALLENGE_INPUT_LOC_A = 0x37a0; + uint256 internal constant NU_CHALLENGE_INPUT_LOC_B = 0x37c0; + uint256 internal constant NU_CHALLENGE_INPUT_LOC_C = 0x37e0; + + bytes4 internal constant PUBLIC_INPUT_INVALID_BN128_G1_POINT_SELECTOR = 0xeba9f4a6; + bytes4 internal constant PUBLIC_INPUT_GE_P_SELECTOR = 0x374a972f; + bytes4 internal constant MOD_EXP_FAILURE_SELECTOR = 0xf894a7bc; + bytes4 internal constant EC_SCALAR_MUL_FAILURE_SELECTOR = 0xf755f369; + bytes4 internal constant PROOF_FAILURE_SELECTOR = 0x0711fcec; + + uint256 internal constant ETA_INPUT_LENGTH = 0xc0; // W1, W2, W3 = 6 * 0x20 bytes + + // We need to hash 41 field elements when generating the NU challenge + // w1, w2, w3, w4, s, z, z_lookup, q1, q2, q3, q4, qm, qc, qarith (14) + // qsort, qelliptic, qaux, sigma1, sigma2, sigma, sigma4, (7) + // table1, table2, table3, table4, tabletype, id1, id2, id3, id4, (9) + // w1_omega, w2_omega, w3_omega, w4_omega, s_omega, z_omega, z_lookup_omega, (7) + // table1_omega, table2_omega, table3_omega, table4_omega (4) + uint256 internal constant NU_INPUT_LENGTH = 0x520; // 0x520 = 41 * 0x20 + + // There are ELEVEN G1 group elements added into the transcript in the `beta` round, that we need to skip over + // W1, W2, W3, W4, S, Z, Z_LOOKUP, T1, T2, T3, T4 + uint256 internal constant NU_CALLDATA_SKIP_LENGTH = 0x2c0; // 11 * 0x40 = 0x2c0 + + uint256 internal constant NEGATIVE_INVERSE_OF_2_MODULO_P = + 0x183227397098d014dc2822db40c0ac2e9419f4243cdcb848a1f0fac9f8000000; + uint256 internal constant LIMB_SIZE = 0x100000000000000000; // 2<<68 + uint256 internal constant SUBLIMB_SHIFT = 0x4000; // 2<<14 + + error PUBLIC_INPUT_COUNT_INVALID(uint256 expected, uint256 actual); + error PUBLIC_INPUT_INVALID_BN128_G1_POINT(); + error PUBLIC_INPUT_GE_P(); + error MOD_EXP_FAILURE(); + error EC_SCALAR_MUL_FAILURE(); + error PROOF_FAILURE(); + + function getVerificationKeyHash() public pure virtual returns (bytes32); + + function loadVerificationKey(uint256 _vk, uint256 _omegaInverseLoc) internal pure virtual; + + /** + * @notice Verify a Ultra Plonk proof + * @param _proof - The serialized proof + * @param _publicInputs - An array of the public inputs + * @return True if proof is valid, reverts otherwise + */ + function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external view returns (bool) { + loadVerificationKey(N_LOC, OMEGA_INVERSE_LOC); + + uint256 requiredPublicInputCount; + assembly { + requiredPublicInputCount := mload(NUM_INPUTS_LOC) + } + if (requiredPublicInputCount != _publicInputs.length) { + revert PUBLIC_INPUT_COUNT_INVALID(requiredPublicInputCount, _publicInputs.length); + } + + assembly { + let q := 21888242871839275222246405745257275088696311157297823662689037894645226208583 // EC group order + let p := 21888242871839275222246405745257275088548364400416034343698204186575808495617 // Prime field order + + /** + * LOAD PROOF FROM CALLDATA + */ + { + let data_ptr := add(calldataload(0x04), 0x24) + + mstore(W1_Y_LOC, mod(calldataload(data_ptr), q)) + mstore(W1_X_LOC, mod(calldataload(add(data_ptr, 0x20)), q)) + + mstore(W2_Y_LOC, mod(calldataload(add(data_ptr, 0x40)), q)) + mstore(W2_X_LOC, mod(calldataload(add(data_ptr, 0x60)), q)) + + mstore(W3_Y_LOC, mod(calldataload(add(data_ptr, 0x80)), q)) + mstore(W3_X_LOC, mod(calldataload(add(data_ptr, 0xa0)), q)) + + mstore(W4_Y_LOC, mod(calldataload(add(data_ptr, 0xc0)), q)) + mstore(W4_X_LOC, mod(calldataload(add(data_ptr, 0xe0)), q)) + + mstore(S_Y_LOC, mod(calldataload(add(data_ptr, 0x100)), q)) + mstore(S_X_LOC, mod(calldataload(add(data_ptr, 0x120)), q)) + mstore(Z_Y_LOC, mod(calldataload(add(data_ptr, 0x140)), q)) + mstore(Z_X_LOC, mod(calldataload(add(data_ptr, 0x160)), q)) + mstore(Z_LOOKUP_Y_LOC, mod(calldataload(add(data_ptr, 0x180)), q)) + mstore(Z_LOOKUP_X_LOC, mod(calldataload(add(data_ptr, 0x1a0)), q)) + mstore(T1_Y_LOC, mod(calldataload(add(data_ptr, 0x1c0)), q)) + mstore(T1_X_LOC, mod(calldataload(add(data_ptr, 0x1e0)), q)) + + mstore(T2_Y_LOC, mod(calldataload(add(data_ptr, 0x200)), q)) + mstore(T2_X_LOC, mod(calldataload(add(data_ptr, 0x220)), q)) + + mstore(T3_Y_LOC, mod(calldataload(add(data_ptr, 0x240)), q)) + mstore(T3_X_LOC, mod(calldataload(add(data_ptr, 0x260)), q)) + + mstore(T4_Y_LOC, mod(calldataload(add(data_ptr, 0x280)), q)) + mstore(T4_X_LOC, mod(calldataload(add(data_ptr, 0x2a0)), q)) + + mstore(W1_EVAL_LOC, mod(calldataload(add(data_ptr, 0x2c0)), p)) + mstore(W2_EVAL_LOC, mod(calldataload(add(data_ptr, 0x2e0)), p)) + mstore(W3_EVAL_LOC, mod(calldataload(add(data_ptr, 0x300)), p)) + mstore(W4_EVAL_LOC, mod(calldataload(add(data_ptr, 0x320)), p)) + mstore(S_EVAL_LOC, mod(calldataload(add(data_ptr, 0x340)), p)) + mstore(Z_EVAL_LOC, mod(calldataload(add(data_ptr, 0x360)), p)) + mstore(Z_LOOKUP_EVAL_LOC, mod(calldataload(add(data_ptr, 0x380)), p)) + mstore(Q1_EVAL_LOC, mod(calldataload(add(data_ptr, 0x3a0)), p)) + mstore(Q2_EVAL_LOC, mod(calldataload(add(data_ptr, 0x3c0)), p)) + mstore(Q3_EVAL_LOC, mod(calldataload(add(data_ptr, 0x3e0)), p)) + mstore(Q4_EVAL_LOC, mod(calldataload(add(data_ptr, 0x400)), p)) + mstore(QM_EVAL_LOC, mod(calldataload(add(data_ptr, 0x420)), p)) + mstore(QC_EVAL_LOC, mod(calldataload(add(data_ptr, 0x440)), p)) + mstore(QARITH_EVAL_LOC, mod(calldataload(add(data_ptr, 0x460)), p)) + mstore(QSORT_EVAL_LOC, mod(calldataload(add(data_ptr, 0x480)), p)) + mstore(QELLIPTIC_EVAL_LOC, mod(calldataload(add(data_ptr, 0x4a0)), p)) + mstore(QAUX_EVAL_LOC, mod(calldataload(add(data_ptr, 0x4c0)), p)) + + mstore(SIGMA1_EVAL_LOC, mod(calldataload(add(data_ptr, 0x4e0)), p)) + mstore(SIGMA2_EVAL_LOC, mod(calldataload(add(data_ptr, 0x500)), p)) + + mstore(SIGMA3_EVAL_LOC, mod(calldataload(add(data_ptr, 0x520)), p)) + mstore(SIGMA4_EVAL_LOC, mod(calldataload(add(data_ptr, 0x540)), p)) + + mstore(TABLE1_EVAL_LOC, mod(calldataload(add(data_ptr, 0x560)), p)) + mstore(TABLE2_EVAL_LOC, mod(calldataload(add(data_ptr, 0x580)), p)) + mstore(TABLE3_EVAL_LOC, mod(calldataload(add(data_ptr, 0x5a0)), p)) + mstore(TABLE4_EVAL_LOC, mod(calldataload(add(data_ptr, 0x5c0)), p)) + mstore(TABLE_TYPE_EVAL_LOC, mod(calldataload(add(data_ptr, 0x5e0)), p)) + + mstore(ID1_EVAL_LOC, mod(calldataload(add(data_ptr, 0x600)), p)) + mstore(ID2_EVAL_LOC, mod(calldataload(add(data_ptr, 0x620)), p)) + mstore(ID3_EVAL_LOC, mod(calldataload(add(data_ptr, 0x640)), p)) + mstore(ID4_EVAL_LOC, mod(calldataload(add(data_ptr, 0x660)), p)) + + mstore(W1_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x680)), p)) + mstore(W2_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x6a0)), p)) + mstore(W3_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x6c0)), p)) + mstore(W4_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x6e0)), p)) + mstore(S_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x700)), p)) + + mstore(Z_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x720)), p)) + + mstore(Z_LOOKUP_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x740)), p)) + mstore(TABLE1_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x760)), p)) + mstore(TABLE2_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x780)), p)) + mstore(TABLE3_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x7a0)), p)) + mstore(TABLE4_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x7c0)), p)) + + mstore(PI_Z_Y_LOC, mod(calldataload(add(data_ptr, 0x7e0)), q)) + mstore(PI_Z_X_LOC, mod(calldataload(add(data_ptr, 0x800)), q)) + + mstore(PI_Z_OMEGA_Y_LOC, mod(calldataload(add(data_ptr, 0x820)), q)) + mstore(PI_Z_OMEGA_X_LOC, mod(calldataload(add(data_ptr, 0x840)), q)) + } + + /** + * LOAD RECURSIVE PROOF INTO MEMORY + */ + { + if mload(CONTAINS_RECURSIVE_PROOF_LOC) { + let public_inputs_ptr := add(calldataload(0x24), 0x24) + let index_counter := add(shl(5, mload(RECURSIVE_PROOF_PUBLIC_INPUT_INDICES_LOC)), public_inputs_ptr) + + let x0 := calldataload(index_counter) + x0 := add(x0, shl(68, calldataload(add(index_counter, 0x20)))) + x0 := add(x0, shl(136, calldataload(add(index_counter, 0x40)))) + x0 := add(x0, shl(204, calldataload(add(index_counter, 0x60)))) + let y0 := calldataload(add(index_counter, 0x80)) + y0 := add(y0, shl(68, calldataload(add(index_counter, 0xa0)))) + y0 := add(y0, shl(136, calldataload(add(index_counter, 0xc0)))) + y0 := add(y0, shl(204, calldataload(add(index_counter, 0xe0)))) + let x1 := calldataload(add(index_counter, 0x100)) + x1 := add(x1, shl(68, calldataload(add(index_counter, 0x120)))) + x1 := add(x1, shl(136, calldataload(add(index_counter, 0x140)))) + x1 := add(x1, shl(204, calldataload(add(index_counter, 0x160)))) + let y1 := calldataload(add(index_counter, 0x180)) + y1 := add(y1, shl(68, calldataload(add(index_counter, 0x1a0)))) + y1 := add(y1, shl(136, calldataload(add(index_counter, 0x1c0)))) + y1 := add(y1, shl(204, calldataload(add(index_counter, 0x1e0)))) + mstore(RECURSIVE_P1_X_LOC, x0) + mstore(RECURSIVE_P1_Y_LOC, y0) + mstore(RECURSIVE_P2_X_LOC, x1) + mstore(RECURSIVE_P2_Y_LOC, y1) + + // validate these are valid bn128 G1 points + if iszero(and(and(lt(x0, q), lt(x1, q)), and(lt(y0, q), lt(y1, q)))) { + mstore(0x00, PUBLIC_INPUT_INVALID_BN128_G1_POINT_SELECTOR) + revert(0x00, 0x04) + } + } + } + + { + /** + * Generate initial challenge + */ + mstore(0x00, shl(224, mload(N_LOC))) + mstore(0x04, shl(224, mload(NUM_INPUTS_LOC))) + let challenge := keccak256(0x00, 0x08) + + /** + * Generate eta challenge + */ + mstore(PUBLIC_INPUTS_HASH_LOCATION, challenge) + // The public input location is stored at 0x24, we then add 0x24 to skip selector and the length of public inputs + let public_inputs_start := add(calldataload(0x24), 0x24) + // copy the public inputs over + let public_input_size := mul(mload(NUM_INPUTS_LOC), 0x20) + calldatacopy(add(PUBLIC_INPUTS_HASH_LOCATION, 0x20), public_inputs_start, public_input_size) + + // copy W1, W2, W3 into challenge. Each point is 0x40 bytes, so load 0xc0 = 3 * 0x40 bytes (ETA input length) + let w_start := add(calldataload(0x04), 0x24) + calldatacopy(add(add(PUBLIC_INPUTS_HASH_LOCATION, 0x20), public_input_size), w_start, ETA_INPUT_LENGTH) + + // Challenge is the old challenge + public inputs + W1, W2, W3 (0x20 + public_input_size + 0xc0) + let challenge_bytes_size := add(0x20, add(public_input_size, ETA_INPUT_LENGTH)) + + challenge := keccak256(PUBLIC_INPUTS_HASH_LOCATION, challenge_bytes_size) + { + let eta := mod(challenge, p) + mstore(C_ETA_LOC, eta) + mstore(C_ETA_SQR_LOC, mulmod(eta, eta, p)) + mstore(C_ETA_CUBE_LOC, mulmod(mload(C_ETA_SQR_LOC), eta, p)) + } + + /** + * Generate beta challenge + */ + mstore(0x00, challenge) + mstore(0x20, mload(W4_Y_LOC)) + mstore(0x40, mload(W4_X_LOC)) + mstore(0x60, mload(S_Y_LOC)) + mstore(0x80, mload(S_X_LOC)) + challenge := keccak256(0x00, 0xa0) + mstore(C_BETA_LOC, mod(challenge, p)) + + /** + * Generate gamma challenge + */ + mstore(0x00, challenge) + mstore8(0x20, 0x01) + challenge := keccak256(0x00, 0x21) + mstore(C_GAMMA_LOC, mod(challenge, p)) + + /** + * Generate alpha challenge + */ + mstore(0x00, challenge) + mstore(0x20, mload(Z_Y_LOC)) + mstore(0x40, mload(Z_X_LOC)) + mstore(0x60, mload(Z_LOOKUP_Y_LOC)) + mstore(0x80, mload(Z_LOOKUP_X_LOC)) + challenge := keccak256(0x00, 0xa0) + mstore(C_ALPHA_LOC, mod(challenge, p)) + + /** + * Compute and store some powers of alpha for future computations + */ + let alpha := mload(C_ALPHA_LOC) + mstore(C_ALPHA_SQR_LOC, mulmod(alpha, alpha, p)) + mstore(C_ALPHA_CUBE_LOC, mulmod(mload(C_ALPHA_SQR_LOC), alpha, p)) + mstore(C_ALPHA_QUAD_LOC, mulmod(mload(C_ALPHA_CUBE_LOC), alpha, p)) + mstore(C_ALPHA_BASE_LOC, alpha) + + /** + * Generate zeta challenge + */ + mstore(0x00, challenge) + mstore(0x20, mload(T1_Y_LOC)) + mstore(0x40, mload(T1_X_LOC)) + mstore(0x60, mload(T2_Y_LOC)) + mstore(0x80, mload(T2_X_LOC)) + mstore(0xa0, mload(T3_Y_LOC)) + mstore(0xc0, mload(T3_X_LOC)) + mstore(0xe0, mload(T4_Y_LOC)) + mstore(0x100, mload(T4_X_LOC)) + + challenge := keccak256(0x00, 0x120) + + mstore(C_ZETA_LOC, mod(challenge, p)) + mstore(C_CURRENT_LOC, challenge) + } + + /** + * EVALUATE FIELD OPERATIONS + */ + + /** + * COMPUTE PUBLIC INPUT DELTA + * ΔPI = âˆáµ¢âˆˆâ„“(wáµ¢ + β σ(i) + γ) / âˆáµ¢âˆˆâ„“(wáµ¢ + β σ'(i) + γ) + */ + { + let beta := mload(C_BETA_LOC) // β + let gamma := mload(C_GAMMA_LOC) // γ + let work_root := mload(OMEGA_LOC) // ω + let numerator_value := 1 + let denominator_value := 1 + + let p_clone := p // move p to the front of the stack + let valid_inputs := true + + // Load the starting point of the public inputs (jump over the selector and the length of public inputs [0x24]) + let public_inputs_ptr := add(calldataload(0x24), 0x24) + + // endpoint_ptr = public_inputs_ptr + num_inputs * 0x20. // every public input is 0x20 bytes + let endpoint_ptr := add(public_inputs_ptr, mul(mload(NUM_INPUTS_LOC), 0x20)) + + // root_1 = β * 0x05 + let root_1 := mulmod(beta, 0x05, p_clone) // k1.β + // root_2 = β * 0x0c + let root_2 := mulmod(beta, 0x0c, p_clone) + // @note 0x05 + 0x07 == 0x0c == external coset generator + + for {} lt(public_inputs_ptr, endpoint_ptr) { public_inputs_ptr := add(public_inputs_ptr, 0x20) } { + /** + * input = public_input[i] + * valid_inputs &= input < p + * temp = input + gamma + * numerator_value *= (β.σ(i) + wáµ¢ + γ) // σ(i) = 0x05.ωⱠ+ * denominator_value *= (β.σ'(i) + wáµ¢ + γ) // σ'(i) = 0x0c.ωⱠ+ * root_1 *= ω + * root_2 *= ω + */ + + let input := calldataload(public_inputs_ptr) + valid_inputs := and(valid_inputs, lt(input, p_clone)) + let temp := addmod(input, gamma, p_clone) + + numerator_value := mulmod(numerator_value, add(root_1, temp), p_clone) + denominator_value := mulmod(denominator_value, add(root_2, temp), p_clone) + + root_1 := mulmod(root_1, work_root, p_clone) + root_2 := mulmod(root_2, work_root, p_clone) + } + + // Revert if not all public inputs are field elements (i.e. < p) + if iszero(valid_inputs) { + mstore(0x00, PUBLIC_INPUT_GE_P_SELECTOR) + revert(0x00, 0x04) + } + + mstore(DELTA_NUMERATOR_LOC, numerator_value) + mstore(DELTA_DENOMINATOR_LOC, denominator_value) + } + + /** + * Compute Plookup delta factor [γ(1 + β)]^{n-k} + * k = num roots cut out of Z_H = 4 + */ + { + let delta_base := mulmod(mload(C_GAMMA_LOC), addmod(mload(C_BETA_LOC), 1, p), p) + let delta_numerator := delta_base + { + let exponent := mload(N_LOC) + let count := 1 + for {} lt(count, exponent) { count := add(count, count) } { + delta_numerator := mulmod(delta_numerator, delta_numerator, p) + } + } + mstore(PLOOKUP_DELTA_NUMERATOR_LOC, delta_numerator) + + let delta_denominator := mulmod(delta_base, delta_base, p) + delta_denominator := mulmod(delta_denominator, delta_denominator, p) + mstore(PLOOKUP_DELTA_DENOMINATOR_LOC, delta_denominator) + } + /** + * Compute lagrange poly and vanishing poly fractions + */ + { + /** + * vanishing_numerator = zeta + * ZETA_POW_N = zeta^n + * vanishing_numerator -= 1 + * accumulating_root = omega_inverse + * work_root = p - accumulating_root + * domain_inverse = domain_inverse + * vanishing_denominator = zeta + work_root + * work_root *= accumulating_root + * vanishing_denominator *= (zeta + work_root) + * work_root *= accumulating_root + * vanishing_denominator *= (zeta + work_root) + * vanishing_denominator *= (zeta + (zeta + accumulating_root)) + * work_root = omega + * lagrange_numerator = vanishing_numerator * domain_inverse + * l_start_denominator = zeta - 1 + * accumulating_root = work_root^2 + * l_end_denominator = accumulating_root^2 * work_root * zeta - 1 + * Note: l_end_denominator term contains a term \omega^5 to cut out 5 roots of unity from vanishing poly + */ + + let zeta := mload(C_ZETA_LOC) + + // compute zeta^n, where n is a power of 2 + let vanishing_numerator := zeta + { + // pow_small + let exponent := mload(N_LOC) + let count := 1 + for {} lt(count, exponent) { count := add(count, count) } { + vanishing_numerator := mulmod(vanishing_numerator, vanishing_numerator, p) + } + } + mstore(ZETA_POW_N_LOC, vanishing_numerator) + vanishing_numerator := addmod(vanishing_numerator, sub(p, 1), p) + + let accumulating_root := mload(OMEGA_INVERSE_LOC) + let work_root := sub(p, accumulating_root) + let domain_inverse := mload(DOMAIN_INVERSE_LOC) + + let vanishing_denominator := addmod(zeta, work_root, p) + work_root := mulmod(work_root, accumulating_root, p) + vanishing_denominator := mulmod(vanishing_denominator, addmod(zeta, work_root, p), p) + work_root := mulmod(work_root, accumulating_root, p) + vanishing_denominator := mulmod(vanishing_denominator, addmod(zeta, work_root, p), p) + vanishing_denominator := + mulmod(vanishing_denominator, addmod(zeta, mulmod(work_root, accumulating_root, p), p), p) + + work_root := mload(OMEGA_LOC) + + let lagrange_numerator := mulmod(vanishing_numerator, domain_inverse, p) + let l_start_denominator := addmod(zeta, sub(p, 1), p) + + accumulating_root := mulmod(work_root, work_root, p) + + let l_end_denominator := + addmod( + mulmod(mulmod(mulmod(accumulating_root, accumulating_root, p), work_root, p), zeta, p), sub(p, 1), p + ) + + /** + * Compute inversions using Montgomery's batch inversion trick + */ + let accumulator := mload(DELTA_DENOMINATOR_LOC) + let t0 := accumulator + accumulator := mulmod(accumulator, vanishing_denominator, p) + let t1 := accumulator + accumulator := mulmod(accumulator, vanishing_numerator, p) + let t2 := accumulator + accumulator := mulmod(accumulator, l_start_denominator, p) + let t3 := accumulator + accumulator := mulmod(accumulator, mload(PLOOKUP_DELTA_DENOMINATOR_LOC), p) + let t4 := accumulator + { + mstore(0, 0x20) + mstore(0x20, 0x20) + mstore(0x40, 0x20) + mstore(0x60, mulmod(accumulator, l_end_denominator, p)) + mstore(0x80, sub(p, 2)) + mstore(0xa0, p) + if iszero(staticcall(gas(), 0x05, 0x00, 0xc0, 0x00, 0x20)) { + mstore(0x0, MOD_EXP_FAILURE_SELECTOR) + revert(0x00, 0x04) + } + accumulator := mload(0x00) + } + + t4 := mulmod(accumulator, t4, p) + accumulator := mulmod(accumulator, l_end_denominator, p) + + t3 := mulmod(accumulator, t3, p) + accumulator := mulmod(accumulator, mload(PLOOKUP_DELTA_DENOMINATOR_LOC), p) + + t2 := mulmod(accumulator, t2, p) + accumulator := mulmod(accumulator, l_start_denominator, p) + + t1 := mulmod(accumulator, t1, p) + accumulator := mulmod(accumulator, vanishing_numerator, p) + + t0 := mulmod(accumulator, t0, p) + accumulator := mulmod(accumulator, vanishing_denominator, p) + + accumulator := mulmod(mulmod(accumulator, accumulator, p), mload(DELTA_DENOMINATOR_LOC), p) + + mstore(PUBLIC_INPUT_DELTA_LOC, mulmod(mload(DELTA_NUMERATOR_LOC), accumulator, p)) + mstore(ZERO_POLY_LOC, mulmod(vanishing_numerator, t0, p)) + mstore(ZERO_POLY_INVERSE_LOC, mulmod(vanishing_denominator, t1, p)) + mstore(L_START_LOC, mulmod(lagrange_numerator, t2, p)) + mstore(PLOOKUP_DELTA_LOC, mulmod(mload(PLOOKUP_DELTA_NUMERATOR_LOC), t3, p)) + mstore(L_END_LOC, mulmod(lagrange_numerator, t4, p)) + } + + /** + * UltraPlonk Widget Ordering: + * + * 1. Permutation widget + * 2. Plookup widget + * 3. Arithmetic widget + * 4. Fixed base widget (?) + * 5. GenPermSort widget + * 6. Elliptic widget + * 7. Auxiliary widget + */ + + /** + * COMPUTE PERMUTATION WIDGET EVALUATION + */ + { + let alpha := mload(C_ALPHA_LOC) + let beta := mload(C_BETA_LOC) + let gamma := mload(C_GAMMA_LOC) + + /** + * t1 = (W1 + gamma + beta * ID1) * (W2 + gamma + beta * ID2) + * t2 = (W3 + gamma + beta * ID3) * (W4 + gamma + beta * ID4) + * result = alpha_base * z_eval * t1 * t2 + * t1 = (W1 + gamma + beta * sigma_1_eval) * (W2 + gamma + beta * sigma_2_eval) + * t2 = (W2 + gamma + beta * sigma_3_eval) * (W3 + gamma + beta * sigma_4_eval) + * result -= (alpha_base * z_omega_eval * t1 * t2) + */ + let t1 := + mulmod( + add(add(mload(W1_EVAL_LOC), gamma), mulmod(beta, mload(ID1_EVAL_LOC), p)), + add(add(mload(W2_EVAL_LOC), gamma), mulmod(beta, mload(ID2_EVAL_LOC), p)), + p + ) + let t2 := + mulmod( + add(add(mload(W3_EVAL_LOC), gamma), mulmod(beta, mload(ID3_EVAL_LOC), p)), + add(add(mload(W4_EVAL_LOC), gamma), mulmod(beta, mload(ID4_EVAL_LOC), p)), + p + ) + let result := mulmod(mload(C_ALPHA_BASE_LOC), mulmod(mload(Z_EVAL_LOC), mulmod(t1, t2, p), p), p) + t1 := + mulmod( + add(add(mload(W1_EVAL_LOC), gamma), mulmod(beta, mload(SIGMA1_EVAL_LOC), p)), + add(add(mload(W2_EVAL_LOC), gamma), mulmod(beta, mload(SIGMA2_EVAL_LOC), p)), + p + ) + t2 := + mulmod( + add(add(mload(W3_EVAL_LOC), gamma), mulmod(beta, mload(SIGMA3_EVAL_LOC), p)), + add(add(mload(W4_EVAL_LOC), gamma), mulmod(beta, mload(SIGMA4_EVAL_LOC), p)), + p + ) + result := + addmod( + result, + sub(p, mulmod(mload(C_ALPHA_BASE_LOC), mulmod(mload(Z_OMEGA_EVAL_LOC), mulmod(t1, t2, p), p), p)), + p + ) + + /** + * alpha_base *= alpha + * result += alpha_base . (L_{n-k}(Ê“) . (z(Ê“.ω) - ∆_{PI})) + * alpha_base *= alpha + * result += alpha_base . (L_1(Ê“)(Z(Ê“) - 1)) + * alpha_Base *= alpha + */ + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p)) + result := + addmod( + result, + mulmod( + mload(C_ALPHA_BASE_LOC), + mulmod( + mload(L_END_LOC), + addmod(mload(Z_OMEGA_EVAL_LOC), sub(p, mload(PUBLIC_INPUT_DELTA_LOC)), p), + p + ), + p + ), + p + ) + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p)) + mstore( + PERMUTATION_IDENTITY, + addmod( + result, + mulmod( + mload(C_ALPHA_BASE_LOC), + mulmod(mload(L_START_LOC), addmod(mload(Z_EVAL_LOC), sub(p, 1), p), p), + p + ), + p + ) + ) + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p)) + } + + /** + * COMPUTE PLOOKUP WIDGET EVALUATION + */ + { + /** + * Goal: f = (w1(z) + q2.w1(zω)) + η(w2(z) + qm.w2(zω)) + η²(w3(z) + qc.w_3(zω)) + q3(z).η³ + * f = η.q3(z) + * f += (w3(z) + qc.w_3(zω)) + * f *= η + * f += (w2(z) + qm.w2(zω)) + * f *= η + * f += (w1(z) + q2.w1(zω)) + */ + let f := mulmod(mload(C_ETA_LOC), mload(Q3_EVAL_LOC), p) + f := + addmod(f, addmod(mload(W3_EVAL_LOC), mulmod(mload(QC_EVAL_LOC), mload(W3_OMEGA_EVAL_LOC), p), p), p) + f := mulmod(f, mload(C_ETA_LOC), p) + f := + addmod(f, addmod(mload(W2_EVAL_LOC), mulmod(mload(QM_EVAL_LOC), mload(W2_OMEGA_EVAL_LOC), p), p), p) + f := mulmod(f, mload(C_ETA_LOC), p) + f := + addmod(f, addmod(mload(W1_EVAL_LOC), mulmod(mload(Q2_EVAL_LOC), mload(W1_OMEGA_EVAL_LOC), p), p), p) + + // t(z) = table4(z).η³ + table3(z).η² + table2(z).η + table1(z) + let t := + addmod( + addmod( + addmod( + mulmod(mload(TABLE4_EVAL_LOC), mload(C_ETA_CUBE_LOC), p), + mulmod(mload(TABLE3_EVAL_LOC), mload(C_ETA_SQR_LOC), p), + p + ), + mulmod(mload(TABLE2_EVAL_LOC), mload(C_ETA_LOC), p), + p + ), + mload(TABLE1_EVAL_LOC), + p + ) + + // t(zw) = table4(zw).η³ + table3(zw).η² + table2(zw).η + table1(zw) + let t_omega := + addmod( + addmod( + addmod( + mulmod(mload(TABLE4_OMEGA_EVAL_LOC), mload(C_ETA_CUBE_LOC), p), + mulmod(mload(TABLE3_OMEGA_EVAL_LOC), mload(C_ETA_SQR_LOC), p), + p + ), + mulmod(mload(TABLE2_OMEGA_EVAL_LOC), mload(C_ETA_LOC), p), + p + ), + mload(TABLE1_OMEGA_EVAL_LOC), + p + ) + + /** + * Goal: numerator = (TABLE_TYPE_EVAL * f(z) + γ) * (t(z) + βt(zω) + γ(β + 1)) * (β + 1) + * gamma_beta_constant = γ(β + 1) + * numerator = f * TABLE_TYPE_EVAL + gamma + * temp0 = t(z) + t(zω) * β + gamma_beta_constant + * numerator *= temp0 + * numerator *= (β + 1) + * temp0 = alpha * l_1 + * numerator += temp0 + * numerator *= z_lookup(z) + * numerator -= temp0 + */ + let gamma_beta_constant := mulmod(mload(C_GAMMA_LOC), addmod(mload(C_BETA_LOC), 1, p), p) + let numerator := addmod(mulmod(f, mload(TABLE_TYPE_EVAL_LOC), p), mload(C_GAMMA_LOC), p) + let temp0 := addmod(addmod(t, mulmod(t_omega, mload(C_BETA_LOC), p), p), gamma_beta_constant, p) + numerator := mulmod(numerator, temp0, p) + numerator := mulmod(numerator, addmod(mload(C_BETA_LOC), 1, p), p) + temp0 := mulmod(mload(C_ALPHA_LOC), mload(L_START_LOC), p) + numerator := addmod(numerator, temp0, p) + numerator := mulmod(numerator, mload(Z_LOOKUP_EVAL_LOC), p) + numerator := addmod(numerator, sub(p, temp0), p) + + /** + * Goal: denominator = z_lookup(zω)*[s(z) + βs(zω) + γ(1 + β)] - [z_lookup(zω) - [γ(1 + β)]^{n-k}]*α²L_end(z) + * note: delta_factor = [γ(1 + β)]^{n-k} + * denominator = s(z) + βs(zω) + γ(β + 1) + * temp1 = α²L_end(z) + * denominator -= temp1 + * denominator *= z_lookup(zω) + * denominator += temp1 * delta_factor + * PLOOKUP_IDENTITY = (numerator - denominator).alpha_base + * alpha_base *= alpha^3 + */ + let denominator := + addmod( + addmod(mload(S_EVAL_LOC), mulmod(mload(S_OMEGA_EVAL_LOC), mload(C_BETA_LOC), p), p), + gamma_beta_constant, + p + ) + let temp1 := mulmod(mload(C_ALPHA_SQR_LOC), mload(L_END_LOC), p) + denominator := addmod(denominator, sub(p, temp1), p) + denominator := mulmod(denominator, mload(Z_LOOKUP_OMEGA_EVAL_LOC), p) + denominator := addmod(denominator, mulmod(temp1, mload(PLOOKUP_DELTA_LOC), p), p) + + mstore(PLOOKUP_IDENTITY, mulmod(addmod(numerator, sub(p, denominator), p), mload(C_ALPHA_BASE_LOC), p)) + + // update alpha + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_CUBE_LOC), p)) + } + + /** + * COMPUTE ARITHMETIC WIDGET EVALUATION + */ + { + /** + * The basic arithmetic gate identity in standard plonk is as follows. + * (w_1 . w_2 . q_m) + (w_1 . q_1) + (w_2 . q_2) + (w_3 . q_3) + (w_4 . q_4) + q_c = 0 + * However, for Ultraplonk, we extend this to support "passing" wires between rows (shown without alpha scaling below): + * q_arith * ( ( (-1/2) * (q_arith - 3) * q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c ) + + * (q_arith - 1)*( α * (q_arith - 2) * (w_1 + w_4 - w_1_omega + q_m) + w_4_omega) ) = 0 + * + * This formula results in several cases depending on q_arith: + * 1. q_arith == 0: Arithmetic gate is completely disabled + * + * 2. q_arith == 1: Everything in the minigate on the right is disabled. The equation is just a standard plonk equation + * with extra wires: q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c = 0 + * + * 3. q_arith == 2: The (w_1 + w_4 - ...) term is disabled. THe equation is: + * (1/2) * q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + w_4_omega = 0 + * It allows defining w_4 at next index (w_4_omega) in terms of current wire values + * + * 4. q_arith == 3: The product of w_1 and w_2 is disabled, but a mini addition gate is enabled. α allows us to split + * the equation into two: + * + * q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + 2 * w_4_omega = 0 + * and + * w_1 + w_4 - w_1_omega + q_m = 0 (we are reusing q_m here) + * + * 5. q_arith > 3: The product of w_1 and w_2 is scaled by (q_arith - 3), while the w_4_omega term is scaled by (q_arith - 1). + * The equation can be split into two: + * + * (q_arith - 3)* q_m * w_1 * w_ 2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + (q_arith - 1) * w_4_omega = 0 + * and + * w_1 + w_4 - w_1_omega + q_m = 0 + * + * The problem that q_m is used both in both equations can be dealt with by appropriately changing selector values at + * the next gate. Then we can treat (q_arith - 1) as a simulated q_6 selector and scale q_m to handle (q_arith - 3) at + * product. + */ + + let w1q1 := mulmod(mload(W1_EVAL_LOC), mload(Q1_EVAL_LOC), p) + let w2q2 := mulmod(mload(W2_EVAL_LOC), mload(Q2_EVAL_LOC), p) + let w3q3 := mulmod(mload(W3_EVAL_LOC), mload(Q3_EVAL_LOC), p) + let w4q3 := mulmod(mload(W4_EVAL_LOC), mload(Q4_EVAL_LOC), p) + + // @todo - Add a explicit test that hits QARITH == 3 + // w1w2qm := (w_1 . w_2 . q_m . (QARITH_EVAL_LOC - 3)) / 2 + let w1w2qm := + mulmod( + mulmod( + mulmod(mulmod(mload(W1_EVAL_LOC), mload(W2_EVAL_LOC), p), mload(QM_EVAL_LOC), p), + addmod(mload(QARITH_EVAL_LOC), sub(p, 3), p), + p + ), + NEGATIVE_INVERSE_OF_2_MODULO_P, + p + ) + + // (w_1 . w_2 . q_m . (q_arith - 3)) / -2) + (w_1 . q_1) + (w_2 . q_2) + (w_3 . q_3) + (w_4 . q_4) + q_c + let identity := + addmod( + mload(QC_EVAL_LOC), addmod(w4q3, addmod(w3q3, addmod(w2q2, addmod(w1q1, w1w2qm, p), p), p), p), p + ) + + // if q_arith == 3 we evaluate an additional mini addition gate (on top of the regular one), where: + // w_1 + w_4 - w_1_omega + q_m = 0 + // we use this gate to save an addition gate when adding or subtracting non-native field elements + // α * (q_arith - 2) * (w_1 + w_4 - w_1_omega + q_m) + let extra_small_addition_gate_identity := + mulmod( + mload(C_ALPHA_LOC), + mulmod( + addmod(mload(QARITH_EVAL_LOC), sub(p, 2), p), + addmod( + mload(QM_EVAL_LOC), + addmod( + sub(p, mload(W1_OMEGA_EVAL_LOC)), addmod(mload(W1_EVAL_LOC), mload(W4_EVAL_LOC), p), p + ), + p + ), + p + ), + p + ) + + // if q_arith == 2 OR q_arith == 3 we add the 4th wire of the NEXT gate into the arithmetic identity + // N.B. if q_arith > 2, this wire value will be scaled by (q_arith - 1) relative to the other gate wires! + // alpha_base * q_arith * (identity + (q_arith - 1) * (w_4_omega + extra_small_addition_gate_identity)) + mstore( + ARITHMETIC_IDENTITY, + mulmod( + mload(C_ALPHA_BASE_LOC), + mulmod( + mload(QARITH_EVAL_LOC), + addmod( + identity, + mulmod( + addmod(mload(QARITH_EVAL_LOC), sub(p, 1), p), + addmod(mload(W4_OMEGA_EVAL_LOC), extra_small_addition_gate_identity, p), + p + ), + p + ), + p + ), + p + ) + ) + + // update alpha + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_SQR_LOC), p)) + } + + /** + * COMPUTE GENPERMSORT WIDGET EVALUATION + */ + { + /** + * D1 = (w2 - w1) + * D2 = (w3 - w2) + * D3 = (w4 - w3) + * D4 = (w1_omega - w4) + * + * α_a = alpha_base + * α_b = alpha_base * α + * α_c = alpha_base * α^2 + * α_d = alpha_base * α^3 + * + * range_accumulator = ( + * D1(D1 - 1)(D1 - 2)(D1 - 3).α_a + + * D2(D2 - 1)(D2 - 2)(D2 - 3).α_b + + * D3(D3 - 1)(D3 - 2)(D3 - 3).α_c + + * D4(D4 - 1)(D4 - 2)(D4 - 3).α_d + + * ) . q_sort + */ + let minus_two := sub(p, 2) + let minus_three := sub(p, 3) + let d1 := addmod(mload(W2_EVAL_LOC), sub(p, mload(W1_EVAL_LOC)), p) + let d2 := addmod(mload(W3_EVAL_LOC), sub(p, mload(W2_EVAL_LOC)), p) + let d3 := addmod(mload(W4_EVAL_LOC), sub(p, mload(W3_EVAL_LOC)), p) + let d4 := addmod(mload(W1_OMEGA_EVAL_LOC), sub(p, mload(W4_EVAL_LOC)), p) + + let range_accumulator := + mulmod( + mulmod( + mulmod(addmod(mulmod(d1, d1, p), sub(p, d1), p), addmod(d1, minus_two, p), p), + addmod(d1, minus_three, p), + p + ), + mload(C_ALPHA_BASE_LOC), + p + ) + range_accumulator := + addmod( + range_accumulator, + mulmod( + mulmod( + mulmod(addmod(mulmod(d2, d2, p), sub(p, d2), p), addmod(d2, minus_two, p), p), + addmod(d2, minus_three, p), + p + ), + mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p), + p + ), + p + ) + range_accumulator := + addmod( + range_accumulator, + mulmod( + mulmod( + mulmod(addmod(mulmod(d3, d3, p), sub(p, d3), p), addmod(d3, minus_two, p), p), + addmod(d3, minus_three, p), + p + ), + mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_SQR_LOC), p), + p + ), + p + ) + range_accumulator := + addmod( + range_accumulator, + mulmod( + mulmod( + mulmod(addmod(mulmod(d4, d4, p), sub(p, d4), p), addmod(d4, minus_two, p), p), + addmod(d4, minus_three, p), + p + ), + mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_CUBE_LOC), p), + p + ), + p + ) + range_accumulator := mulmod(range_accumulator, mload(QSORT_EVAL_LOC), p) + + mstore(SORT_IDENTITY, range_accumulator) + + // update alpha + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_QUAD_LOC), p)) + } + + /** + * COMPUTE ELLIPTIC WIDGET EVALUATION + */ + { + /** + * endo_term = (-x_2) * x_1 * (x_3 * 2 + x_1) * q_beta + * endo_sqr_term = x_2^2 + * endo_sqr_term *= (x_3 - x_1) + * endo_sqr_term *= q_beta^2 + * leftovers = x_2^2 + * leftovers *= x_2 + * leftovers += x_1^2 * (x_3 + x_1) @follow-up Invalid comment in BB widget + * leftovers -= (y_2^2 + y_1^2) + * sign_term = y_2 * y_1 + * sign_term += sign_term + * sign_term *= q_sign + */ + + let endo_term := + mulmod( + mulmod( + mulmod(sub(p, mload(X2_EVAL_LOC)), mload(X1_EVAL_LOC), p), + addmod(addmod(mload(X3_EVAL_LOC), mload(X3_EVAL_LOC), p), mload(X1_EVAL_LOC), p), + p + ), + mload(QBETA_LOC), + p + ) + + let endo_sqr_term := mulmod(mload(X2_EVAL_LOC), mload(X2_EVAL_LOC), p) + endo_sqr_term := mulmod(endo_sqr_term, addmod(mload(X3_EVAL_LOC), sub(p, mload(X1_EVAL_LOC)), p), p) + endo_sqr_term := mulmod(endo_sqr_term, mload(QBETA_SQR_LOC), p) + + let leftovers := mulmod(mload(X2_EVAL_LOC), mload(X2_EVAL_LOC), p) + leftovers := mulmod(leftovers, mload(X2_EVAL_LOC), p) + leftovers := + addmod( + leftovers, + mulmod( + mulmod(mload(X1_EVAL_LOC), mload(X1_EVAL_LOC), p), + addmod(mload(X3_EVAL_LOC), mload(X1_EVAL_LOC), p), + p + ), + p + ) + leftovers := + addmod( + leftovers, + sub( + p, + addmod( + mulmod(mload(Y2_EVAL_LOC), mload(Y2_EVAL_LOC), p), + mulmod(mload(Y1_EVAL_LOC), mload(Y1_EVAL_LOC), p), + p + ) + ), + p + ) + + let sign_term := mulmod(mload(Y2_EVAL_LOC), mload(Y1_EVAL_LOC), p) + sign_term := addmod(sign_term, sign_term, p) + sign_term := mulmod(sign_term, mload(QSIGN_LOC), p) + + /** + * x_identity = endo_term + endo_sqr_term + sign_term + leftovers + * x_identity *= alpha_base + * endo_term = (x_2 * q_beta) * (y_3 + y_1) + * sign_term = -((y2 * q_sign) * (x_1 + x_3)) + * leftovers = - x1 * (y_3 + y_1) + y_1 * (x_1 - x_3) + * y_identity = (endo_term + sign_term + leftovers) * (alpha_base * α) + */ + + let x_identity := addmod(addmod(endo_term, endo_sqr_term, p), addmod(sign_term, leftovers, p), p) + x_identity := mulmod(x_identity, mload(C_ALPHA_BASE_LOC), p) + endo_term := + mulmod( + mulmod(mload(X2_EVAL_LOC), mload(QBETA_LOC), p), + addmod(mload(Y3_EVAL_LOC), mload(Y1_EVAL_LOC), p), + p + ) + sign_term := + sub( + p, + mulmod( + mulmod(mload(Y2_EVAL_LOC), mload(QSIGN_LOC), p), + addmod(mload(X1_EVAL_LOC), sub(p, mload(X3_EVAL_LOC)), p), + p + ) + ) + leftovers := + addmod( + sub(p, mulmod(mload(X1_EVAL_LOC), addmod(mload(Y3_EVAL_LOC), mload(Y1_EVAL_LOC), p), p)), + mulmod(mload(Y1_EVAL_LOC), addmod(mload(X1_EVAL_LOC), sub(p, mload(X3_EVAL_LOC)), p), p), + p + ) + let y_identity := + mulmod( + addmod(addmod(endo_term, sign_term, p), leftovers, p), + mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p), + p + ) + + // ELLIPTIC_IDENTITY = (x_identity + y_identity) * Q_ELLIPTIC_EVAL + mstore(ELLIPTIC_IDENTITY, mulmod(addmod(x_identity, y_identity, p), mload(QELLIPTIC_EVAL_LOC), p)) + + // update alpha + // The paper says to use ALPHA^2, we use ALPHA^4 this is a small oversight in the prover protocol + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_QUAD_LOC), p)) + } + + /** + * COMPUTE AUXILIARY WIDGET EVALUATION + */ + { + { + /** + * Non native field arithmetic gate 2 + * _ _ + * / _ _ _ 14 \ + * q_2 . q_4 | (w_1 . w_2) + (w_1 . w_2) + (w_1 . w_4 + w_2 . w_3 - w_3) . 2 - w_3 - w_4 | + * \_ _/ + * + * limb_subproduct = w_1 . w_2_omega + w_1_omega . w_2 + * non_native_field_gate_2 = w_1 * w_4 + w_4 * w_3 - w_3_omega + * non_native_field_gate_2 = non_native_field_gate_2 * limb_size + * non_native_field_gate_2 -= w_4_omega + * non_native_field_gate_2 += limb_subproduct + * non_native_field_gate_2 *= q_4 + * limb_subproduct *= limb_size + * limb_subproduct += w_1_omega * w_2_omega + * non_native_field_gate_1 = (limb_subproduct + w_3 + w_4) * q_3 + * non_native_field_gate_3 = (limb_subproduct + w_4 - (w_3_omega + w_4_omega)) * q_m + * non_native_field_identity = (non_native_field_gate_1 + non_native_field_gate_2 + non_native_field_gate_3) * q_2 + */ + + let limb_subproduct := + addmod( + mulmod(mload(W1_EVAL_LOC), mload(W2_OMEGA_EVAL_LOC), p), + mulmod(mload(W1_OMEGA_EVAL_LOC), mload(W2_EVAL_LOC), p), + p + ) + + let non_native_field_gate_2 := + addmod( + addmod( + mulmod(mload(W1_EVAL_LOC), mload(W4_EVAL_LOC), p), + mulmod(mload(W2_EVAL_LOC), mload(W3_EVAL_LOC), p), + p + ), + sub(p, mload(W3_OMEGA_EVAL_LOC)), + p + ) + non_native_field_gate_2 := mulmod(non_native_field_gate_2, LIMB_SIZE, p) + non_native_field_gate_2 := addmod(non_native_field_gate_2, sub(p, mload(W4_OMEGA_EVAL_LOC)), p) + non_native_field_gate_2 := addmod(non_native_field_gate_2, limb_subproduct, p) + non_native_field_gate_2 := mulmod(non_native_field_gate_2, mload(Q4_EVAL_LOC), p) + limb_subproduct := mulmod(limb_subproduct, LIMB_SIZE, p) + limb_subproduct := + addmod(limb_subproduct, mulmod(mload(W1_OMEGA_EVAL_LOC), mload(W2_OMEGA_EVAL_LOC), p), p) + let non_native_field_gate_1 := + mulmod( + addmod(limb_subproduct, sub(p, addmod(mload(W3_EVAL_LOC), mload(W4_EVAL_LOC), p)), p), + mload(Q3_EVAL_LOC), + p + ) + let non_native_field_gate_3 := + mulmod( + addmod( + addmod(limb_subproduct, mload(W4_EVAL_LOC), p), + sub(p, addmod(mload(W3_OMEGA_EVAL_LOC), mload(W4_OMEGA_EVAL_LOC), p)), + p + ), + mload(QM_EVAL_LOC), + p + ) + let non_native_field_identity := + mulmod( + addmod(addmod(non_native_field_gate_1, non_native_field_gate_2, p), non_native_field_gate_3, p), + mload(Q2_EVAL_LOC), + p + ) + + mstore(AUX_NON_NATIVE_FIELD_EVALUATION, non_native_field_identity) + } + + { + /** + * limb_accumulator_1 = w_2_omega; + * limb_accumulator_1 *= SUBLIMB_SHIFT; + * limb_accumulator_1 += w_1_omega; + * limb_accumulator_1 *= SUBLIMB_SHIFT; + * limb_accumulator_1 += w_3; + * limb_accumulator_1 *= SUBLIMB_SHIFT; + * limb_accumulator_1 += w_2; + * limb_accumulator_1 *= SUBLIMB_SHIFT; + * limb_accumulator_1 += w_1; + * limb_accumulator_1 -= w_4; + * limb_accumulator_1 *= q_4; + */ + let limb_accumulator_1 := mulmod(mload(W2_OMEGA_EVAL_LOC), SUBLIMB_SHIFT, p) + limb_accumulator_1 := addmod(limb_accumulator_1, mload(W1_OMEGA_EVAL_LOC), p) + limb_accumulator_1 := mulmod(limb_accumulator_1, SUBLIMB_SHIFT, p) + limb_accumulator_1 := addmod(limb_accumulator_1, mload(W3_EVAL_LOC), p) + limb_accumulator_1 := mulmod(limb_accumulator_1, SUBLIMB_SHIFT, p) + limb_accumulator_1 := addmod(limb_accumulator_1, mload(W2_EVAL_LOC), p) + limb_accumulator_1 := mulmod(limb_accumulator_1, SUBLIMB_SHIFT, p) + limb_accumulator_1 := addmod(limb_accumulator_1, mload(W1_EVAL_LOC), p) + limb_accumulator_1 := addmod(limb_accumulator_1, sub(p, mload(W4_EVAL_LOC)), p) + limb_accumulator_1 := mulmod(limb_accumulator_1, mload(Q4_EVAL_LOC), p) + + /** + * limb_accumulator_2 = w_3_omega; + * limb_accumulator_2 *= SUBLIMB_SHIFT; + * limb_accumulator_2 += w_2_omega; + * limb_accumulator_2 *= SUBLIMB_SHIFT; + * limb_accumulator_2 += w_1_omega; + * limb_accumulator_2 *= SUBLIMB_SHIFT; + * limb_accumulator_2 += w_4; + * limb_accumulator_2 *= SUBLIMB_SHIFT; + * limb_accumulator_2 += w_3; + * limb_accumulator_2 -= w_4_omega; + * limb_accumulator_2 *= q_m; + */ + let limb_accumulator_2 := mulmod(mload(W3_OMEGA_EVAL_LOC), SUBLIMB_SHIFT, p) + limb_accumulator_2 := addmod(limb_accumulator_2, mload(W2_OMEGA_EVAL_LOC), p) + limb_accumulator_2 := mulmod(limb_accumulator_2, SUBLIMB_SHIFT, p) + limb_accumulator_2 := addmod(limb_accumulator_2, mload(W1_OMEGA_EVAL_LOC), p) + limb_accumulator_2 := mulmod(limb_accumulator_2, SUBLIMB_SHIFT, p) + limb_accumulator_2 := addmod(limb_accumulator_2, mload(W4_EVAL_LOC), p) + limb_accumulator_2 := mulmod(limb_accumulator_2, SUBLIMB_SHIFT, p) + limb_accumulator_2 := addmod(limb_accumulator_2, mload(W3_EVAL_LOC), p) + limb_accumulator_2 := addmod(limb_accumulator_2, sub(p, mload(W4_OMEGA_EVAL_LOC)), p) + limb_accumulator_2 := mulmod(limb_accumulator_2, mload(QM_EVAL_LOC), p) + + mstore( + AUX_LIMB_ACCUMULATOR_EVALUATION, + mulmod(addmod(limb_accumulator_1, limb_accumulator_2, p), mload(Q3_EVAL_LOC), p) + ) + } + + { + /** + * memory_record_check = w_3; + * memory_record_check *= eta; + * memory_record_check += w_2; + * memory_record_check *= eta; + * memory_record_check += w_1; + * memory_record_check *= eta; + * memory_record_check += q_c; + * + * partial_record_check = memory_record_check; + * + * memory_record_check -= w_4; + */ + + let memory_record_check := mulmod(mload(W3_EVAL_LOC), mload(C_ETA_LOC), p) + memory_record_check := addmod(memory_record_check, mload(W2_EVAL_LOC), p) + memory_record_check := mulmod(memory_record_check, mload(C_ETA_LOC), p) + memory_record_check := addmod(memory_record_check, mload(W1_EVAL_LOC), p) + memory_record_check := mulmod(memory_record_check, mload(C_ETA_LOC), p) + memory_record_check := addmod(memory_record_check, mload(QC_EVAL_LOC), p) + + let partial_record_check := memory_record_check + memory_record_check := addmod(memory_record_check, sub(p, mload(W4_EVAL_LOC)), p) + + mstore(AUX_MEMORY_EVALUATION, memory_record_check) + + // index_delta = w_1_omega - w_1 + let index_delta := addmod(mload(W1_OMEGA_EVAL_LOC), sub(p, mload(W1_EVAL_LOC)), p) + // record_delta = w_4_omega - w_4 + let record_delta := addmod(mload(W4_OMEGA_EVAL_LOC), sub(p, mload(W4_EVAL_LOC)), p) + // index_is_monotonically_increasing = index_delta * (index_delta - 1) + let index_is_monotonically_increasing := mulmod(index_delta, addmod(index_delta, sub(p, 1), p), p) + + // adjacent_values_match_if_adjacent_indices_match = record_delta * (1 - index_delta) + let adjacent_values_match_if_adjacent_indices_match := + mulmod(record_delta, addmod(1, sub(p, index_delta), p), p) + + // AUX_ROM_CONSISTENCY_EVALUATION = ((adjacent_values_match_if_adjacent_indices_match * alpha) + index_is_monotonically_increasing) * alpha + partial_record_check + mstore( + AUX_ROM_CONSISTENCY_EVALUATION, + addmod( + mulmod( + addmod( + mulmod(adjacent_values_match_if_adjacent_indices_match, mload(C_ALPHA_LOC), p), + index_is_monotonically_increasing, + p + ), + mload(C_ALPHA_LOC), + p + ), + memory_record_check, + p + ) + ) + + { + /** + * next_gate_access_type = w_3_omega; + * next_gate_access_type *= eta; + * next_gate_access_type += w_2_omega; + * next_gate_access_type *= eta; + * next_gate_access_type += w_1_omega; + * next_gate_access_type *= eta; + * next_gate_access_type = w_4_omega - next_gate_access_type; + */ + let next_gate_access_type := mulmod(mload(W3_OMEGA_EVAL_LOC), mload(C_ETA_LOC), p) + next_gate_access_type := addmod(next_gate_access_type, mload(W2_OMEGA_EVAL_LOC), p) + next_gate_access_type := mulmod(next_gate_access_type, mload(C_ETA_LOC), p) + next_gate_access_type := addmod(next_gate_access_type, mload(W1_OMEGA_EVAL_LOC), p) + next_gate_access_type := mulmod(next_gate_access_type, mload(C_ETA_LOC), p) + next_gate_access_type := addmod(mload(W4_OMEGA_EVAL_LOC), sub(p, next_gate_access_type), p) + + // value_delta = w_3_omega - w_3 + let value_delta := addmod(mload(W3_OMEGA_EVAL_LOC), sub(p, mload(W3_EVAL_LOC)), p) + // adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation = (1 - index_delta) * value_delta * (1 - next_gate_access_type); + + let adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation := + mulmod( + addmod(1, sub(p, index_delta), p), + mulmod(value_delta, addmod(1, sub(p, next_gate_access_type), p), p), + p + ) + + // AUX_RAM_CONSISTENCY_EVALUATION + + /** + * access_type = w_4 - partial_record_check + * access_check = access_type^2 - access_type + * next_gate_access_type_is_boolean = next_gate_access_type^2 - next_gate_access_type + * RAM_consistency_check_identity = adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation; + * RAM_consistency_check_identity *= alpha; + * RAM_consistency_check_identity += index_is_monotonically_increasing; + * RAM_consistency_check_identity *= alpha; + * RAM_consistency_check_identity += next_gate_access_type_is_boolean; + * RAM_consistency_check_identity *= alpha; + * RAM_consistency_check_identity += access_check; + */ + + let access_type := addmod(mload(W4_EVAL_LOC), sub(p, partial_record_check), p) + let access_check := mulmod(access_type, addmod(access_type, sub(p, 1), p), p) + let next_gate_access_type_is_boolean := + mulmod(next_gate_access_type, addmod(next_gate_access_type, sub(p, 1), p), p) + let RAM_cci := + mulmod( + adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation, + mload(C_ALPHA_LOC), + p + ) + RAM_cci := addmod(RAM_cci, index_is_monotonically_increasing, p) + RAM_cci := mulmod(RAM_cci, mload(C_ALPHA_LOC), p) + RAM_cci := addmod(RAM_cci, next_gate_access_type_is_boolean, p) + RAM_cci := mulmod(RAM_cci, mload(C_ALPHA_LOC), p) + RAM_cci := addmod(RAM_cci, access_check, p) + + mstore(AUX_RAM_CONSISTENCY_EVALUATION, RAM_cci) + } + + { + // timestamp_delta = w_2_omega - w_2 + let timestamp_delta := addmod(mload(W2_OMEGA_EVAL_LOC), sub(p, mload(W2_EVAL_LOC)), p) + + // RAM_timestamp_check_identity = (1 - index_delta) * timestamp_delta - w_3 + let RAM_timestamp_check_identity := + addmod( + mulmod(timestamp_delta, addmod(1, sub(p, index_delta), p), p), sub(p, mload(W3_EVAL_LOC)), p + ) + + /** + * memory_identity = ROM_consistency_check_identity * q_2; + * memory_identity += RAM_timestamp_check_identity * q_4; + * memory_identity += memory_record_check * q_m; + * memory_identity *= q_1; + * memory_identity += (RAM_consistency_check_identity * q_arith); + * + * auxiliary_identity = memory_identity + non_native_field_identity + limb_accumulator_identity; + * auxiliary_identity *= q_aux; + * auxiliary_identity *= alpha_base; + */ + let memory_identity := mulmod(mload(AUX_ROM_CONSISTENCY_EVALUATION), mload(Q2_EVAL_LOC), p) + memory_identity := + addmod(memory_identity, mulmod(RAM_timestamp_check_identity, mload(Q4_EVAL_LOC), p), p) + memory_identity := + addmod(memory_identity, mulmod(mload(AUX_MEMORY_EVALUATION), mload(QM_EVAL_LOC), p), p) + memory_identity := mulmod(memory_identity, mload(Q1_EVAL_LOC), p) + memory_identity := + addmod( + memory_identity, mulmod(mload(AUX_RAM_CONSISTENCY_EVALUATION), mload(QARITH_EVAL_LOC), p), p + ) + + let auxiliary_identity := addmod(memory_identity, mload(AUX_NON_NATIVE_FIELD_EVALUATION), p) + auxiliary_identity := addmod(auxiliary_identity, mload(AUX_LIMB_ACCUMULATOR_EVALUATION), p) + auxiliary_identity := mulmod(auxiliary_identity, mload(QAUX_EVAL_LOC), p) + auxiliary_identity := mulmod(auxiliary_identity, mload(C_ALPHA_BASE_LOC), p) + + mstore(AUX_IDENTITY, auxiliary_identity) + + // update alpha + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_CUBE_LOC), p)) + } + } + } + + { + /** + * quotient = ARITHMETIC_IDENTITY + * quotient += PERMUTATION_IDENTITY + * quotient += PLOOKUP_IDENTITY + * quotient += SORT_IDENTITY + * quotient += ELLIPTIC_IDENTITY + * quotient += AUX_IDENTITY + * quotient *= ZERO_POLY_INVERSE + */ + mstore( + QUOTIENT_EVAL_LOC, + mulmod( + addmod( + addmod( + addmod( + addmod( + addmod(mload(PERMUTATION_IDENTITY), mload(PLOOKUP_IDENTITY), p), + mload(ARITHMETIC_IDENTITY), + p + ), + mload(SORT_IDENTITY), + p + ), + mload(ELLIPTIC_IDENTITY), + p + ), + mload(AUX_IDENTITY), + p + ), + mload(ZERO_POLY_INVERSE_LOC), + p + ) + ) + } + + /** + * GENERATE NU AND SEPARATOR CHALLENGES + */ + { + let current_challenge := mload(C_CURRENT_LOC) + // get a calldata pointer that points to the start of the data we want to copy + let calldata_ptr := add(calldataload(0x04), 0x24) + + calldata_ptr := add(calldata_ptr, NU_CALLDATA_SKIP_LENGTH) + + mstore(NU_CHALLENGE_INPUT_LOC_A, current_challenge) + mstore(NU_CHALLENGE_INPUT_LOC_B, mload(QUOTIENT_EVAL_LOC)) + calldatacopy(NU_CHALLENGE_INPUT_LOC_C, calldata_ptr, NU_INPUT_LENGTH) + + // hash length = (0x20 + num field elements), we include the previous challenge in the hash + let challenge := keccak256(NU_CHALLENGE_INPUT_LOC_A, add(NU_INPUT_LENGTH, 0x40)) + + mstore(C_V0_LOC, mod(challenge, p)) + // We need THIRTY-ONE independent nu challenges! + mstore(0x00, challenge) + mstore8(0x20, 0x01) + mstore(C_V1_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x02) + mstore(C_V2_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x03) + mstore(C_V3_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x04) + mstore(C_V4_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x05) + mstore(C_V5_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x06) + mstore(C_V6_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x07) + mstore(C_V7_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x08) + mstore(C_V8_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x09) + mstore(C_V9_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x0a) + mstore(C_V10_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x0b) + mstore(C_V11_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x0c) + mstore(C_V12_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x0d) + mstore(C_V13_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x0e) + mstore(C_V14_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x0f) + mstore(C_V15_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x10) + mstore(C_V16_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x11) + mstore(C_V17_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x12) + mstore(C_V18_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x13) + mstore(C_V19_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x14) + mstore(C_V20_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x15) + mstore(C_V21_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x16) + mstore(C_V22_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x17) + mstore(C_V23_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x18) + mstore(C_V24_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x19) + mstore(C_V25_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x1a) + mstore(C_V26_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x1b) + mstore(C_V27_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x1c) + mstore(C_V28_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x1d) + mstore(C_V29_LOC, mod(keccak256(0x00, 0x21), p)) + + // @follow-up - Why are both v29 and v30 using appending 0x1d to the prior challenge and hashing, should it not change? + mstore8(0x20, 0x1d) + challenge := keccak256(0x00, 0x21) + mstore(C_V30_LOC, mod(challenge, p)) + + // separator + mstore(0x00, challenge) + mstore(0x20, mload(PI_Z_Y_LOC)) + mstore(0x40, mload(PI_Z_X_LOC)) + mstore(0x60, mload(PI_Z_OMEGA_Y_LOC)) + mstore(0x80, mload(PI_Z_OMEGA_X_LOC)) + + mstore(C_U_LOC, mod(keccak256(0x00, 0xa0), p)) + } + + let success := 0 + // VALIDATE T1 + { + let x := mload(T1_X_LOC) + let y := mload(T1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)) + mstore(ACCUMULATOR_X_LOC, x) + mstore(add(ACCUMULATOR_X_LOC, 0x20), y) + } + // VALIDATE T2 + { + let x := mload(T2_X_LOC) // 0x1400 + let y := mload(T2_Y_LOC) // 0x1420 + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(ZETA_POW_N_LOC)) + // accumulator_2 = [T2].zeta^n + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = [T1] + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE T3 + { + let x := mload(T3_X_LOC) + let y := mload(T3_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(mload(ZETA_POW_N_LOC), mload(ZETA_POW_N_LOC), p)) + // accumulator_2 = [T3].zeta^{2n} + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE T4 + { + let x := mload(T4_X_LOC) + let y := mload(T4_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(mulmod(mload(ZETA_POW_N_LOC), mload(ZETA_POW_N_LOC), p), mload(ZETA_POW_N_LOC), p)) + // accumulator_2 = [T4].zeta^{3n} + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE W1 + { + let x := mload(W1_X_LOC) + let y := mload(W1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V0_LOC), p)) + // accumulator_2 = v0.(u + 1).[W1] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE W2 + { + let x := mload(W2_X_LOC) + let y := mload(W2_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V1_LOC), p)) + // accumulator_2 = v1.(u + 1).[W2] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE W3 + { + let x := mload(W3_X_LOC) + let y := mload(W3_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V2_LOC), p)) + // accumulator_2 = v2.(u + 1).[W3] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE W4 + { + let x := mload(W4_X_LOC) + let y := mload(W4_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V3_LOC), p)) + // accumulator_2 = v3.(u + 1).[W4] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE S + { + let x := mload(S_X_LOC) + let y := mload(S_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V4_LOC), p)) + // accumulator_2 = v4.(u + 1).[S] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE Z + { + let x := mload(Z_X_LOC) + let y := mload(Z_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V5_LOC), p)) + // accumulator_2 = v5.(u + 1).[Z] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE Z_LOOKUP + { + let x := mload(Z_LOOKUP_X_LOC) + let y := mload(Z_LOOKUP_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V6_LOC), p)) + // accumulator_2 = v6.(u + 1).[Z_LOOKUP] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE Q1 + { + let x := mload(Q1_X_LOC) + let y := mload(Q1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V7_LOC)) + // accumulator_2 = v7.[Q1] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE Q2 + { + let x := mload(Q2_X_LOC) + let y := mload(Q2_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V8_LOC)) + // accumulator_2 = v8.[Q2] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE Q3 + { + let x := mload(Q3_X_LOC) + let y := mload(Q3_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V9_LOC)) + // accumulator_2 = v9.[Q3] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE Q4 + { + let x := mload(Q4_X_LOC) + let y := mload(Q4_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V10_LOC)) + // accumulator_2 = v10.[Q4] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE QM + { + let x := mload(QM_X_LOC) + let y := mload(QM_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V11_LOC)) + // accumulator_2 = v11.[Q;] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE QC + { + let x := mload(QC_X_LOC) + let y := mload(QC_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V12_LOC)) + // accumulator_2 = v12.[QC] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE QARITH + { + let x := mload(QARITH_X_LOC) + let y := mload(QARITH_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V13_LOC)) + // accumulator_2 = v13.[QARITH] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE QSORT + { + let x := mload(QSORT_X_LOC) + let y := mload(QSORT_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V14_LOC)) + // accumulator_2 = v14.[QSORT] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE QELLIPTIC + { + let x := mload(QELLIPTIC_X_LOC) + let y := mload(QELLIPTIC_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V15_LOC)) + // accumulator_2 = v15.[QELLIPTIC] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE QAUX + { + let x := mload(QAUX_X_LOC) + let y := mload(QAUX_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V16_LOC)) + // accumulator_2 = v15.[Q_AUX] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE SIGMA1 + { + let x := mload(SIGMA1_X_LOC) + let y := mload(SIGMA1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V17_LOC)) + // accumulator_2 = v17.[sigma1] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE SIGMA2 + { + let x := mload(SIGMA2_X_LOC) + let y := mload(SIGMA2_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V18_LOC)) + // accumulator_2 = v18.[sigma2] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE SIGMA3 + { + let x := mload(SIGMA3_X_LOC) + let y := mload(SIGMA3_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V19_LOC)) + // accumulator_2 = v19.[sigma3] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE SIGMA4 + { + let x := mload(SIGMA4_X_LOC) + let y := mload(SIGMA4_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V20_LOC)) + // accumulator_2 = v20.[sigma4] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE TABLE1 + { + let x := mload(TABLE1_X_LOC) + let y := mload(TABLE1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V21_LOC), p)) + // accumulator_2 = u.[table1] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE TABLE2 + { + let x := mload(TABLE2_X_LOC) + let y := mload(TABLE2_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V22_LOC), p)) + // accumulator_2 = u.[table2] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE TABLE3 + { + let x := mload(TABLE3_X_LOC) + let y := mload(TABLE3_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V23_LOC), p)) + // accumulator_2 = u.[table3] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE TABLE4 + { + let x := mload(TABLE4_X_LOC) + let y := mload(TABLE4_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V24_LOC), p)) + // accumulator_2 = u.[table4] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE TABLE_TYPE + { + let x := mload(TABLE_TYPE_X_LOC) + let y := mload(TABLE_TYPE_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V25_LOC)) + // accumulator_2 = v25.[TableType] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE ID1 + { + let x := mload(ID1_X_LOC) + let y := mload(ID1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V26_LOC)) + // accumulator_2 = v26.[ID1] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE ID2 + { + let x := mload(ID2_X_LOC) + let y := mload(ID2_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V27_LOC)) + // accumulator_2 = v27.[ID2] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE ID3 + { + let x := mload(ID3_X_LOC) + let y := mload(ID3_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V28_LOC)) + // accumulator_2 = v28.[ID3] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE ID4 + { + let x := mload(ID4_X_LOC) + let y := mload(ID4_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V29_LOC)) + // accumulator_2 = v29.[ID4] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + /** + * COMPUTE BATCH EVALUATION SCALAR MULTIPLIER + */ + { + /** + * batch_evaluation = v0 * (w_1_omega * u + w_1_eval) + * batch_evaluation += v1 * (w_2_omega * u + w_2_eval) + * batch_evaluation += v2 * (w_3_omega * u + w_3_eval) + * batch_evaluation += v3 * (w_4_omega * u + w_4_eval) + * batch_evaluation += v4 * (s_omega_eval * u + s_eval) + * batch_evaluation += v5 * (z_omega_eval * u + z_eval) + * batch_evaluation += v6 * (z_lookup_omega_eval * u + z_lookup_eval) + */ + let batch_evaluation := + mulmod( + mload(C_V0_LOC), + addmod(mulmod(mload(W1_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(W1_EVAL_LOC), p), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V1_LOC), + addmod(mulmod(mload(W2_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(W2_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V2_LOC), + addmod(mulmod(mload(W3_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(W3_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V3_LOC), + addmod(mulmod(mload(W4_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(W4_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V4_LOC), + addmod(mulmod(mload(S_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(S_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V5_LOC), + addmod(mulmod(mload(Z_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(Z_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V6_LOC), + addmod(mulmod(mload(Z_LOOKUP_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(Z_LOOKUP_EVAL_LOC), p), + p + ), + p + ) + + /** + * batch_evaluation += v7 * Q1_EVAL + * batch_evaluation += v8 * Q2_EVAL + * batch_evaluation += v9 * Q3_EVAL + * batch_evaluation += v10 * Q4_EVAL + * batch_evaluation += v11 * QM_EVAL + * batch_evaluation += v12 * QC_EVAL + * batch_evaluation += v13 * QARITH_EVAL + * batch_evaluation += v14 * QSORT_EVAL_LOC + * batch_evaluation += v15 * QELLIPTIC_EVAL_LOC + * batch_evaluation += v16 * QAUX_EVAL_LOC + * batch_evaluation += v17 * SIGMA1_EVAL_LOC + * batch_evaluation += v18 * SIGMA2_EVAL_LOC + * batch_evaluation += v19 * SIGMA3_EVAL_LOC + * batch_evaluation += v20 * SIGMA4_EVAL_LOC + */ + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V7_LOC), mload(Q1_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V8_LOC), mload(Q2_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V9_LOC), mload(Q3_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V10_LOC), mload(Q4_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V11_LOC), mload(QM_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V12_LOC), mload(QC_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V13_LOC), mload(QARITH_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V14_LOC), mload(QSORT_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V15_LOC), mload(QELLIPTIC_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V16_LOC), mload(QAUX_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V17_LOC), mload(SIGMA1_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V18_LOC), mload(SIGMA2_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V19_LOC), mload(SIGMA3_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V20_LOC), mload(SIGMA4_EVAL_LOC), p), p) + + /** + * batch_evaluation += v21 * (table1(zw) * u + table1(z)) + * batch_evaluation += v22 * (table2(zw) * u + table2(z)) + * batch_evaluation += v23 * (table3(zw) * u + table3(z)) + * batch_evaluation += v24 * (table4(zw) * u + table4(z)) + * batch_evaluation += v25 * table_type_eval + * batch_evaluation += v26 * id1_eval + * batch_evaluation += v27 * id2_eval + * batch_evaluation += v28 * id3_eval + * batch_evaluation += v29 * id4_eval + * batch_evaluation += quotient_eval + */ + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V21_LOC), + addmod(mulmod(mload(TABLE1_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(TABLE1_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V22_LOC), + addmod(mulmod(mload(TABLE2_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(TABLE2_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V23_LOC), + addmod(mulmod(mload(TABLE3_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(TABLE3_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V24_LOC), + addmod(mulmod(mload(TABLE4_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(TABLE4_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V25_LOC), mload(TABLE_TYPE_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V26_LOC), mload(ID1_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V27_LOC), mload(ID2_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V28_LOC), mload(ID3_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V29_LOC), mload(ID4_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mload(QUOTIENT_EVAL_LOC), p) + + mstore(0x00, 0x01) // [1].x + mstore(0x20, 0x02) // [1].y + mstore(0x40, sub(p, batch_evaluation)) + // accumulator_2 = -[1].(batch_evaluation) + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + mstore(OPENING_COMMITMENT_SUCCESS_FLAG, success) + } + + /** + * PERFORM PAIRING PREAMBLE + */ + { + let u := mload(C_U_LOC) + let zeta := mload(C_ZETA_LOC) + // VALIDATE PI_Z + { + let x := mload(PI_Z_X_LOC) + let y := mload(PI_Z_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)) + mstore(0x00, x) + mstore(0x20, y) + } + // compute zeta.[PI_Z] and add into accumulator + mstore(0x40, zeta) + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE PI_Z_OMEGA + { + let x := mload(PI_Z_OMEGA_X_LOC) + let y := mload(PI_Z_OMEGA_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(mulmod(u, zeta, p), mload(OMEGA_LOC), p)) + // accumulator_2 = u.zeta.omega.[PI_Z_OMEGA] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // PAIRING_RHS = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, PAIRING_RHS_X_LOC, 0x40)) + + mstore(0x00, mload(PI_Z_X_LOC)) + mstore(0x20, mload(PI_Z_Y_LOC)) + mstore(0x40, mload(PI_Z_OMEGA_X_LOC)) + mstore(0x60, mload(PI_Z_OMEGA_Y_LOC)) + mstore(0x80, u) + success := and(success, staticcall(gas(), 7, 0x40, 0x60, 0x40, 0x40)) + // PAIRING_LHS = [PI_Z] + [PI_Z_OMEGA] * u + success := and(success, staticcall(gas(), 6, 0x00, 0x80, PAIRING_LHS_X_LOC, 0x40)) + // negate lhs y-coordinate + mstore(PAIRING_LHS_Y_LOC, sub(q, mload(PAIRING_LHS_Y_LOC))) + + if mload(CONTAINS_RECURSIVE_PROOF_LOC) { + // VALIDATE RECURSIVE P1 + { + let x := mload(RECURSIVE_P1_X_LOC) + let y := mload(RECURSIVE_P1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + + // compute u.u.[recursive_p1] and write into 0x60 + mstore(0x40, mulmod(u, u, p)) + success := and(success, staticcall(gas(), 7, 0x00, 0x60, 0x60, 0x40)) + // VALIDATE RECURSIVE P2 + { + let x := mload(RECURSIVE_P2_X_LOC) + let y := mload(RECURSIVE_P2_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + // compute u.u.[recursive_p2] and write into 0x00 + // 0x40 still contains u*u + success := and(success, staticcall(gas(), 7, 0x00, 0x60, 0x00, 0x40)) + + // compute u.u.[recursiveP1] + rhs and write into rhs + mstore(0xa0, mload(PAIRING_RHS_X_LOC)) + mstore(0xc0, mload(PAIRING_RHS_Y_LOC)) + success := and(success, staticcall(gas(), 6, 0x60, 0x80, PAIRING_RHS_X_LOC, 0x40)) + + // compute u.u.[recursiveP2] + lhs and write into lhs + mstore(0x40, mload(PAIRING_LHS_X_LOC)) + mstore(0x60, mload(PAIRING_LHS_Y_LOC)) + success := and(success, staticcall(gas(), 6, 0x00, 0x80, PAIRING_LHS_X_LOC, 0x40)) + } + + if iszero(success) { + mstore(0x0, EC_SCALAR_MUL_FAILURE_SELECTOR) + revert(0x00, 0x04) + } + mstore(PAIRING_PREAMBLE_SUCCESS_FLAG, success) + } + + /** + * PERFORM PAIRING + */ + { + // rhs paired with [1]_2 + // lhs paired with [x]_2 + + mstore(0x00, mload(PAIRING_RHS_X_LOC)) + mstore(0x20, mload(PAIRING_RHS_Y_LOC)) + mstore(0x40, 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2) // this is [1]_2 + mstore(0x60, 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed) + mstore(0x80, 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b) + mstore(0xa0, 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa) + + mstore(0xc0, mload(PAIRING_LHS_X_LOC)) + mstore(0xe0, mload(PAIRING_LHS_Y_LOC)) + mstore(0x100, mload(G2X_X0_LOC)) + mstore(0x120, mload(G2X_X1_LOC)) + mstore(0x140, mload(G2X_Y0_LOC)) + mstore(0x160, mload(G2X_Y1_LOC)) + + success := staticcall(gas(), 8, 0x00, 0x180, 0x00, 0x20) + mstore(PAIRING_SUCCESS_FLAG, success) + mstore(RESULT_FLAG, mload(0x00)) + } + if iszero( + and( + and(and(mload(PAIRING_SUCCESS_FLAG), mload(RESULT_FLAG)), mload(PAIRING_PREAMBLE_SUCCESS_FLAG)), + mload(OPENING_COMMITMENT_SUCCESS_FLAG) + ) + ) { + mstore(0x0, PROOF_FAILURE_SELECTOR) + revert(0x00, 0x04) + } + { + mstore(0x00, 0x01) + return(0x00, 0x20) // Proof succeeded! + } + } + } +} + +contract UltraVerifier is BaseUltraVerifier { + function getVerificationKeyHash() public pure override(BaseUltraVerifier) returns (bytes32) { + return UltraVerificationKey.verificationKeyHash(); + } + + function loadVerificationKey(uint256 vk, uint256 _omegaInverseLoc) internal pure virtual override(BaseUltraVerifier) { + UltraVerificationKey.loadVerificationKey(vk, _omegaInverseLoc); + } +} diff --git a/circuits/main/src/main.nr b/circuits/main/src/main.nr new file mode 100644 index 0000000000000000000000000000000000000000..6e4d1bb8bc8e032da7b157013bd0ae8f957ab7ea --- /dev/null +++ b/circuits/main/src/main.nr @@ -0,0 +1,84 @@ +use dep::std; +global state_depth = 9; +global utxo_depth = 4; +global batch_depth = 4; +fn main( + current_root: pub Field, + deposit: pub Field, + withdrawals: pub [Field; 16], + commitment_out: pub [Field; 16], + recipients: pub [Field; 16], + oracle: pub Field, + old_root_proof: [Field; 16], + nullifier_hashes: pub [Field; 16], + secrets: [Field; 16], + utxo_in: [Field; 48], + utxo_out: [Field; 48], + indexes: [Field; 48], + hash_path: [Field; 272], +) -> pub Field { + let trees: Field = 4; + let mut sum_in: Field = deposit; + let mut sum_out: Field = withdrawals.reduce(|a, b| a + b); + for i in 0..16 { + if (utxo_in[i*3 + 1] != 0) { + let owner = std::hash::pedersen([secrets[i]]); + assert(owner[0] == utxo_in[i*3 + 0]); + assert(nullifier_hashes[i] == std::hash::pedersen([secrets[i], secrets[i]])[0]); + let commitment_in = std::hash::pedersen([utxo_in[i*3 + 0], utxo_in[i*3 + 1], utxo_in[i*3 + 2]])[0]; + let mut hash_path_utxo: [Field; utxo_depth] = [0; utxo_depth]; + let mut hash_path_tx: [Field; batch_depth] = [0; batch_depth]; + let mut hash_path_historic: [Field; state_depth] = [0; state_depth]; + for j in 0..4 { + hash_path_utxo[j] = hash_path[(state_depth + utxo_depth + batch_depth) * i + j]; + hash_path_tx[j] = hash_path[(state_depth + utxo_depth + batch_depth) * i + utxo_depth + j]; + } + for l in 0..state_depth { + hash_path_historic[l] = hash_path[(state_depth + utxo_depth + batch_depth) * i + utxo_depth + batch_depth + l]; + } + let index_utxo = indexes[trees * i + 0]; + let index_tx = indexes[trees * i + 1]; + let index_historic = indexes[trees * i + 2]; + let utxo_root = std::merkle::compute_merkle_root( + commitment_in, + index_utxo, + hash_path_utxo + ); + let tx_root = std::merkle::compute_merkle_root( + utxo_root, + index_tx, + hash_path_tx + ); + let leaf_batch = std::hash::pedersen([tx_root, oracle])[0]; + let leaf_historic = std::hash::pedersen([leaf_batch, old_root_proof[i]])[0]; + let historic_root = std::merkle::compute_merkle_root( + leaf_historic, + index_historic, + hash_path_historic + ); + assert(historic_root == current_root); + sum_in += utxo_in[i*3 + 1]; + } + } + for k in 0..16 { + if (utxo_out[k*3 + 1] != 0) { + let commitment_out_calc = std::hash::pedersen([utxo_out[k*3 + 0], utxo_out[k*3 + 1], utxo_out[k*3 + 2]]); + assert(commitment_out_calc[0] == commitment_out[k]); + sum_out += utxo_out[k*3 + 1]; + } + else { + let zero_hash = 0xf35fcb490b7ea67c3ac26ed530fa5d8dfe8be344e7177ebb63fe02723fb6f725 as Field; + assert(commitment_out[k] == zero_hash); + } + } + assert(sum_in == sum_out); + let utxo_root_calc: Field = pedersen_tree_four(commitment_out); + utxo_root_calc +} +fn pedersen_tree_four(leaves: [Field; 16]) -> Field { + let mut tx_tree: [Field; 16] = leaves; + for l in 0..8 { tx_tree[l] = std::hash::pedersen([tx_tree[2*l], tx_tree[2*l + 1]])[0]; } + for l in 0..4 { tx_tree[l] = std::hash::pedersen([tx_tree[2*l], tx_tree[2*l + 1]])[0]; } + for l in 0..2 { tx_tree[l] = std::hash::pedersen([tx_tree[2*l], tx_tree[2*l + 1]])[0]; } + std::hash::pedersen([tx_tree[0], tx_tree[1]])[0] +} \ No newline at end of file diff --git a/circuits/main/target/main.json b/circuits/main/target/main.json new file mode 100644 index 0000000000000000000000000000000000000000..488dc93ad4ae18c28bb358f67704ceba97b81d09 --- /dev/null +++ b/circuits/main/target/main.json @@ -0,0 +1 @@ +{"backend":"acvm-backend-barretenberg","abi":{"parameters":[{"name":"current_root","type":{"kind":"field"},"visibility":"public"},{"name":"deposit","type":{"kind":"field"},"visibility":"public"},{"name":"withdrawals","type":{"kind":"array","length":16,"type":{"kind":"field"}},"visibility":"public"},{"name":"commitment_out","type":{"kind":"array","length":16,"type":{"kind":"field"}},"visibility":"public"},{"name":"recipients","type":{"kind":"array","length":16,"type":{"kind":"field"}},"visibility":"public"},{"name":"oracle","type":{"kind":"field"},"visibility":"public"},{"name":"old_root_proof","type":{"kind":"array","length":16,"type":{"kind":"field"}},"visibility":"private"},{"name":"nullifier_hashes","type":{"kind":"array","length":16,"type":{"kind":"field"}},"visibility":"public"},{"name":"secrets","type":{"kind":"array","length":16,"type":{"kind":"field"}},"visibility":"private"},{"name":"utxo_in","type":{"kind":"array","length":48,"type":{"kind":"field"}},"visibility":"private"},{"name":"utxo_out","type":{"kind":"array","length":48,"type":{"kind":"field"}},"visibility":"private"},{"name":"indexes","type":{"kind":"array","length":48,"type":{"kind":"field"}},"visibility":"private"},{"name":"hash_path","type":{"kind":"array","length":272,"type":{"kind":"field"}},"visibility":"private"}],"param_witnesses":{"commitment_out":[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34],"current_root":[1],"deposit":[2],"hash_path":[244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515],"indexes":[196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243],"nullifier_hashes":[68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83],"old_root_proof":[52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67],"oracle":[51],"recipients":[35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50],"secrets":[84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99],"utxo_in":[100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147],"utxo_out":[148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195],"withdrawals":[3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18]},"return_type":{"kind":"field"},"return_witnesses":[4671]},"bytecode":"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"} \ No newline at end of file diff --git a/circuits/recursion/Nargo.toml b/circuits/recursion/Nargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..5469c07c7de05b4b883520b2ceb0c89b932ecf77 --- /dev/null +++ b/circuits/recursion/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "recursion" +type = "bin" +authors = [""] +compiler_version = "0.9.0" + +[dependencies] diff --git a/circuits/recursion/Prover.toml b/circuits/recursion/Prover.toml new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/circuits/recursion/Verifier.toml b/circuits/recursion/Verifier.toml new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/circuits/recursion/contract/recursion/plonk_vk.sol b/circuits/recursion/contract/recursion/plonk_vk.sol new file mode 100644 index 0000000000000000000000000000000000000000..944adf42ee1f15221f8ddb512538424a4cffa8e1 --- /dev/null +++ b/circuits/recursion/contract/recursion/plonk_vk.sol @@ -0,0 +1,2611 @@ +// Verification Key Hash: 82a0e2cee227b6fb3137880ee66026d05a5aaadd8a7d84079a490ac13c4b86aa +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2022 Aztec +pragma solidity >=0.8.4; + +library UltraVerificationKey { + function verificationKeyHash() internal pure returns(bytes32) { + return 0x82a0e2cee227b6fb3137880ee66026d05a5aaadd8a7d84079a490ac13c4b86aa; + } + + function loadVerificationKey(uint256 _vk, uint256 _omegaInverseLoc) internal pure { + assembly { + mstore(add(_vk, 0x00), 0x0000000000000000000000000000000000000000000000000000000000080000) // vk.circuit_size + mstore(add(_vk, 0x20), 0x0000000000000000000000000000000000000000000000000000000000000024) // vk.num_inputs + mstore(add(_vk, 0x40), 0x2260e724844bca5251829353968e4915305258418357473a5c1d597f613f6cbd) // vk.work_root + mstore(add(_vk, 0x60), 0x3064486657634403844b0eac78ca882cfd284341fcb0615a15cfcd17b14d8201) // vk.domain_inverse + mstore(add(_vk, 0x80), 0x11b071f902eecdc05b4943a2febe1e9cbfa430be1e22a2809d4fffe060608f30) // vk.Q1.x + mstore(add(_vk, 0xa0), 0x13896c8f1e1b73a6636508284e6e55df02238aab3ae04105b345b1dee15b052f) // vk.Q1.y + mstore(add(_vk, 0xc0), 0x0916f8b80ca7b561b859ce7150cc9c1d242334653ab30bfbffe2cd4a54504c61) // vk.Q2.x + mstore(add(_vk, 0xe0), 0x2888dc92bd783f0f56f57c865ab1f1ea41fe8deaed9fc91b091ca3db2b6de3cc) // vk.Q2.y + mstore(add(_vk, 0x100), 0x120a493b68fcc4f791c64972c81acad705c95cd65d36948093ded25ecc4e707f) // vk.Q3.x + mstore(add(_vk, 0x120), 0x241ab99646f6e50ff1dcdccfc41a3feeb1cf75a1f261e851ab06836291b9b943) // vk.Q3.y + mstore(add(_vk, 0x140), 0x01362a1f1ff97b452de0ac04725ac215b171fd862e01ded45f7e806fbefd5fe5) // vk.Q4.x + mstore(add(_vk, 0x160), 0x25d2d06cb1043af7986b2049cbab7f7a88a54c25e4bc8f14433bb39afba872bc) // vk.Q4.y + mstore(add(_vk, 0x180), 0x0f01aed4a6231d33a21c40727645d5d6b163852a6c6833a43e6165199829e213) // vk.Q_M.x + mstore(add(_vk, 0x1a0), 0x019efeb324e468d0a76c33012c3efb6a890d893eccae908c5f137baea3b932f6) // vk.Q_M.y + mstore(add(_vk, 0x1c0), 0x0e277f26853604627f021d7d5740160d47043154ee787b076be2208ac90a9666) // vk.Q_C.x + mstore(add(_vk, 0x1e0), 0x115fb2d9c8ef3fee3113064e54dd299acb3fbe249c52c4eaf7ddd5ea511d4a48) // vk.Q_C.y + mstore(add(_vk, 0x200), 0x0f19ea9fe36453db55a8560084163be7f55004c1c4234e761b67a14e849b6907) // vk.Q_ARITHMETIC.x + mstore(add(_vk, 0x220), 0x142cf7bc7b2aa0f42050ade1488657bc63856be638c1d157a85c256b53f65ef4) // vk.Q_ARITHMETIC.y + mstore(add(_vk, 0x240), 0x034ff0a1c64d1e6af006b0039cf65dfd17c7e905723f266741042c5dde321e81) // vk.QSORT.x + mstore(add(_vk, 0x260), 0x22873eafa0cdb28654784fd8c68119e1a8a99252df586a7485da1feee556fcec) // vk.QSORT.y + mstore(add(_vk, 0x280), 0x23f5f6970dd0cceedb135a3a3f30cfe3b0269d33e3f553317496d29a544b5a62) // vk.Q_ELLIPTIC.x + mstore(add(_vk, 0x2a0), 0x270623e3f1eff355c22d6ddae107d2f99a67d22c7af762e3b8edb6986ddcf1b3) // vk.Q_ELLIPTIC.y + mstore(add(_vk, 0x2c0), 0x06853a42dede8a6279451da2c12bd2c06b630b49ed9cf77f6d1b0a703d419328) // vk.Q_AUX.x + mstore(add(_vk, 0x2e0), 0x01ef8f3f07237a290e826c2597a98bd3fed6e60c8e5be81db1b54c7798bd083f) // vk.Q_AUX.y + mstore(add(_vk, 0x300), 0x276a90bf6eec49cd5e31cb868d714b38805563f24fad21e168f597520b3b1f4e) // vk.SIGMA1.x + mstore(add(_vk, 0x320), 0x22a086ce72b523ab9858d7cd782aee228ff9c18b7c5900412c5b82502fef5b1c) // vk.SIGMA1.y + mstore(add(_vk, 0x340), 0x1a10f3eeb35e51bda8df6b240ac1a6b3fbaf614eaa19e7655cca4a5b2a8f8165) // vk.SIGMA2.x + mstore(add(_vk, 0x360), 0x08cdc78ec23a7ec954f881665ddb53cefe6edee63ee6a3c8415d2cc93b332e25) // vk.SIGMA2.y + mstore(add(_vk, 0x380), 0x06b3916f1f919044a65a6b25c1f572c9e1d2985d2d869559facfb20b9b04d6b1) // vk.SIGMA3.x + mstore(add(_vk, 0x3a0), 0x0a021155576fccaf3612005c6490dba33a8551fd4b90fb7fc0efff45ae50028c) // vk.SIGMA3.y + mstore(add(_vk, 0x3c0), 0x01a34c4fe947c3a2cd85c02df315cc972c02a82111229afa52914ebd30e4fa9f) // vk.SIGMA4.x + mstore(add(_vk, 0x3e0), 0x014e16dbb2b81c45ccc27fb975f62c31c7338fb36f3ed7d9c235ed89e8b832f7) // vk.SIGMA4.y + mstore(add(_vk, 0x400), 0x2fb583c2a09e1762576580dab537fc24c6d34716e68b3d75c106db4f66362648) // vk.TABLE1.x + mstore(add(_vk, 0x420), 0x23f8cf2668d8ebbb7472c0882f38d7c38856632985a78f754297a47faab0a491) // vk.TABLE1.y + mstore(add(_vk, 0x440), 0x17e35f8d4bf88a6803bba08a78a9267d20aff06b0a60f61a4a00aebf9f9fc7b1) // vk.TABLE2.x + mstore(add(_vk, 0x460), 0x0f88c0ad7b6162820c6c75ff4b4be0651f69927737f9dda272ebf26649faa64f) // vk.TABLE2.y + mstore(add(_vk, 0x480), 0x01c8bab7a1bafb2c9056138761ffb59be9fdeb32edaf6538c0315f36f9279c07) // vk.TABLE3.x + mstore(add(_vk, 0x4a0), 0x21d060188a0c759b9debb64fe30393f63ee2bac8830f4b3724b8103e0cea528e) // vk.TABLE3.y + mstore(add(_vk, 0x4c0), 0x0b67cf32c00e76184073a85ed3c263c7021c3abd63b46545a5e9d07e0304ee22) // vk.TABLE4.x + mstore(add(_vk, 0x4e0), 0x07ad8d70ffbae8d1111b6fa8cca4ccc12326812513d81ff1fae90bf27474ce46) // vk.TABLE4.y + mstore(add(_vk, 0x500), 0x1447f09274c0399f9f5b08f4bbd25d7d53dc868b97264be6e6d0f7d740f67767) // vk.TABLE_TYPE.x + mstore(add(_vk, 0x520), 0x037231732044d488092c2a155234ff78b4d75c947125a1d32ec32f4efd7354be) // vk.TABLE_TYPE.y + mstore(add(_vk, 0x540), 0x2deb65c6968a56cf21a01c9d6f9e16655dcf7720d52657631abab33211b667fa) // vk.ID1.x + mstore(add(_vk, 0x560), 0x18a43caeb9516e4401cb61620e8dc51f1a93d3f0ed097c04eab867d56e4df47e) // vk.ID1.y + mstore(add(_vk, 0x580), 0x1e5ee22c88520be7f3023e3fc62deb366524bbd51d781c26aee32df954d9b93f) // vk.ID2.x + mstore(add(_vk, 0x5a0), 0x2fa0775108095bbb4548c02d41b78118f67556a16259ea672cab171727bc8967) // vk.ID2.y + mstore(add(_vk, 0x5c0), 0x1894e9095aebba0c5fc53bb1575cb34e73aaa3ce5fbeea4edefa9f3826933f1d) // vk.ID3.x + mstore(add(_vk, 0x5e0), 0x1212ea71a64f557b3151b499f22322a17d3c3f4513d189f55d2736287231d08d) // vk.ID3.y + mstore(add(_vk, 0x600), 0x100349fc6d8acd4110d377d940f34669a525512b00e92de4ef4af6f5a40aec62) // vk.ID4.x + mstore(add(_vk, 0x620), 0x25e0e421d758a475b523902ae78e4d3908909155bc7cbe48b35a1532e075d54b) // vk.ID4.y + mstore(add(_vk, 0x640), 0x01) // vk.contains_recursive_proof + mstore(add(_vk, 0x660), 20) // vk.recursive_proof_public_input_indices + mstore(add(_vk, 0x680), 0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1) // vk.g2_x.X.c1 + mstore(add(_vk, 0x6a0), 0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0) // vk.g2_x.X.c0 + mstore(add(_vk, 0x6c0), 0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4) // vk.g2_x.Y.c1 + mstore(add(_vk, 0x6e0), 0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55) // vk.g2_x.Y.c0 + mstore(_omegaInverseLoc, 0x06e402c0a314fb67a15cf806664ae1b722dbc0efe66e6c81d98f9924ca535321) // vk.work_root_inverse + } + } +} + +/** + * @title Ultra Plonk proof verification contract + * @dev Top level Plonk proof verification contract, which allows Plonk proof to be verified + */ +abstract contract BaseUltraVerifier { + // VERIFICATION KEY MEMORY LOCATIONS + uint256 internal constant N_LOC = 0x380; + uint256 internal constant NUM_INPUTS_LOC = 0x3a0; + uint256 internal constant OMEGA_LOC = 0x3c0; + uint256 internal constant DOMAIN_INVERSE_LOC = 0x3e0; + uint256 internal constant Q1_X_LOC = 0x400; + uint256 internal constant Q1_Y_LOC = 0x420; + uint256 internal constant Q2_X_LOC = 0x440; + uint256 internal constant Q2_Y_LOC = 0x460; + uint256 internal constant Q3_X_LOC = 0x480; + uint256 internal constant Q3_Y_LOC = 0x4a0; + uint256 internal constant Q4_X_LOC = 0x4c0; + uint256 internal constant Q4_Y_LOC = 0x4e0; + uint256 internal constant QM_X_LOC = 0x500; + uint256 internal constant QM_Y_LOC = 0x520; + uint256 internal constant QC_X_LOC = 0x540; + uint256 internal constant QC_Y_LOC = 0x560; + uint256 internal constant QARITH_X_LOC = 0x580; + uint256 internal constant QARITH_Y_LOC = 0x5a0; + uint256 internal constant QSORT_X_LOC = 0x5c0; + uint256 internal constant QSORT_Y_LOC = 0x5e0; + uint256 internal constant QELLIPTIC_X_LOC = 0x600; + uint256 internal constant QELLIPTIC_Y_LOC = 0x620; + uint256 internal constant QAUX_X_LOC = 0x640; + uint256 internal constant QAUX_Y_LOC = 0x660; + uint256 internal constant SIGMA1_X_LOC = 0x680; + uint256 internal constant SIGMA1_Y_LOC = 0x6a0; + uint256 internal constant SIGMA2_X_LOC = 0x6c0; + uint256 internal constant SIGMA2_Y_LOC = 0x6e0; + uint256 internal constant SIGMA3_X_LOC = 0x700; + uint256 internal constant SIGMA3_Y_LOC = 0x720; + uint256 internal constant SIGMA4_X_LOC = 0x740; + uint256 internal constant SIGMA4_Y_LOC = 0x760; + uint256 internal constant TABLE1_X_LOC = 0x780; + uint256 internal constant TABLE1_Y_LOC = 0x7a0; + uint256 internal constant TABLE2_X_LOC = 0x7c0; + uint256 internal constant TABLE2_Y_LOC = 0x7e0; + uint256 internal constant TABLE3_X_LOC = 0x800; + uint256 internal constant TABLE3_Y_LOC = 0x820; + uint256 internal constant TABLE4_X_LOC = 0x840; + uint256 internal constant TABLE4_Y_LOC = 0x860; + uint256 internal constant TABLE_TYPE_X_LOC = 0x880; + uint256 internal constant TABLE_TYPE_Y_LOC = 0x8a0; + uint256 internal constant ID1_X_LOC = 0x8c0; + uint256 internal constant ID1_Y_LOC = 0x8e0; + uint256 internal constant ID2_X_LOC = 0x900; + uint256 internal constant ID2_Y_LOC = 0x920; + uint256 internal constant ID3_X_LOC = 0x940; + uint256 internal constant ID3_Y_LOC = 0x960; + uint256 internal constant ID4_X_LOC = 0x980; + uint256 internal constant ID4_Y_LOC = 0x9a0; + uint256 internal constant CONTAINS_RECURSIVE_PROOF_LOC = 0x9c0; + uint256 internal constant RECURSIVE_PROOF_PUBLIC_INPUT_INDICES_LOC = 0x9e0; + uint256 internal constant G2X_X0_LOC = 0xa00; + uint256 internal constant G2X_X1_LOC = 0xa20; + uint256 internal constant G2X_Y0_LOC = 0xa40; + uint256 internal constant G2X_Y1_LOC = 0xa60; + + // ### PROOF DATA MEMORY LOCATIONS + uint256 internal constant W1_X_LOC = 0x1200; + uint256 internal constant W1_Y_LOC = 0x1220; + uint256 internal constant W2_X_LOC = 0x1240; + uint256 internal constant W2_Y_LOC = 0x1260; + uint256 internal constant W3_X_LOC = 0x1280; + uint256 internal constant W3_Y_LOC = 0x12a0; + uint256 internal constant W4_X_LOC = 0x12c0; + uint256 internal constant W4_Y_LOC = 0x12e0; + uint256 internal constant S_X_LOC = 0x1300; + uint256 internal constant S_Y_LOC = 0x1320; + uint256 internal constant Z_X_LOC = 0x1340; + uint256 internal constant Z_Y_LOC = 0x1360; + uint256 internal constant Z_LOOKUP_X_LOC = 0x1380; + uint256 internal constant Z_LOOKUP_Y_LOC = 0x13a0; + uint256 internal constant T1_X_LOC = 0x13c0; + uint256 internal constant T1_Y_LOC = 0x13e0; + uint256 internal constant T2_X_LOC = 0x1400; + uint256 internal constant T2_Y_LOC = 0x1420; + uint256 internal constant T3_X_LOC = 0x1440; + uint256 internal constant T3_Y_LOC = 0x1460; + uint256 internal constant T4_X_LOC = 0x1480; + uint256 internal constant T4_Y_LOC = 0x14a0; + + uint256 internal constant W1_EVAL_LOC = 0x1600; + uint256 internal constant W2_EVAL_LOC = 0x1620; + uint256 internal constant W3_EVAL_LOC = 0x1640; + uint256 internal constant W4_EVAL_LOC = 0x1660; + uint256 internal constant S_EVAL_LOC = 0x1680; + uint256 internal constant Z_EVAL_LOC = 0x16a0; + uint256 internal constant Z_LOOKUP_EVAL_LOC = 0x16c0; + uint256 internal constant Q1_EVAL_LOC = 0x16e0; + uint256 internal constant Q2_EVAL_LOC = 0x1700; + uint256 internal constant Q3_EVAL_LOC = 0x1720; + uint256 internal constant Q4_EVAL_LOC = 0x1740; + uint256 internal constant QM_EVAL_LOC = 0x1760; + uint256 internal constant QC_EVAL_LOC = 0x1780; + uint256 internal constant QARITH_EVAL_LOC = 0x17a0; + uint256 internal constant QSORT_EVAL_LOC = 0x17c0; + uint256 internal constant QELLIPTIC_EVAL_LOC = 0x17e0; + uint256 internal constant QAUX_EVAL_LOC = 0x1800; + uint256 internal constant TABLE1_EVAL_LOC = 0x1840; + uint256 internal constant TABLE2_EVAL_LOC = 0x1860; + uint256 internal constant TABLE3_EVAL_LOC = 0x1880; + uint256 internal constant TABLE4_EVAL_LOC = 0x18a0; + uint256 internal constant TABLE_TYPE_EVAL_LOC = 0x18c0; + uint256 internal constant ID1_EVAL_LOC = 0x18e0; + uint256 internal constant ID2_EVAL_LOC = 0x1900; + uint256 internal constant ID3_EVAL_LOC = 0x1920; + uint256 internal constant ID4_EVAL_LOC = 0x1940; + uint256 internal constant SIGMA1_EVAL_LOC = 0x1960; + uint256 internal constant SIGMA2_EVAL_LOC = 0x1980; + uint256 internal constant SIGMA3_EVAL_LOC = 0x19a0; + uint256 internal constant SIGMA4_EVAL_LOC = 0x19c0; + uint256 internal constant W1_OMEGA_EVAL_LOC = 0x19e0; + uint256 internal constant W2_OMEGA_EVAL_LOC = 0x2000; + uint256 internal constant W3_OMEGA_EVAL_LOC = 0x2020; + uint256 internal constant W4_OMEGA_EVAL_LOC = 0x2040; + uint256 internal constant S_OMEGA_EVAL_LOC = 0x2060; + uint256 internal constant Z_OMEGA_EVAL_LOC = 0x2080; + uint256 internal constant Z_LOOKUP_OMEGA_EVAL_LOC = 0x20a0; + uint256 internal constant TABLE1_OMEGA_EVAL_LOC = 0x20c0; + uint256 internal constant TABLE2_OMEGA_EVAL_LOC = 0x20e0; + uint256 internal constant TABLE3_OMEGA_EVAL_LOC = 0x2100; + uint256 internal constant TABLE4_OMEGA_EVAL_LOC = 0x2120; + + uint256 internal constant PI_Z_X_LOC = 0x2300; + uint256 internal constant PI_Z_Y_LOC = 0x2320; + uint256 internal constant PI_Z_OMEGA_X_LOC = 0x2340; + uint256 internal constant PI_Z_OMEGA_Y_LOC = 0x2360; + + // Used for elliptic widget. These are alias names for wire + shifted wire evaluations + uint256 internal constant X1_EVAL_LOC = W2_EVAL_LOC; + uint256 internal constant X2_EVAL_LOC = W1_OMEGA_EVAL_LOC; + uint256 internal constant X3_EVAL_LOC = W2_OMEGA_EVAL_LOC; + uint256 internal constant Y1_EVAL_LOC = W3_EVAL_LOC; + uint256 internal constant Y2_EVAL_LOC = W4_OMEGA_EVAL_LOC; + uint256 internal constant Y3_EVAL_LOC = W3_OMEGA_EVAL_LOC; + uint256 internal constant QBETA_LOC = Q3_EVAL_LOC; + uint256 internal constant QBETA_SQR_LOC = Q4_EVAL_LOC; + uint256 internal constant QSIGN_LOC = Q1_EVAL_LOC; + + // ### CHALLENGES MEMORY OFFSETS + + uint256 internal constant C_BETA_LOC = 0x2600; + uint256 internal constant C_GAMMA_LOC = 0x2620; + uint256 internal constant C_ALPHA_LOC = 0x2640; + uint256 internal constant C_ETA_LOC = 0x2660; + uint256 internal constant C_ETA_SQR_LOC = 0x2680; + uint256 internal constant C_ETA_CUBE_LOC = 0x26a0; + + uint256 internal constant C_ZETA_LOC = 0x26c0; + uint256 internal constant C_CURRENT_LOC = 0x26e0; + uint256 internal constant C_V0_LOC = 0x2700; + uint256 internal constant C_V1_LOC = 0x2720; + uint256 internal constant C_V2_LOC = 0x2740; + uint256 internal constant C_V3_LOC = 0x2760; + uint256 internal constant C_V4_LOC = 0x2780; + uint256 internal constant C_V5_LOC = 0x27a0; + uint256 internal constant C_V6_LOC = 0x27c0; + uint256 internal constant C_V7_LOC = 0x27e0; + uint256 internal constant C_V8_LOC = 0x2800; + uint256 internal constant C_V9_LOC = 0x2820; + uint256 internal constant C_V10_LOC = 0x2840; + uint256 internal constant C_V11_LOC = 0x2860; + uint256 internal constant C_V12_LOC = 0x2880; + uint256 internal constant C_V13_LOC = 0x28a0; + uint256 internal constant C_V14_LOC = 0x28c0; + uint256 internal constant C_V15_LOC = 0x28e0; + uint256 internal constant C_V16_LOC = 0x2900; + uint256 internal constant C_V17_LOC = 0x2920; + uint256 internal constant C_V18_LOC = 0x2940; + uint256 internal constant C_V19_LOC = 0x2960; + uint256 internal constant C_V20_LOC = 0x2980; + uint256 internal constant C_V21_LOC = 0x29a0; + uint256 internal constant C_V22_LOC = 0x29c0; + uint256 internal constant C_V23_LOC = 0x29e0; + uint256 internal constant C_V24_LOC = 0x2a00; + uint256 internal constant C_V25_LOC = 0x2a20; + uint256 internal constant C_V26_LOC = 0x2a40; + uint256 internal constant C_V27_LOC = 0x2a60; + uint256 internal constant C_V28_LOC = 0x2a80; + uint256 internal constant C_V29_LOC = 0x2aa0; + uint256 internal constant C_V30_LOC = 0x2ac0; + + uint256 internal constant C_U_LOC = 0x2b00; + + // ### LOCAL VARIABLES MEMORY OFFSETS + uint256 internal constant DELTA_NUMERATOR_LOC = 0x3000; + uint256 internal constant DELTA_DENOMINATOR_LOC = 0x3020; + uint256 internal constant ZETA_POW_N_LOC = 0x3040; + uint256 internal constant PUBLIC_INPUT_DELTA_LOC = 0x3060; + uint256 internal constant ZERO_POLY_LOC = 0x3080; + uint256 internal constant L_START_LOC = 0x30a0; + uint256 internal constant L_END_LOC = 0x30c0; + uint256 internal constant R_ZERO_EVAL_LOC = 0x30e0; + + uint256 internal constant PLOOKUP_DELTA_NUMERATOR_LOC = 0x3100; + uint256 internal constant PLOOKUP_DELTA_DENOMINATOR_LOC = 0x3120; + uint256 internal constant PLOOKUP_DELTA_LOC = 0x3140; + + uint256 internal constant ACCUMULATOR_X_LOC = 0x3160; + uint256 internal constant ACCUMULATOR_Y_LOC = 0x3180; + uint256 internal constant ACCUMULATOR2_X_LOC = 0x31a0; + uint256 internal constant ACCUMULATOR2_Y_LOC = 0x31c0; + uint256 internal constant PAIRING_LHS_X_LOC = 0x31e0; + uint256 internal constant PAIRING_LHS_Y_LOC = 0x3200; + uint256 internal constant PAIRING_RHS_X_LOC = 0x3220; + uint256 internal constant PAIRING_RHS_Y_LOC = 0x3240; + + // ### SUCCESS FLAG MEMORY LOCATIONS + uint256 internal constant GRAND_PRODUCT_SUCCESS_FLAG = 0x3300; + uint256 internal constant ARITHMETIC_TERM_SUCCESS_FLAG = 0x3020; + uint256 internal constant BATCH_OPENING_SUCCESS_FLAG = 0x3340; + uint256 internal constant OPENING_COMMITMENT_SUCCESS_FLAG = 0x3360; + uint256 internal constant PAIRING_PREAMBLE_SUCCESS_FLAG = 0x3380; + uint256 internal constant PAIRING_SUCCESS_FLAG = 0x33a0; + uint256 internal constant RESULT_FLAG = 0x33c0; + + // misc stuff + uint256 internal constant OMEGA_INVERSE_LOC = 0x3400; + uint256 internal constant C_ALPHA_SQR_LOC = 0x3420; + uint256 internal constant C_ALPHA_CUBE_LOC = 0x3440; + uint256 internal constant C_ALPHA_QUAD_LOC = 0x3460; + uint256 internal constant C_ALPHA_BASE_LOC = 0x3480; + + // ### RECURSION VARIABLE MEMORY LOCATIONS + uint256 internal constant RECURSIVE_P1_X_LOC = 0x3500; + uint256 internal constant RECURSIVE_P1_Y_LOC = 0x3520; + uint256 internal constant RECURSIVE_P2_X_LOC = 0x3540; + uint256 internal constant RECURSIVE_P2_Y_LOC = 0x3560; + + uint256 internal constant PUBLIC_INPUTS_HASH_LOCATION = 0x3580; + + // sub-identity storage + uint256 internal constant PERMUTATION_IDENTITY = 0x3600; + uint256 internal constant PLOOKUP_IDENTITY = 0x3620; + uint256 internal constant ARITHMETIC_IDENTITY = 0x3640; + uint256 internal constant SORT_IDENTITY = 0x3660; + uint256 internal constant ELLIPTIC_IDENTITY = 0x3680; + uint256 internal constant AUX_IDENTITY = 0x36a0; + uint256 internal constant AUX_NON_NATIVE_FIELD_EVALUATION = 0x36c0; + uint256 internal constant AUX_LIMB_ACCUMULATOR_EVALUATION = 0x36e0; + uint256 internal constant AUX_RAM_CONSISTENCY_EVALUATION = 0x3700; + uint256 internal constant AUX_ROM_CONSISTENCY_EVALUATION = 0x3720; + uint256 internal constant AUX_MEMORY_EVALUATION = 0x3740; + + uint256 internal constant QUOTIENT_EVAL_LOC = 0x3760; + uint256 internal constant ZERO_POLY_INVERSE_LOC = 0x3780; + + // when hashing public inputs we use memory at NU_CHALLENGE_INPUT_LOC_A, as the hash input size is unknown at compile time + uint256 internal constant NU_CHALLENGE_INPUT_LOC_A = 0x37a0; + uint256 internal constant NU_CHALLENGE_INPUT_LOC_B = 0x37c0; + uint256 internal constant NU_CHALLENGE_INPUT_LOC_C = 0x37e0; + + bytes4 internal constant PUBLIC_INPUT_INVALID_BN128_G1_POINT_SELECTOR = 0xeba9f4a6; + bytes4 internal constant PUBLIC_INPUT_GE_P_SELECTOR = 0x374a972f; + bytes4 internal constant MOD_EXP_FAILURE_SELECTOR = 0xf894a7bc; + bytes4 internal constant EC_SCALAR_MUL_FAILURE_SELECTOR = 0xf755f369; + bytes4 internal constant PROOF_FAILURE_SELECTOR = 0x0711fcec; + + uint256 internal constant ETA_INPUT_LENGTH = 0xc0; // W1, W2, W3 = 6 * 0x20 bytes + + // We need to hash 41 field elements when generating the NU challenge + // w1, w2, w3, w4, s, z, z_lookup, q1, q2, q3, q4, qm, qc, qarith (14) + // qsort, qelliptic, qaux, sigma1, sigma2, sigma, sigma4, (7) + // table1, table2, table3, table4, tabletype, id1, id2, id3, id4, (9) + // w1_omega, w2_omega, w3_omega, w4_omega, s_omega, z_omega, z_lookup_omega, (7) + // table1_omega, table2_omega, table3_omega, table4_omega (4) + uint256 internal constant NU_INPUT_LENGTH = 0x520; // 0x520 = 41 * 0x20 + + // There are ELEVEN G1 group elements added into the transcript in the `beta` round, that we need to skip over + // W1, W2, W3, W4, S, Z, Z_LOOKUP, T1, T2, T3, T4 + uint256 internal constant NU_CALLDATA_SKIP_LENGTH = 0x2c0; // 11 * 0x40 = 0x2c0 + + uint256 internal constant NEGATIVE_INVERSE_OF_2_MODULO_P = + 0x183227397098d014dc2822db40c0ac2e9419f4243cdcb848a1f0fac9f8000000; + uint256 internal constant LIMB_SIZE = 0x100000000000000000; // 2<<68 + uint256 internal constant SUBLIMB_SHIFT = 0x4000; // 2<<14 + + error PUBLIC_INPUT_COUNT_INVALID(uint256 expected, uint256 actual); + error PUBLIC_INPUT_INVALID_BN128_G1_POINT(); + error PUBLIC_INPUT_GE_P(); + error MOD_EXP_FAILURE(); + error EC_SCALAR_MUL_FAILURE(); + error PROOF_FAILURE(); + + function getVerificationKeyHash() public pure virtual returns (bytes32); + + function loadVerificationKey(uint256 _vk, uint256 _omegaInverseLoc) internal pure virtual; + + /** + * @notice Verify a Ultra Plonk proof + * @param _proof - The serialized proof + * @param _publicInputs - An array of the public inputs + * @return True if proof is valid, reverts otherwise + */ + function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external view returns (bool) { + loadVerificationKey(N_LOC, OMEGA_INVERSE_LOC); + + uint256 requiredPublicInputCount; + assembly { + requiredPublicInputCount := mload(NUM_INPUTS_LOC) + } + if (requiredPublicInputCount != _publicInputs.length) { + revert PUBLIC_INPUT_COUNT_INVALID(requiredPublicInputCount, _publicInputs.length); + } + + assembly { + let q := 21888242871839275222246405745257275088696311157297823662689037894645226208583 // EC group order + let p := 21888242871839275222246405745257275088548364400416034343698204186575808495617 // Prime field order + + /** + * LOAD PROOF FROM CALLDATA + */ + { + let data_ptr := add(calldataload(0x04), 0x24) + + mstore(W1_Y_LOC, mod(calldataload(data_ptr), q)) + mstore(W1_X_LOC, mod(calldataload(add(data_ptr, 0x20)), q)) + + mstore(W2_Y_LOC, mod(calldataload(add(data_ptr, 0x40)), q)) + mstore(W2_X_LOC, mod(calldataload(add(data_ptr, 0x60)), q)) + + mstore(W3_Y_LOC, mod(calldataload(add(data_ptr, 0x80)), q)) + mstore(W3_X_LOC, mod(calldataload(add(data_ptr, 0xa0)), q)) + + mstore(W4_Y_LOC, mod(calldataload(add(data_ptr, 0xc0)), q)) + mstore(W4_X_LOC, mod(calldataload(add(data_ptr, 0xe0)), q)) + + mstore(S_Y_LOC, mod(calldataload(add(data_ptr, 0x100)), q)) + mstore(S_X_LOC, mod(calldataload(add(data_ptr, 0x120)), q)) + mstore(Z_Y_LOC, mod(calldataload(add(data_ptr, 0x140)), q)) + mstore(Z_X_LOC, mod(calldataload(add(data_ptr, 0x160)), q)) + mstore(Z_LOOKUP_Y_LOC, mod(calldataload(add(data_ptr, 0x180)), q)) + mstore(Z_LOOKUP_X_LOC, mod(calldataload(add(data_ptr, 0x1a0)), q)) + mstore(T1_Y_LOC, mod(calldataload(add(data_ptr, 0x1c0)), q)) + mstore(T1_X_LOC, mod(calldataload(add(data_ptr, 0x1e0)), q)) + + mstore(T2_Y_LOC, mod(calldataload(add(data_ptr, 0x200)), q)) + mstore(T2_X_LOC, mod(calldataload(add(data_ptr, 0x220)), q)) + + mstore(T3_Y_LOC, mod(calldataload(add(data_ptr, 0x240)), q)) + mstore(T3_X_LOC, mod(calldataload(add(data_ptr, 0x260)), q)) + + mstore(T4_Y_LOC, mod(calldataload(add(data_ptr, 0x280)), q)) + mstore(T4_X_LOC, mod(calldataload(add(data_ptr, 0x2a0)), q)) + + mstore(W1_EVAL_LOC, mod(calldataload(add(data_ptr, 0x2c0)), p)) + mstore(W2_EVAL_LOC, mod(calldataload(add(data_ptr, 0x2e0)), p)) + mstore(W3_EVAL_LOC, mod(calldataload(add(data_ptr, 0x300)), p)) + mstore(W4_EVAL_LOC, mod(calldataload(add(data_ptr, 0x320)), p)) + mstore(S_EVAL_LOC, mod(calldataload(add(data_ptr, 0x340)), p)) + mstore(Z_EVAL_LOC, mod(calldataload(add(data_ptr, 0x360)), p)) + mstore(Z_LOOKUP_EVAL_LOC, mod(calldataload(add(data_ptr, 0x380)), p)) + mstore(Q1_EVAL_LOC, mod(calldataload(add(data_ptr, 0x3a0)), p)) + mstore(Q2_EVAL_LOC, mod(calldataload(add(data_ptr, 0x3c0)), p)) + mstore(Q3_EVAL_LOC, mod(calldataload(add(data_ptr, 0x3e0)), p)) + mstore(Q4_EVAL_LOC, mod(calldataload(add(data_ptr, 0x400)), p)) + mstore(QM_EVAL_LOC, mod(calldataload(add(data_ptr, 0x420)), p)) + mstore(QC_EVAL_LOC, mod(calldataload(add(data_ptr, 0x440)), p)) + mstore(QARITH_EVAL_LOC, mod(calldataload(add(data_ptr, 0x460)), p)) + mstore(QSORT_EVAL_LOC, mod(calldataload(add(data_ptr, 0x480)), p)) + mstore(QELLIPTIC_EVAL_LOC, mod(calldataload(add(data_ptr, 0x4a0)), p)) + mstore(QAUX_EVAL_LOC, mod(calldataload(add(data_ptr, 0x4c0)), p)) + + mstore(SIGMA1_EVAL_LOC, mod(calldataload(add(data_ptr, 0x4e0)), p)) + mstore(SIGMA2_EVAL_LOC, mod(calldataload(add(data_ptr, 0x500)), p)) + + mstore(SIGMA3_EVAL_LOC, mod(calldataload(add(data_ptr, 0x520)), p)) + mstore(SIGMA4_EVAL_LOC, mod(calldataload(add(data_ptr, 0x540)), p)) + + mstore(TABLE1_EVAL_LOC, mod(calldataload(add(data_ptr, 0x560)), p)) + mstore(TABLE2_EVAL_LOC, mod(calldataload(add(data_ptr, 0x580)), p)) + mstore(TABLE3_EVAL_LOC, mod(calldataload(add(data_ptr, 0x5a0)), p)) + mstore(TABLE4_EVAL_LOC, mod(calldataload(add(data_ptr, 0x5c0)), p)) + mstore(TABLE_TYPE_EVAL_LOC, mod(calldataload(add(data_ptr, 0x5e0)), p)) + + mstore(ID1_EVAL_LOC, mod(calldataload(add(data_ptr, 0x600)), p)) + mstore(ID2_EVAL_LOC, mod(calldataload(add(data_ptr, 0x620)), p)) + mstore(ID3_EVAL_LOC, mod(calldataload(add(data_ptr, 0x640)), p)) + mstore(ID4_EVAL_LOC, mod(calldataload(add(data_ptr, 0x660)), p)) + + mstore(W1_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x680)), p)) + mstore(W2_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x6a0)), p)) + mstore(W3_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x6c0)), p)) + mstore(W4_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x6e0)), p)) + mstore(S_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x700)), p)) + + mstore(Z_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x720)), p)) + + mstore(Z_LOOKUP_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x740)), p)) + mstore(TABLE1_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x760)), p)) + mstore(TABLE2_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x780)), p)) + mstore(TABLE3_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x7a0)), p)) + mstore(TABLE4_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x7c0)), p)) + + mstore(PI_Z_Y_LOC, mod(calldataload(add(data_ptr, 0x7e0)), q)) + mstore(PI_Z_X_LOC, mod(calldataload(add(data_ptr, 0x800)), q)) + + mstore(PI_Z_OMEGA_Y_LOC, mod(calldataload(add(data_ptr, 0x820)), q)) + mstore(PI_Z_OMEGA_X_LOC, mod(calldataload(add(data_ptr, 0x840)), q)) + } + + /** + * LOAD RECURSIVE PROOF INTO MEMORY + */ + { + if mload(CONTAINS_RECURSIVE_PROOF_LOC) { + let public_inputs_ptr := add(calldataload(0x24), 0x24) + let index_counter := add(shl(5, mload(RECURSIVE_PROOF_PUBLIC_INPUT_INDICES_LOC)), public_inputs_ptr) + + let x0 := calldataload(index_counter) + x0 := add(x0, shl(68, calldataload(add(index_counter, 0x20)))) + x0 := add(x0, shl(136, calldataload(add(index_counter, 0x40)))) + x0 := add(x0, shl(204, calldataload(add(index_counter, 0x60)))) + let y0 := calldataload(add(index_counter, 0x80)) + y0 := add(y0, shl(68, calldataload(add(index_counter, 0xa0)))) + y0 := add(y0, shl(136, calldataload(add(index_counter, 0xc0)))) + y0 := add(y0, shl(204, calldataload(add(index_counter, 0xe0)))) + let x1 := calldataload(add(index_counter, 0x100)) + x1 := add(x1, shl(68, calldataload(add(index_counter, 0x120)))) + x1 := add(x1, shl(136, calldataload(add(index_counter, 0x140)))) + x1 := add(x1, shl(204, calldataload(add(index_counter, 0x160)))) + let y1 := calldataload(add(index_counter, 0x180)) + y1 := add(y1, shl(68, calldataload(add(index_counter, 0x1a0)))) + y1 := add(y1, shl(136, calldataload(add(index_counter, 0x1c0)))) + y1 := add(y1, shl(204, calldataload(add(index_counter, 0x1e0)))) + mstore(RECURSIVE_P1_X_LOC, x0) + mstore(RECURSIVE_P1_Y_LOC, y0) + mstore(RECURSIVE_P2_X_LOC, x1) + mstore(RECURSIVE_P2_Y_LOC, y1) + + // validate these are valid bn128 G1 points + if iszero(and(and(lt(x0, q), lt(x1, q)), and(lt(y0, q), lt(y1, q)))) { + mstore(0x00, PUBLIC_INPUT_INVALID_BN128_G1_POINT_SELECTOR) + revert(0x00, 0x04) + } + } + } + + { + /** + * Generate initial challenge + */ + mstore(0x00, shl(224, mload(N_LOC))) + mstore(0x04, shl(224, mload(NUM_INPUTS_LOC))) + let challenge := keccak256(0x00, 0x08) + + /** + * Generate eta challenge + */ + mstore(PUBLIC_INPUTS_HASH_LOCATION, challenge) + // The public input location is stored at 0x24, we then add 0x24 to skip selector and the length of public inputs + let public_inputs_start := add(calldataload(0x24), 0x24) + // copy the public inputs over + let public_input_size := mul(mload(NUM_INPUTS_LOC), 0x20) + calldatacopy(add(PUBLIC_INPUTS_HASH_LOCATION, 0x20), public_inputs_start, public_input_size) + + // copy W1, W2, W3 into challenge. Each point is 0x40 bytes, so load 0xc0 = 3 * 0x40 bytes (ETA input length) + let w_start := add(calldataload(0x04), 0x24) + calldatacopy(add(add(PUBLIC_INPUTS_HASH_LOCATION, 0x20), public_input_size), w_start, ETA_INPUT_LENGTH) + + // Challenge is the old challenge + public inputs + W1, W2, W3 (0x20 + public_input_size + 0xc0) + let challenge_bytes_size := add(0x20, add(public_input_size, ETA_INPUT_LENGTH)) + + challenge := keccak256(PUBLIC_INPUTS_HASH_LOCATION, challenge_bytes_size) + { + let eta := mod(challenge, p) + mstore(C_ETA_LOC, eta) + mstore(C_ETA_SQR_LOC, mulmod(eta, eta, p)) + mstore(C_ETA_CUBE_LOC, mulmod(mload(C_ETA_SQR_LOC), eta, p)) + } + + /** + * Generate beta challenge + */ + mstore(0x00, challenge) + mstore(0x20, mload(W4_Y_LOC)) + mstore(0x40, mload(W4_X_LOC)) + mstore(0x60, mload(S_Y_LOC)) + mstore(0x80, mload(S_X_LOC)) + challenge := keccak256(0x00, 0xa0) + mstore(C_BETA_LOC, mod(challenge, p)) + + /** + * Generate gamma challenge + */ + mstore(0x00, challenge) + mstore8(0x20, 0x01) + challenge := keccak256(0x00, 0x21) + mstore(C_GAMMA_LOC, mod(challenge, p)) + + /** + * Generate alpha challenge + */ + mstore(0x00, challenge) + mstore(0x20, mload(Z_Y_LOC)) + mstore(0x40, mload(Z_X_LOC)) + mstore(0x60, mload(Z_LOOKUP_Y_LOC)) + mstore(0x80, mload(Z_LOOKUP_X_LOC)) + challenge := keccak256(0x00, 0xa0) + mstore(C_ALPHA_LOC, mod(challenge, p)) + + /** + * Compute and store some powers of alpha for future computations + */ + let alpha := mload(C_ALPHA_LOC) + mstore(C_ALPHA_SQR_LOC, mulmod(alpha, alpha, p)) + mstore(C_ALPHA_CUBE_LOC, mulmod(mload(C_ALPHA_SQR_LOC), alpha, p)) + mstore(C_ALPHA_QUAD_LOC, mulmod(mload(C_ALPHA_CUBE_LOC), alpha, p)) + mstore(C_ALPHA_BASE_LOC, alpha) + + /** + * Generate zeta challenge + */ + mstore(0x00, challenge) + mstore(0x20, mload(T1_Y_LOC)) + mstore(0x40, mload(T1_X_LOC)) + mstore(0x60, mload(T2_Y_LOC)) + mstore(0x80, mload(T2_X_LOC)) + mstore(0xa0, mload(T3_Y_LOC)) + mstore(0xc0, mload(T3_X_LOC)) + mstore(0xe0, mload(T4_Y_LOC)) + mstore(0x100, mload(T4_X_LOC)) + + challenge := keccak256(0x00, 0x120) + + mstore(C_ZETA_LOC, mod(challenge, p)) + mstore(C_CURRENT_LOC, challenge) + } + + /** + * EVALUATE FIELD OPERATIONS + */ + + /** + * COMPUTE PUBLIC INPUT DELTA + * ΔPI = âˆáµ¢âˆˆâ„“(wáµ¢ + β σ(i) + γ) / âˆáµ¢âˆˆâ„“(wáµ¢ + β σ'(i) + γ) + */ + { + let beta := mload(C_BETA_LOC) // β + let gamma := mload(C_GAMMA_LOC) // γ + let work_root := mload(OMEGA_LOC) // ω + let numerator_value := 1 + let denominator_value := 1 + + let p_clone := p // move p to the front of the stack + let valid_inputs := true + + // Load the starting point of the public inputs (jump over the selector and the length of public inputs [0x24]) + let public_inputs_ptr := add(calldataload(0x24), 0x24) + + // endpoint_ptr = public_inputs_ptr + num_inputs * 0x20. // every public input is 0x20 bytes + let endpoint_ptr := add(public_inputs_ptr, mul(mload(NUM_INPUTS_LOC), 0x20)) + + // root_1 = β * 0x05 + let root_1 := mulmod(beta, 0x05, p_clone) // k1.β + // root_2 = β * 0x0c + let root_2 := mulmod(beta, 0x0c, p_clone) + // @note 0x05 + 0x07 == 0x0c == external coset generator + + for {} lt(public_inputs_ptr, endpoint_ptr) { public_inputs_ptr := add(public_inputs_ptr, 0x20) } { + /** + * input = public_input[i] + * valid_inputs &= input < p + * temp = input + gamma + * numerator_value *= (β.σ(i) + wáµ¢ + γ) // σ(i) = 0x05.ωⱠ+ * denominator_value *= (β.σ'(i) + wáµ¢ + γ) // σ'(i) = 0x0c.ωⱠ+ * root_1 *= ω + * root_2 *= ω + */ + + let input := calldataload(public_inputs_ptr) + valid_inputs := and(valid_inputs, lt(input, p_clone)) + let temp := addmod(input, gamma, p_clone) + + numerator_value := mulmod(numerator_value, add(root_1, temp), p_clone) + denominator_value := mulmod(denominator_value, add(root_2, temp), p_clone) + + root_1 := mulmod(root_1, work_root, p_clone) + root_2 := mulmod(root_2, work_root, p_clone) + } + + // Revert if not all public inputs are field elements (i.e. < p) + if iszero(valid_inputs) { + mstore(0x00, PUBLIC_INPUT_GE_P_SELECTOR) + revert(0x00, 0x04) + } + + mstore(DELTA_NUMERATOR_LOC, numerator_value) + mstore(DELTA_DENOMINATOR_LOC, denominator_value) + } + + /** + * Compute Plookup delta factor [γ(1 + β)]^{n-k} + * k = num roots cut out of Z_H = 4 + */ + { + let delta_base := mulmod(mload(C_GAMMA_LOC), addmod(mload(C_BETA_LOC), 1, p), p) + let delta_numerator := delta_base + { + let exponent := mload(N_LOC) + let count := 1 + for {} lt(count, exponent) { count := add(count, count) } { + delta_numerator := mulmod(delta_numerator, delta_numerator, p) + } + } + mstore(PLOOKUP_DELTA_NUMERATOR_LOC, delta_numerator) + + let delta_denominator := mulmod(delta_base, delta_base, p) + delta_denominator := mulmod(delta_denominator, delta_denominator, p) + mstore(PLOOKUP_DELTA_DENOMINATOR_LOC, delta_denominator) + } + /** + * Compute lagrange poly and vanishing poly fractions + */ + { + /** + * vanishing_numerator = zeta + * ZETA_POW_N = zeta^n + * vanishing_numerator -= 1 + * accumulating_root = omega_inverse + * work_root = p - accumulating_root + * domain_inverse = domain_inverse + * vanishing_denominator = zeta + work_root + * work_root *= accumulating_root + * vanishing_denominator *= (zeta + work_root) + * work_root *= accumulating_root + * vanishing_denominator *= (zeta + work_root) + * vanishing_denominator *= (zeta + (zeta + accumulating_root)) + * work_root = omega + * lagrange_numerator = vanishing_numerator * domain_inverse + * l_start_denominator = zeta - 1 + * accumulating_root = work_root^2 + * l_end_denominator = accumulating_root^2 * work_root * zeta - 1 + * Note: l_end_denominator term contains a term \omega^5 to cut out 5 roots of unity from vanishing poly + */ + + let zeta := mload(C_ZETA_LOC) + + // compute zeta^n, where n is a power of 2 + let vanishing_numerator := zeta + { + // pow_small + let exponent := mload(N_LOC) + let count := 1 + for {} lt(count, exponent) { count := add(count, count) } { + vanishing_numerator := mulmod(vanishing_numerator, vanishing_numerator, p) + } + } + mstore(ZETA_POW_N_LOC, vanishing_numerator) + vanishing_numerator := addmod(vanishing_numerator, sub(p, 1), p) + + let accumulating_root := mload(OMEGA_INVERSE_LOC) + let work_root := sub(p, accumulating_root) + let domain_inverse := mload(DOMAIN_INVERSE_LOC) + + let vanishing_denominator := addmod(zeta, work_root, p) + work_root := mulmod(work_root, accumulating_root, p) + vanishing_denominator := mulmod(vanishing_denominator, addmod(zeta, work_root, p), p) + work_root := mulmod(work_root, accumulating_root, p) + vanishing_denominator := mulmod(vanishing_denominator, addmod(zeta, work_root, p), p) + vanishing_denominator := + mulmod(vanishing_denominator, addmod(zeta, mulmod(work_root, accumulating_root, p), p), p) + + work_root := mload(OMEGA_LOC) + + let lagrange_numerator := mulmod(vanishing_numerator, domain_inverse, p) + let l_start_denominator := addmod(zeta, sub(p, 1), p) + + accumulating_root := mulmod(work_root, work_root, p) + + let l_end_denominator := + addmod( + mulmod(mulmod(mulmod(accumulating_root, accumulating_root, p), work_root, p), zeta, p), sub(p, 1), p + ) + + /** + * Compute inversions using Montgomery's batch inversion trick + */ + let accumulator := mload(DELTA_DENOMINATOR_LOC) + let t0 := accumulator + accumulator := mulmod(accumulator, vanishing_denominator, p) + let t1 := accumulator + accumulator := mulmod(accumulator, vanishing_numerator, p) + let t2 := accumulator + accumulator := mulmod(accumulator, l_start_denominator, p) + let t3 := accumulator + accumulator := mulmod(accumulator, mload(PLOOKUP_DELTA_DENOMINATOR_LOC), p) + let t4 := accumulator + { + mstore(0, 0x20) + mstore(0x20, 0x20) + mstore(0x40, 0x20) + mstore(0x60, mulmod(accumulator, l_end_denominator, p)) + mstore(0x80, sub(p, 2)) + mstore(0xa0, p) + if iszero(staticcall(gas(), 0x05, 0x00, 0xc0, 0x00, 0x20)) { + mstore(0x0, MOD_EXP_FAILURE_SELECTOR) + revert(0x00, 0x04) + } + accumulator := mload(0x00) + } + + t4 := mulmod(accumulator, t4, p) + accumulator := mulmod(accumulator, l_end_denominator, p) + + t3 := mulmod(accumulator, t3, p) + accumulator := mulmod(accumulator, mload(PLOOKUP_DELTA_DENOMINATOR_LOC), p) + + t2 := mulmod(accumulator, t2, p) + accumulator := mulmod(accumulator, l_start_denominator, p) + + t1 := mulmod(accumulator, t1, p) + accumulator := mulmod(accumulator, vanishing_numerator, p) + + t0 := mulmod(accumulator, t0, p) + accumulator := mulmod(accumulator, vanishing_denominator, p) + + accumulator := mulmod(mulmod(accumulator, accumulator, p), mload(DELTA_DENOMINATOR_LOC), p) + + mstore(PUBLIC_INPUT_DELTA_LOC, mulmod(mload(DELTA_NUMERATOR_LOC), accumulator, p)) + mstore(ZERO_POLY_LOC, mulmod(vanishing_numerator, t0, p)) + mstore(ZERO_POLY_INVERSE_LOC, mulmod(vanishing_denominator, t1, p)) + mstore(L_START_LOC, mulmod(lagrange_numerator, t2, p)) + mstore(PLOOKUP_DELTA_LOC, mulmod(mload(PLOOKUP_DELTA_NUMERATOR_LOC), t3, p)) + mstore(L_END_LOC, mulmod(lagrange_numerator, t4, p)) + } + + /** + * UltraPlonk Widget Ordering: + * + * 1. Permutation widget + * 2. Plookup widget + * 3. Arithmetic widget + * 4. Fixed base widget (?) + * 5. GenPermSort widget + * 6. Elliptic widget + * 7. Auxiliary widget + */ + + /** + * COMPUTE PERMUTATION WIDGET EVALUATION + */ + { + let alpha := mload(C_ALPHA_LOC) + let beta := mload(C_BETA_LOC) + let gamma := mload(C_GAMMA_LOC) + + /** + * t1 = (W1 + gamma + beta * ID1) * (W2 + gamma + beta * ID2) + * t2 = (W3 + gamma + beta * ID3) * (W4 + gamma + beta * ID4) + * result = alpha_base * z_eval * t1 * t2 + * t1 = (W1 + gamma + beta * sigma_1_eval) * (W2 + gamma + beta * sigma_2_eval) + * t2 = (W2 + gamma + beta * sigma_3_eval) * (W3 + gamma + beta * sigma_4_eval) + * result -= (alpha_base * z_omega_eval * t1 * t2) + */ + let t1 := + mulmod( + add(add(mload(W1_EVAL_LOC), gamma), mulmod(beta, mload(ID1_EVAL_LOC), p)), + add(add(mload(W2_EVAL_LOC), gamma), mulmod(beta, mload(ID2_EVAL_LOC), p)), + p + ) + let t2 := + mulmod( + add(add(mload(W3_EVAL_LOC), gamma), mulmod(beta, mload(ID3_EVAL_LOC), p)), + add(add(mload(W4_EVAL_LOC), gamma), mulmod(beta, mload(ID4_EVAL_LOC), p)), + p + ) + let result := mulmod(mload(C_ALPHA_BASE_LOC), mulmod(mload(Z_EVAL_LOC), mulmod(t1, t2, p), p), p) + t1 := + mulmod( + add(add(mload(W1_EVAL_LOC), gamma), mulmod(beta, mload(SIGMA1_EVAL_LOC), p)), + add(add(mload(W2_EVAL_LOC), gamma), mulmod(beta, mload(SIGMA2_EVAL_LOC), p)), + p + ) + t2 := + mulmod( + add(add(mload(W3_EVAL_LOC), gamma), mulmod(beta, mload(SIGMA3_EVAL_LOC), p)), + add(add(mload(W4_EVAL_LOC), gamma), mulmod(beta, mload(SIGMA4_EVAL_LOC), p)), + p + ) + result := + addmod( + result, + sub(p, mulmod(mload(C_ALPHA_BASE_LOC), mulmod(mload(Z_OMEGA_EVAL_LOC), mulmod(t1, t2, p), p), p)), + p + ) + + /** + * alpha_base *= alpha + * result += alpha_base . (L_{n-k}(Ê“) . (z(Ê“.ω) - ∆_{PI})) + * alpha_base *= alpha + * result += alpha_base . (L_1(Ê“)(Z(Ê“) - 1)) + * alpha_Base *= alpha + */ + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p)) + result := + addmod( + result, + mulmod( + mload(C_ALPHA_BASE_LOC), + mulmod( + mload(L_END_LOC), + addmod(mload(Z_OMEGA_EVAL_LOC), sub(p, mload(PUBLIC_INPUT_DELTA_LOC)), p), + p + ), + p + ), + p + ) + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p)) + mstore( + PERMUTATION_IDENTITY, + addmod( + result, + mulmod( + mload(C_ALPHA_BASE_LOC), + mulmod(mload(L_START_LOC), addmod(mload(Z_EVAL_LOC), sub(p, 1), p), p), + p + ), + p + ) + ) + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p)) + } + + /** + * COMPUTE PLOOKUP WIDGET EVALUATION + */ + { + /** + * Goal: f = (w1(z) + q2.w1(zω)) + η(w2(z) + qm.w2(zω)) + η²(w3(z) + qc.w_3(zω)) + q3(z).η³ + * f = η.q3(z) + * f += (w3(z) + qc.w_3(zω)) + * f *= η + * f += (w2(z) + qm.w2(zω)) + * f *= η + * f += (w1(z) + q2.w1(zω)) + */ + let f := mulmod(mload(C_ETA_LOC), mload(Q3_EVAL_LOC), p) + f := + addmod(f, addmod(mload(W3_EVAL_LOC), mulmod(mload(QC_EVAL_LOC), mload(W3_OMEGA_EVAL_LOC), p), p), p) + f := mulmod(f, mload(C_ETA_LOC), p) + f := + addmod(f, addmod(mload(W2_EVAL_LOC), mulmod(mload(QM_EVAL_LOC), mload(W2_OMEGA_EVAL_LOC), p), p), p) + f := mulmod(f, mload(C_ETA_LOC), p) + f := + addmod(f, addmod(mload(W1_EVAL_LOC), mulmod(mload(Q2_EVAL_LOC), mload(W1_OMEGA_EVAL_LOC), p), p), p) + + // t(z) = table4(z).η³ + table3(z).η² + table2(z).η + table1(z) + let t := + addmod( + addmod( + addmod( + mulmod(mload(TABLE4_EVAL_LOC), mload(C_ETA_CUBE_LOC), p), + mulmod(mload(TABLE3_EVAL_LOC), mload(C_ETA_SQR_LOC), p), + p + ), + mulmod(mload(TABLE2_EVAL_LOC), mload(C_ETA_LOC), p), + p + ), + mload(TABLE1_EVAL_LOC), + p + ) + + // t(zw) = table4(zw).η³ + table3(zw).η² + table2(zw).η + table1(zw) + let t_omega := + addmod( + addmod( + addmod( + mulmod(mload(TABLE4_OMEGA_EVAL_LOC), mload(C_ETA_CUBE_LOC), p), + mulmod(mload(TABLE3_OMEGA_EVAL_LOC), mload(C_ETA_SQR_LOC), p), + p + ), + mulmod(mload(TABLE2_OMEGA_EVAL_LOC), mload(C_ETA_LOC), p), + p + ), + mload(TABLE1_OMEGA_EVAL_LOC), + p + ) + + /** + * Goal: numerator = (TABLE_TYPE_EVAL * f(z) + γ) * (t(z) + βt(zω) + γ(β + 1)) * (β + 1) + * gamma_beta_constant = γ(β + 1) + * numerator = f * TABLE_TYPE_EVAL + gamma + * temp0 = t(z) + t(zω) * β + gamma_beta_constant + * numerator *= temp0 + * numerator *= (β + 1) + * temp0 = alpha * l_1 + * numerator += temp0 + * numerator *= z_lookup(z) + * numerator -= temp0 + */ + let gamma_beta_constant := mulmod(mload(C_GAMMA_LOC), addmod(mload(C_BETA_LOC), 1, p), p) + let numerator := addmod(mulmod(f, mload(TABLE_TYPE_EVAL_LOC), p), mload(C_GAMMA_LOC), p) + let temp0 := addmod(addmod(t, mulmod(t_omega, mload(C_BETA_LOC), p), p), gamma_beta_constant, p) + numerator := mulmod(numerator, temp0, p) + numerator := mulmod(numerator, addmod(mload(C_BETA_LOC), 1, p), p) + temp0 := mulmod(mload(C_ALPHA_LOC), mload(L_START_LOC), p) + numerator := addmod(numerator, temp0, p) + numerator := mulmod(numerator, mload(Z_LOOKUP_EVAL_LOC), p) + numerator := addmod(numerator, sub(p, temp0), p) + + /** + * Goal: denominator = z_lookup(zω)*[s(z) + βs(zω) + γ(1 + β)] - [z_lookup(zω) - [γ(1 + β)]^{n-k}]*α²L_end(z) + * note: delta_factor = [γ(1 + β)]^{n-k} + * denominator = s(z) + βs(zω) + γ(β + 1) + * temp1 = α²L_end(z) + * denominator -= temp1 + * denominator *= z_lookup(zω) + * denominator += temp1 * delta_factor + * PLOOKUP_IDENTITY = (numerator - denominator).alpha_base + * alpha_base *= alpha^3 + */ + let denominator := + addmod( + addmod(mload(S_EVAL_LOC), mulmod(mload(S_OMEGA_EVAL_LOC), mload(C_BETA_LOC), p), p), + gamma_beta_constant, + p + ) + let temp1 := mulmod(mload(C_ALPHA_SQR_LOC), mload(L_END_LOC), p) + denominator := addmod(denominator, sub(p, temp1), p) + denominator := mulmod(denominator, mload(Z_LOOKUP_OMEGA_EVAL_LOC), p) + denominator := addmod(denominator, mulmod(temp1, mload(PLOOKUP_DELTA_LOC), p), p) + + mstore(PLOOKUP_IDENTITY, mulmod(addmod(numerator, sub(p, denominator), p), mload(C_ALPHA_BASE_LOC), p)) + + // update alpha + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_CUBE_LOC), p)) + } + + /** + * COMPUTE ARITHMETIC WIDGET EVALUATION + */ + { + /** + * The basic arithmetic gate identity in standard plonk is as follows. + * (w_1 . w_2 . q_m) + (w_1 . q_1) + (w_2 . q_2) + (w_3 . q_3) + (w_4 . q_4) + q_c = 0 + * However, for Ultraplonk, we extend this to support "passing" wires between rows (shown without alpha scaling below): + * q_arith * ( ( (-1/2) * (q_arith - 3) * q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c ) + + * (q_arith - 1)*( α * (q_arith - 2) * (w_1 + w_4 - w_1_omega + q_m) + w_4_omega) ) = 0 + * + * This formula results in several cases depending on q_arith: + * 1. q_arith == 0: Arithmetic gate is completely disabled + * + * 2. q_arith == 1: Everything in the minigate on the right is disabled. The equation is just a standard plonk equation + * with extra wires: q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c = 0 + * + * 3. q_arith == 2: The (w_1 + w_4 - ...) term is disabled. THe equation is: + * (1/2) * q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + w_4_omega = 0 + * It allows defining w_4 at next index (w_4_omega) in terms of current wire values + * + * 4. q_arith == 3: The product of w_1 and w_2 is disabled, but a mini addition gate is enabled. α allows us to split + * the equation into two: + * + * q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + 2 * w_4_omega = 0 + * and + * w_1 + w_4 - w_1_omega + q_m = 0 (we are reusing q_m here) + * + * 5. q_arith > 3: The product of w_1 and w_2 is scaled by (q_arith - 3), while the w_4_omega term is scaled by (q_arith - 1). + * The equation can be split into two: + * + * (q_arith - 3)* q_m * w_1 * w_ 2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + (q_arith - 1) * w_4_omega = 0 + * and + * w_1 + w_4 - w_1_omega + q_m = 0 + * + * The problem that q_m is used both in both equations can be dealt with by appropriately changing selector values at + * the next gate. Then we can treat (q_arith - 1) as a simulated q_6 selector and scale q_m to handle (q_arith - 3) at + * product. + */ + + let w1q1 := mulmod(mload(W1_EVAL_LOC), mload(Q1_EVAL_LOC), p) + let w2q2 := mulmod(mload(W2_EVAL_LOC), mload(Q2_EVAL_LOC), p) + let w3q3 := mulmod(mload(W3_EVAL_LOC), mload(Q3_EVAL_LOC), p) + let w4q3 := mulmod(mload(W4_EVAL_LOC), mload(Q4_EVAL_LOC), p) + + // @todo - Add a explicit test that hits QARITH == 3 + // w1w2qm := (w_1 . w_2 . q_m . (QARITH_EVAL_LOC - 3)) / 2 + let w1w2qm := + mulmod( + mulmod( + mulmod(mulmod(mload(W1_EVAL_LOC), mload(W2_EVAL_LOC), p), mload(QM_EVAL_LOC), p), + addmod(mload(QARITH_EVAL_LOC), sub(p, 3), p), + p + ), + NEGATIVE_INVERSE_OF_2_MODULO_P, + p + ) + + // (w_1 . w_2 . q_m . (q_arith - 3)) / -2) + (w_1 . q_1) + (w_2 . q_2) + (w_3 . q_3) + (w_4 . q_4) + q_c + let identity := + addmod( + mload(QC_EVAL_LOC), addmod(w4q3, addmod(w3q3, addmod(w2q2, addmod(w1q1, w1w2qm, p), p), p), p), p + ) + + // if q_arith == 3 we evaluate an additional mini addition gate (on top of the regular one), where: + // w_1 + w_4 - w_1_omega + q_m = 0 + // we use this gate to save an addition gate when adding or subtracting non-native field elements + // α * (q_arith - 2) * (w_1 + w_4 - w_1_omega + q_m) + let extra_small_addition_gate_identity := + mulmod( + mload(C_ALPHA_LOC), + mulmod( + addmod(mload(QARITH_EVAL_LOC), sub(p, 2), p), + addmod( + mload(QM_EVAL_LOC), + addmod( + sub(p, mload(W1_OMEGA_EVAL_LOC)), addmod(mload(W1_EVAL_LOC), mload(W4_EVAL_LOC), p), p + ), + p + ), + p + ), + p + ) + + // if q_arith == 2 OR q_arith == 3 we add the 4th wire of the NEXT gate into the arithmetic identity + // N.B. if q_arith > 2, this wire value will be scaled by (q_arith - 1) relative to the other gate wires! + // alpha_base * q_arith * (identity + (q_arith - 1) * (w_4_omega + extra_small_addition_gate_identity)) + mstore( + ARITHMETIC_IDENTITY, + mulmod( + mload(C_ALPHA_BASE_LOC), + mulmod( + mload(QARITH_EVAL_LOC), + addmod( + identity, + mulmod( + addmod(mload(QARITH_EVAL_LOC), sub(p, 1), p), + addmod(mload(W4_OMEGA_EVAL_LOC), extra_small_addition_gate_identity, p), + p + ), + p + ), + p + ), + p + ) + ) + + // update alpha + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_SQR_LOC), p)) + } + + /** + * COMPUTE GENPERMSORT WIDGET EVALUATION + */ + { + /** + * D1 = (w2 - w1) + * D2 = (w3 - w2) + * D3 = (w4 - w3) + * D4 = (w1_omega - w4) + * + * α_a = alpha_base + * α_b = alpha_base * α + * α_c = alpha_base * α^2 + * α_d = alpha_base * α^3 + * + * range_accumulator = ( + * D1(D1 - 1)(D1 - 2)(D1 - 3).α_a + + * D2(D2 - 1)(D2 - 2)(D2 - 3).α_b + + * D3(D3 - 1)(D3 - 2)(D3 - 3).α_c + + * D4(D4 - 1)(D4 - 2)(D4 - 3).α_d + + * ) . q_sort + */ + let minus_two := sub(p, 2) + let minus_three := sub(p, 3) + let d1 := addmod(mload(W2_EVAL_LOC), sub(p, mload(W1_EVAL_LOC)), p) + let d2 := addmod(mload(W3_EVAL_LOC), sub(p, mload(W2_EVAL_LOC)), p) + let d3 := addmod(mload(W4_EVAL_LOC), sub(p, mload(W3_EVAL_LOC)), p) + let d4 := addmod(mload(W1_OMEGA_EVAL_LOC), sub(p, mload(W4_EVAL_LOC)), p) + + let range_accumulator := + mulmod( + mulmod( + mulmod(addmod(mulmod(d1, d1, p), sub(p, d1), p), addmod(d1, minus_two, p), p), + addmod(d1, minus_three, p), + p + ), + mload(C_ALPHA_BASE_LOC), + p + ) + range_accumulator := + addmod( + range_accumulator, + mulmod( + mulmod( + mulmod(addmod(mulmod(d2, d2, p), sub(p, d2), p), addmod(d2, minus_two, p), p), + addmod(d2, minus_three, p), + p + ), + mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p), + p + ), + p + ) + range_accumulator := + addmod( + range_accumulator, + mulmod( + mulmod( + mulmod(addmod(mulmod(d3, d3, p), sub(p, d3), p), addmod(d3, minus_two, p), p), + addmod(d3, minus_three, p), + p + ), + mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_SQR_LOC), p), + p + ), + p + ) + range_accumulator := + addmod( + range_accumulator, + mulmod( + mulmod( + mulmod(addmod(mulmod(d4, d4, p), sub(p, d4), p), addmod(d4, minus_two, p), p), + addmod(d4, minus_three, p), + p + ), + mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_CUBE_LOC), p), + p + ), + p + ) + range_accumulator := mulmod(range_accumulator, mload(QSORT_EVAL_LOC), p) + + mstore(SORT_IDENTITY, range_accumulator) + + // update alpha + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_QUAD_LOC), p)) + } + + /** + * COMPUTE ELLIPTIC WIDGET EVALUATION + */ + { + /** + * endo_term = (-x_2) * x_1 * (x_3 * 2 + x_1) * q_beta + * endo_sqr_term = x_2^2 + * endo_sqr_term *= (x_3 - x_1) + * endo_sqr_term *= q_beta^2 + * leftovers = x_2^2 + * leftovers *= x_2 + * leftovers += x_1^2 * (x_3 + x_1) @follow-up Invalid comment in BB widget + * leftovers -= (y_2^2 + y_1^2) + * sign_term = y_2 * y_1 + * sign_term += sign_term + * sign_term *= q_sign + */ + + let endo_term := + mulmod( + mulmod( + mulmod(sub(p, mload(X2_EVAL_LOC)), mload(X1_EVAL_LOC), p), + addmod(addmod(mload(X3_EVAL_LOC), mload(X3_EVAL_LOC), p), mload(X1_EVAL_LOC), p), + p + ), + mload(QBETA_LOC), + p + ) + + let endo_sqr_term := mulmod(mload(X2_EVAL_LOC), mload(X2_EVAL_LOC), p) + endo_sqr_term := mulmod(endo_sqr_term, addmod(mload(X3_EVAL_LOC), sub(p, mload(X1_EVAL_LOC)), p), p) + endo_sqr_term := mulmod(endo_sqr_term, mload(QBETA_SQR_LOC), p) + + let leftovers := mulmod(mload(X2_EVAL_LOC), mload(X2_EVAL_LOC), p) + leftovers := mulmod(leftovers, mload(X2_EVAL_LOC), p) + leftovers := + addmod( + leftovers, + mulmod( + mulmod(mload(X1_EVAL_LOC), mload(X1_EVAL_LOC), p), + addmod(mload(X3_EVAL_LOC), mload(X1_EVAL_LOC), p), + p + ), + p + ) + leftovers := + addmod( + leftovers, + sub( + p, + addmod( + mulmod(mload(Y2_EVAL_LOC), mload(Y2_EVAL_LOC), p), + mulmod(mload(Y1_EVAL_LOC), mload(Y1_EVAL_LOC), p), + p + ) + ), + p + ) + + let sign_term := mulmod(mload(Y2_EVAL_LOC), mload(Y1_EVAL_LOC), p) + sign_term := addmod(sign_term, sign_term, p) + sign_term := mulmod(sign_term, mload(QSIGN_LOC), p) + + /** + * x_identity = endo_term + endo_sqr_term + sign_term + leftovers + * x_identity *= alpha_base + * endo_term = (x_2 * q_beta) * (y_3 + y_1) + * sign_term = -((y2 * q_sign) * (x_1 + x_3)) + * leftovers = - x1 * (y_3 + y_1) + y_1 * (x_1 - x_3) + * y_identity = (endo_term + sign_term + leftovers) * (alpha_base * α) + */ + + let x_identity := addmod(addmod(endo_term, endo_sqr_term, p), addmod(sign_term, leftovers, p), p) + x_identity := mulmod(x_identity, mload(C_ALPHA_BASE_LOC), p) + endo_term := + mulmod( + mulmod(mload(X2_EVAL_LOC), mload(QBETA_LOC), p), + addmod(mload(Y3_EVAL_LOC), mload(Y1_EVAL_LOC), p), + p + ) + sign_term := + sub( + p, + mulmod( + mulmod(mload(Y2_EVAL_LOC), mload(QSIGN_LOC), p), + addmod(mload(X1_EVAL_LOC), sub(p, mload(X3_EVAL_LOC)), p), + p + ) + ) + leftovers := + addmod( + sub(p, mulmod(mload(X1_EVAL_LOC), addmod(mload(Y3_EVAL_LOC), mload(Y1_EVAL_LOC), p), p)), + mulmod(mload(Y1_EVAL_LOC), addmod(mload(X1_EVAL_LOC), sub(p, mload(X3_EVAL_LOC)), p), p), + p + ) + let y_identity := + mulmod( + addmod(addmod(endo_term, sign_term, p), leftovers, p), + mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p), + p + ) + + // ELLIPTIC_IDENTITY = (x_identity + y_identity) * Q_ELLIPTIC_EVAL + mstore(ELLIPTIC_IDENTITY, mulmod(addmod(x_identity, y_identity, p), mload(QELLIPTIC_EVAL_LOC), p)) + + // update alpha + // The paper says to use ALPHA^2, we use ALPHA^4 this is a small oversight in the prover protocol + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_QUAD_LOC), p)) + } + + /** + * COMPUTE AUXILIARY WIDGET EVALUATION + */ + { + { + /** + * Non native field arithmetic gate 2 + * _ _ + * / _ _ _ 14 \ + * q_2 . q_4 | (w_1 . w_2) + (w_1 . w_2) + (w_1 . w_4 + w_2 . w_3 - w_3) . 2 - w_3 - w_4 | + * \_ _/ + * + * limb_subproduct = w_1 . w_2_omega + w_1_omega . w_2 + * non_native_field_gate_2 = w_1 * w_4 + w_4 * w_3 - w_3_omega + * non_native_field_gate_2 = non_native_field_gate_2 * limb_size + * non_native_field_gate_2 -= w_4_omega + * non_native_field_gate_2 += limb_subproduct + * non_native_field_gate_2 *= q_4 + * limb_subproduct *= limb_size + * limb_subproduct += w_1_omega * w_2_omega + * non_native_field_gate_1 = (limb_subproduct + w_3 + w_4) * q_3 + * non_native_field_gate_3 = (limb_subproduct + w_4 - (w_3_omega + w_4_omega)) * q_m + * non_native_field_identity = (non_native_field_gate_1 + non_native_field_gate_2 + non_native_field_gate_3) * q_2 + */ + + let limb_subproduct := + addmod( + mulmod(mload(W1_EVAL_LOC), mload(W2_OMEGA_EVAL_LOC), p), + mulmod(mload(W1_OMEGA_EVAL_LOC), mload(W2_EVAL_LOC), p), + p + ) + + let non_native_field_gate_2 := + addmod( + addmod( + mulmod(mload(W1_EVAL_LOC), mload(W4_EVAL_LOC), p), + mulmod(mload(W2_EVAL_LOC), mload(W3_EVAL_LOC), p), + p + ), + sub(p, mload(W3_OMEGA_EVAL_LOC)), + p + ) + non_native_field_gate_2 := mulmod(non_native_field_gate_2, LIMB_SIZE, p) + non_native_field_gate_2 := addmod(non_native_field_gate_2, sub(p, mload(W4_OMEGA_EVAL_LOC)), p) + non_native_field_gate_2 := addmod(non_native_field_gate_2, limb_subproduct, p) + non_native_field_gate_2 := mulmod(non_native_field_gate_2, mload(Q4_EVAL_LOC), p) + limb_subproduct := mulmod(limb_subproduct, LIMB_SIZE, p) + limb_subproduct := + addmod(limb_subproduct, mulmod(mload(W1_OMEGA_EVAL_LOC), mload(W2_OMEGA_EVAL_LOC), p), p) + let non_native_field_gate_1 := + mulmod( + addmod(limb_subproduct, sub(p, addmod(mload(W3_EVAL_LOC), mload(W4_EVAL_LOC), p)), p), + mload(Q3_EVAL_LOC), + p + ) + let non_native_field_gate_3 := + mulmod( + addmod( + addmod(limb_subproduct, mload(W4_EVAL_LOC), p), + sub(p, addmod(mload(W3_OMEGA_EVAL_LOC), mload(W4_OMEGA_EVAL_LOC), p)), + p + ), + mload(QM_EVAL_LOC), + p + ) + let non_native_field_identity := + mulmod( + addmod(addmod(non_native_field_gate_1, non_native_field_gate_2, p), non_native_field_gate_3, p), + mload(Q2_EVAL_LOC), + p + ) + + mstore(AUX_NON_NATIVE_FIELD_EVALUATION, non_native_field_identity) + } + + { + /** + * limb_accumulator_1 = w_2_omega; + * limb_accumulator_1 *= SUBLIMB_SHIFT; + * limb_accumulator_1 += w_1_omega; + * limb_accumulator_1 *= SUBLIMB_SHIFT; + * limb_accumulator_1 += w_3; + * limb_accumulator_1 *= SUBLIMB_SHIFT; + * limb_accumulator_1 += w_2; + * limb_accumulator_1 *= SUBLIMB_SHIFT; + * limb_accumulator_1 += w_1; + * limb_accumulator_1 -= w_4; + * limb_accumulator_1 *= q_4; + */ + let limb_accumulator_1 := mulmod(mload(W2_OMEGA_EVAL_LOC), SUBLIMB_SHIFT, p) + limb_accumulator_1 := addmod(limb_accumulator_1, mload(W1_OMEGA_EVAL_LOC), p) + limb_accumulator_1 := mulmod(limb_accumulator_1, SUBLIMB_SHIFT, p) + limb_accumulator_1 := addmod(limb_accumulator_1, mload(W3_EVAL_LOC), p) + limb_accumulator_1 := mulmod(limb_accumulator_1, SUBLIMB_SHIFT, p) + limb_accumulator_1 := addmod(limb_accumulator_1, mload(W2_EVAL_LOC), p) + limb_accumulator_1 := mulmod(limb_accumulator_1, SUBLIMB_SHIFT, p) + limb_accumulator_1 := addmod(limb_accumulator_1, mload(W1_EVAL_LOC), p) + limb_accumulator_1 := addmod(limb_accumulator_1, sub(p, mload(W4_EVAL_LOC)), p) + limb_accumulator_1 := mulmod(limb_accumulator_1, mload(Q4_EVAL_LOC), p) + + /** + * limb_accumulator_2 = w_3_omega; + * limb_accumulator_2 *= SUBLIMB_SHIFT; + * limb_accumulator_2 += w_2_omega; + * limb_accumulator_2 *= SUBLIMB_SHIFT; + * limb_accumulator_2 += w_1_omega; + * limb_accumulator_2 *= SUBLIMB_SHIFT; + * limb_accumulator_2 += w_4; + * limb_accumulator_2 *= SUBLIMB_SHIFT; + * limb_accumulator_2 += w_3; + * limb_accumulator_2 -= w_4_omega; + * limb_accumulator_2 *= q_m; + */ + let limb_accumulator_2 := mulmod(mload(W3_OMEGA_EVAL_LOC), SUBLIMB_SHIFT, p) + limb_accumulator_2 := addmod(limb_accumulator_2, mload(W2_OMEGA_EVAL_LOC), p) + limb_accumulator_2 := mulmod(limb_accumulator_2, SUBLIMB_SHIFT, p) + limb_accumulator_2 := addmod(limb_accumulator_2, mload(W1_OMEGA_EVAL_LOC), p) + limb_accumulator_2 := mulmod(limb_accumulator_2, SUBLIMB_SHIFT, p) + limb_accumulator_2 := addmod(limb_accumulator_2, mload(W4_EVAL_LOC), p) + limb_accumulator_2 := mulmod(limb_accumulator_2, SUBLIMB_SHIFT, p) + limb_accumulator_2 := addmod(limb_accumulator_2, mload(W3_EVAL_LOC), p) + limb_accumulator_2 := addmod(limb_accumulator_2, sub(p, mload(W4_OMEGA_EVAL_LOC)), p) + limb_accumulator_2 := mulmod(limb_accumulator_2, mload(QM_EVAL_LOC), p) + + mstore( + AUX_LIMB_ACCUMULATOR_EVALUATION, + mulmod(addmod(limb_accumulator_1, limb_accumulator_2, p), mload(Q3_EVAL_LOC), p) + ) + } + + { + /** + * memory_record_check = w_3; + * memory_record_check *= eta; + * memory_record_check += w_2; + * memory_record_check *= eta; + * memory_record_check += w_1; + * memory_record_check *= eta; + * memory_record_check += q_c; + * + * partial_record_check = memory_record_check; + * + * memory_record_check -= w_4; + */ + + let memory_record_check := mulmod(mload(W3_EVAL_LOC), mload(C_ETA_LOC), p) + memory_record_check := addmod(memory_record_check, mload(W2_EVAL_LOC), p) + memory_record_check := mulmod(memory_record_check, mload(C_ETA_LOC), p) + memory_record_check := addmod(memory_record_check, mload(W1_EVAL_LOC), p) + memory_record_check := mulmod(memory_record_check, mload(C_ETA_LOC), p) + memory_record_check := addmod(memory_record_check, mload(QC_EVAL_LOC), p) + + let partial_record_check := memory_record_check + memory_record_check := addmod(memory_record_check, sub(p, mload(W4_EVAL_LOC)), p) + + mstore(AUX_MEMORY_EVALUATION, memory_record_check) + + // index_delta = w_1_omega - w_1 + let index_delta := addmod(mload(W1_OMEGA_EVAL_LOC), sub(p, mload(W1_EVAL_LOC)), p) + // record_delta = w_4_omega - w_4 + let record_delta := addmod(mload(W4_OMEGA_EVAL_LOC), sub(p, mload(W4_EVAL_LOC)), p) + // index_is_monotonically_increasing = index_delta * (index_delta - 1) + let index_is_monotonically_increasing := mulmod(index_delta, addmod(index_delta, sub(p, 1), p), p) + + // adjacent_values_match_if_adjacent_indices_match = record_delta * (1 - index_delta) + let adjacent_values_match_if_adjacent_indices_match := + mulmod(record_delta, addmod(1, sub(p, index_delta), p), p) + + // AUX_ROM_CONSISTENCY_EVALUATION = ((adjacent_values_match_if_adjacent_indices_match * alpha) + index_is_monotonically_increasing) * alpha + partial_record_check + mstore( + AUX_ROM_CONSISTENCY_EVALUATION, + addmod( + mulmod( + addmod( + mulmod(adjacent_values_match_if_adjacent_indices_match, mload(C_ALPHA_LOC), p), + index_is_monotonically_increasing, + p + ), + mload(C_ALPHA_LOC), + p + ), + memory_record_check, + p + ) + ) + + { + /** + * next_gate_access_type = w_3_omega; + * next_gate_access_type *= eta; + * next_gate_access_type += w_2_omega; + * next_gate_access_type *= eta; + * next_gate_access_type += w_1_omega; + * next_gate_access_type *= eta; + * next_gate_access_type = w_4_omega - next_gate_access_type; + */ + let next_gate_access_type := mulmod(mload(W3_OMEGA_EVAL_LOC), mload(C_ETA_LOC), p) + next_gate_access_type := addmod(next_gate_access_type, mload(W2_OMEGA_EVAL_LOC), p) + next_gate_access_type := mulmod(next_gate_access_type, mload(C_ETA_LOC), p) + next_gate_access_type := addmod(next_gate_access_type, mload(W1_OMEGA_EVAL_LOC), p) + next_gate_access_type := mulmod(next_gate_access_type, mload(C_ETA_LOC), p) + next_gate_access_type := addmod(mload(W4_OMEGA_EVAL_LOC), sub(p, next_gate_access_type), p) + + // value_delta = w_3_omega - w_3 + let value_delta := addmod(mload(W3_OMEGA_EVAL_LOC), sub(p, mload(W3_EVAL_LOC)), p) + // adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation = (1 - index_delta) * value_delta * (1 - next_gate_access_type); + + let adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation := + mulmod( + addmod(1, sub(p, index_delta), p), + mulmod(value_delta, addmod(1, sub(p, next_gate_access_type), p), p), + p + ) + + // AUX_RAM_CONSISTENCY_EVALUATION + + /** + * access_type = w_4 - partial_record_check + * access_check = access_type^2 - access_type + * next_gate_access_type_is_boolean = next_gate_access_type^2 - next_gate_access_type + * RAM_consistency_check_identity = adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation; + * RAM_consistency_check_identity *= alpha; + * RAM_consistency_check_identity += index_is_monotonically_increasing; + * RAM_consistency_check_identity *= alpha; + * RAM_consistency_check_identity += next_gate_access_type_is_boolean; + * RAM_consistency_check_identity *= alpha; + * RAM_consistency_check_identity += access_check; + */ + + let access_type := addmod(mload(W4_EVAL_LOC), sub(p, partial_record_check), p) + let access_check := mulmod(access_type, addmod(access_type, sub(p, 1), p), p) + let next_gate_access_type_is_boolean := + mulmod(next_gate_access_type, addmod(next_gate_access_type, sub(p, 1), p), p) + let RAM_cci := + mulmod( + adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation, + mload(C_ALPHA_LOC), + p + ) + RAM_cci := addmod(RAM_cci, index_is_monotonically_increasing, p) + RAM_cci := mulmod(RAM_cci, mload(C_ALPHA_LOC), p) + RAM_cci := addmod(RAM_cci, next_gate_access_type_is_boolean, p) + RAM_cci := mulmod(RAM_cci, mload(C_ALPHA_LOC), p) + RAM_cci := addmod(RAM_cci, access_check, p) + + mstore(AUX_RAM_CONSISTENCY_EVALUATION, RAM_cci) + } + + { + // timestamp_delta = w_2_omega - w_2 + let timestamp_delta := addmod(mload(W2_OMEGA_EVAL_LOC), sub(p, mload(W2_EVAL_LOC)), p) + + // RAM_timestamp_check_identity = (1 - index_delta) * timestamp_delta - w_3 + let RAM_timestamp_check_identity := + addmod( + mulmod(timestamp_delta, addmod(1, sub(p, index_delta), p), p), sub(p, mload(W3_EVAL_LOC)), p + ) + + /** + * memory_identity = ROM_consistency_check_identity * q_2; + * memory_identity += RAM_timestamp_check_identity * q_4; + * memory_identity += memory_record_check * q_m; + * memory_identity *= q_1; + * memory_identity += (RAM_consistency_check_identity * q_arith); + * + * auxiliary_identity = memory_identity + non_native_field_identity + limb_accumulator_identity; + * auxiliary_identity *= q_aux; + * auxiliary_identity *= alpha_base; + */ + let memory_identity := mulmod(mload(AUX_ROM_CONSISTENCY_EVALUATION), mload(Q2_EVAL_LOC), p) + memory_identity := + addmod(memory_identity, mulmod(RAM_timestamp_check_identity, mload(Q4_EVAL_LOC), p), p) + memory_identity := + addmod(memory_identity, mulmod(mload(AUX_MEMORY_EVALUATION), mload(QM_EVAL_LOC), p), p) + memory_identity := mulmod(memory_identity, mload(Q1_EVAL_LOC), p) + memory_identity := + addmod( + memory_identity, mulmod(mload(AUX_RAM_CONSISTENCY_EVALUATION), mload(QARITH_EVAL_LOC), p), p + ) + + let auxiliary_identity := addmod(memory_identity, mload(AUX_NON_NATIVE_FIELD_EVALUATION), p) + auxiliary_identity := addmod(auxiliary_identity, mload(AUX_LIMB_ACCUMULATOR_EVALUATION), p) + auxiliary_identity := mulmod(auxiliary_identity, mload(QAUX_EVAL_LOC), p) + auxiliary_identity := mulmod(auxiliary_identity, mload(C_ALPHA_BASE_LOC), p) + + mstore(AUX_IDENTITY, auxiliary_identity) + + // update alpha + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_CUBE_LOC), p)) + } + } + } + + { + /** + * quotient = ARITHMETIC_IDENTITY + * quotient += PERMUTATION_IDENTITY + * quotient += PLOOKUP_IDENTITY + * quotient += SORT_IDENTITY + * quotient += ELLIPTIC_IDENTITY + * quotient += AUX_IDENTITY + * quotient *= ZERO_POLY_INVERSE + */ + mstore( + QUOTIENT_EVAL_LOC, + mulmod( + addmod( + addmod( + addmod( + addmod( + addmod(mload(PERMUTATION_IDENTITY), mload(PLOOKUP_IDENTITY), p), + mload(ARITHMETIC_IDENTITY), + p + ), + mload(SORT_IDENTITY), + p + ), + mload(ELLIPTIC_IDENTITY), + p + ), + mload(AUX_IDENTITY), + p + ), + mload(ZERO_POLY_INVERSE_LOC), + p + ) + ) + } + + /** + * GENERATE NU AND SEPARATOR CHALLENGES + */ + { + let current_challenge := mload(C_CURRENT_LOC) + // get a calldata pointer that points to the start of the data we want to copy + let calldata_ptr := add(calldataload(0x04), 0x24) + + calldata_ptr := add(calldata_ptr, NU_CALLDATA_SKIP_LENGTH) + + mstore(NU_CHALLENGE_INPUT_LOC_A, current_challenge) + mstore(NU_CHALLENGE_INPUT_LOC_B, mload(QUOTIENT_EVAL_LOC)) + calldatacopy(NU_CHALLENGE_INPUT_LOC_C, calldata_ptr, NU_INPUT_LENGTH) + + // hash length = (0x20 + num field elements), we include the previous challenge in the hash + let challenge := keccak256(NU_CHALLENGE_INPUT_LOC_A, add(NU_INPUT_LENGTH, 0x40)) + + mstore(C_V0_LOC, mod(challenge, p)) + // We need THIRTY-ONE independent nu challenges! + mstore(0x00, challenge) + mstore8(0x20, 0x01) + mstore(C_V1_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x02) + mstore(C_V2_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x03) + mstore(C_V3_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x04) + mstore(C_V4_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x05) + mstore(C_V5_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x06) + mstore(C_V6_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x07) + mstore(C_V7_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x08) + mstore(C_V8_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x09) + mstore(C_V9_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x0a) + mstore(C_V10_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x0b) + mstore(C_V11_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x0c) + mstore(C_V12_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x0d) + mstore(C_V13_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x0e) + mstore(C_V14_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x0f) + mstore(C_V15_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x10) + mstore(C_V16_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x11) + mstore(C_V17_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x12) + mstore(C_V18_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x13) + mstore(C_V19_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x14) + mstore(C_V20_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x15) + mstore(C_V21_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x16) + mstore(C_V22_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x17) + mstore(C_V23_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x18) + mstore(C_V24_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x19) + mstore(C_V25_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x1a) + mstore(C_V26_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x1b) + mstore(C_V27_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x1c) + mstore(C_V28_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x1d) + mstore(C_V29_LOC, mod(keccak256(0x00, 0x21), p)) + + // @follow-up - Why are both v29 and v30 using appending 0x1d to the prior challenge and hashing, should it not change? + mstore8(0x20, 0x1d) + challenge := keccak256(0x00, 0x21) + mstore(C_V30_LOC, mod(challenge, p)) + + // separator + mstore(0x00, challenge) + mstore(0x20, mload(PI_Z_Y_LOC)) + mstore(0x40, mload(PI_Z_X_LOC)) + mstore(0x60, mload(PI_Z_OMEGA_Y_LOC)) + mstore(0x80, mload(PI_Z_OMEGA_X_LOC)) + + mstore(C_U_LOC, mod(keccak256(0x00, 0xa0), p)) + } + + let success := 0 + // VALIDATE T1 + { + let x := mload(T1_X_LOC) + let y := mload(T1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)) + mstore(ACCUMULATOR_X_LOC, x) + mstore(add(ACCUMULATOR_X_LOC, 0x20), y) + } + // VALIDATE T2 + { + let x := mload(T2_X_LOC) // 0x1400 + let y := mload(T2_Y_LOC) // 0x1420 + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(ZETA_POW_N_LOC)) + // accumulator_2 = [T2].zeta^n + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = [T1] + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE T3 + { + let x := mload(T3_X_LOC) + let y := mload(T3_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(mload(ZETA_POW_N_LOC), mload(ZETA_POW_N_LOC), p)) + // accumulator_2 = [T3].zeta^{2n} + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE T4 + { + let x := mload(T4_X_LOC) + let y := mload(T4_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(mulmod(mload(ZETA_POW_N_LOC), mload(ZETA_POW_N_LOC), p), mload(ZETA_POW_N_LOC), p)) + // accumulator_2 = [T4].zeta^{3n} + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE W1 + { + let x := mload(W1_X_LOC) + let y := mload(W1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V0_LOC), p)) + // accumulator_2 = v0.(u + 1).[W1] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE W2 + { + let x := mload(W2_X_LOC) + let y := mload(W2_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V1_LOC), p)) + // accumulator_2 = v1.(u + 1).[W2] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE W3 + { + let x := mload(W3_X_LOC) + let y := mload(W3_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V2_LOC), p)) + // accumulator_2 = v2.(u + 1).[W3] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE W4 + { + let x := mload(W4_X_LOC) + let y := mload(W4_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V3_LOC), p)) + // accumulator_2 = v3.(u + 1).[W4] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE S + { + let x := mload(S_X_LOC) + let y := mload(S_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V4_LOC), p)) + // accumulator_2 = v4.(u + 1).[S] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE Z + { + let x := mload(Z_X_LOC) + let y := mload(Z_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V5_LOC), p)) + // accumulator_2 = v5.(u + 1).[Z] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE Z_LOOKUP + { + let x := mload(Z_LOOKUP_X_LOC) + let y := mload(Z_LOOKUP_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V6_LOC), p)) + // accumulator_2 = v6.(u + 1).[Z_LOOKUP] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE Q1 + { + let x := mload(Q1_X_LOC) + let y := mload(Q1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V7_LOC)) + // accumulator_2 = v7.[Q1] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE Q2 + { + let x := mload(Q2_X_LOC) + let y := mload(Q2_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V8_LOC)) + // accumulator_2 = v8.[Q2] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE Q3 + { + let x := mload(Q3_X_LOC) + let y := mload(Q3_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V9_LOC)) + // accumulator_2 = v9.[Q3] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE Q4 + { + let x := mload(Q4_X_LOC) + let y := mload(Q4_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V10_LOC)) + // accumulator_2 = v10.[Q4] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE QM + { + let x := mload(QM_X_LOC) + let y := mload(QM_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V11_LOC)) + // accumulator_2 = v11.[Q;] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE QC + { + let x := mload(QC_X_LOC) + let y := mload(QC_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V12_LOC)) + // accumulator_2 = v12.[QC] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE QARITH + { + let x := mload(QARITH_X_LOC) + let y := mload(QARITH_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V13_LOC)) + // accumulator_2 = v13.[QARITH] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE QSORT + { + let x := mload(QSORT_X_LOC) + let y := mload(QSORT_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V14_LOC)) + // accumulator_2 = v14.[QSORT] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE QELLIPTIC + { + let x := mload(QELLIPTIC_X_LOC) + let y := mload(QELLIPTIC_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V15_LOC)) + // accumulator_2 = v15.[QELLIPTIC] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE QAUX + { + let x := mload(QAUX_X_LOC) + let y := mload(QAUX_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V16_LOC)) + // accumulator_2 = v15.[Q_AUX] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE SIGMA1 + { + let x := mload(SIGMA1_X_LOC) + let y := mload(SIGMA1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V17_LOC)) + // accumulator_2 = v17.[sigma1] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE SIGMA2 + { + let x := mload(SIGMA2_X_LOC) + let y := mload(SIGMA2_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V18_LOC)) + // accumulator_2 = v18.[sigma2] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE SIGMA3 + { + let x := mload(SIGMA3_X_LOC) + let y := mload(SIGMA3_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V19_LOC)) + // accumulator_2 = v19.[sigma3] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE SIGMA4 + { + let x := mload(SIGMA4_X_LOC) + let y := mload(SIGMA4_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V20_LOC)) + // accumulator_2 = v20.[sigma4] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE TABLE1 + { + let x := mload(TABLE1_X_LOC) + let y := mload(TABLE1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V21_LOC), p)) + // accumulator_2 = u.[table1] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE TABLE2 + { + let x := mload(TABLE2_X_LOC) + let y := mload(TABLE2_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V22_LOC), p)) + // accumulator_2 = u.[table2] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE TABLE3 + { + let x := mload(TABLE3_X_LOC) + let y := mload(TABLE3_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V23_LOC), p)) + // accumulator_2 = u.[table3] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE TABLE4 + { + let x := mload(TABLE4_X_LOC) + let y := mload(TABLE4_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V24_LOC), p)) + // accumulator_2 = u.[table4] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE TABLE_TYPE + { + let x := mload(TABLE_TYPE_X_LOC) + let y := mload(TABLE_TYPE_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V25_LOC)) + // accumulator_2 = v25.[TableType] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE ID1 + { + let x := mload(ID1_X_LOC) + let y := mload(ID1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V26_LOC)) + // accumulator_2 = v26.[ID1] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE ID2 + { + let x := mload(ID2_X_LOC) + let y := mload(ID2_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V27_LOC)) + // accumulator_2 = v27.[ID2] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE ID3 + { + let x := mload(ID3_X_LOC) + let y := mload(ID3_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V28_LOC)) + // accumulator_2 = v28.[ID3] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE ID4 + { + let x := mload(ID4_X_LOC) + let y := mload(ID4_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V29_LOC)) + // accumulator_2 = v29.[ID4] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + /** + * COMPUTE BATCH EVALUATION SCALAR MULTIPLIER + */ + { + /** + * batch_evaluation = v0 * (w_1_omega * u + w_1_eval) + * batch_evaluation += v1 * (w_2_omega * u + w_2_eval) + * batch_evaluation += v2 * (w_3_omega * u + w_3_eval) + * batch_evaluation += v3 * (w_4_omega * u + w_4_eval) + * batch_evaluation += v4 * (s_omega_eval * u + s_eval) + * batch_evaluation += v5 * (z_omega_eval * u + z_eval) + * batch_evaluation += v6 * (z_lookup_omega_eval * u + z_lookup_eval) + */ + let batch_evaluation := + mulmod( + mload(C_V0_LOC), + addmod(mulmod(mload(W1_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(W1_EVAL_LOC), p), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V1_LOC), + addmod(mulmod(mload(W2_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(W2_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V2_LOC), + addmod(mulmod(mload(W3_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(W3_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V3_LOC), + addmod(mulmod(mload(W4_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(W4_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V4_LOC), + addmod(mulmod(mload(S_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(S_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V5_LOC), + addmod(mulmod(mload(Z_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(Z_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V6_LOC), + addmod(mulmod(mload(Z_LOOKUP_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(Z_LOOKUP_EVAL_LOC), p), + p + ), + p + ) + + /** + * batch_evaluation += v7 * Q1_EVAL + * batch_evaluation += v8 * Q2_EVAL + * batch_evaluation += v9 * Q3_EVAL + * batch_evaluation += v10 * Q4_EVAL + * batch_evaluation += v11 * QM_EVAL + * batch_evaluation += v12 * QC_EVAL + * batch_evaluation += v13 * QARITH_EVAL + * batch_evaluation += v14 * QSORT_EVAL_LOC + * batch_evaluation += v15 * QELLIPTIC_EVAL_LOC + * batch_evaluation += v16 * QAUX_EVAL_LOC + * batch_evaluation += v17 * SIGMA1_EVAL_LOC + * batch_evaluation += v18 * SIGMA2_EVAL_LOC + * batch_evaluation += v19 * SIGMA3_EVAL_LOC + * batch_evaluation += v20 * SIGMA4_EVAL_LOC + */ + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V7_LOC), mload(Q1_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V8_LOC), mload(Q2_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V9_LOC), mload(Q3_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V10_LOC), mload(Q4_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V11_LOC), mload(QM_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V12_LOC), mload(QC_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V13_LOC), mload(QARITH_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V14_LOC), mload(QSORT_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V15_LOC), mload(QELLIPTIC_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V16_LOC), mload(QAUX_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V17_LOC), mload(SIGMA1_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V18_LOC), mload(SIGMA2_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V19_LOC), mload(SIGMA3_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V20_LOC), mload(SIGMA4_EVAL_LOC), p), p) + + /** + * batch_evaluation += v21 * (table1(zw) * u + table1(z)) + * batch_evaluation += v22 * (table2(zw) * u + table2(z)) + * batch_evaluation += v23 * (table3(zw) * u + table3(z)) + * batch_evaluation += v24 * (table4(zw) * u + table4(z)) + * batch_evaluation += v25 * table_type_eval + * batch_evaluation += v26 * id1_eval + * batch_evaluation += v27 * id2_eval + * batch_evaluation += v28 * id3_eval + * batch_evaluation += v29 * id4_eval + * batch_evaluation += quotient_eval + */ + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V21_LOC), + addmod(mulmod(mload(TABLE1_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(TABLE1_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V22_LOC), + addmod(mulmod(mload(TABLE2_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(TABLE2_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V23_LOC), + addmod(mulmod(mload(TABLE3_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(TABLE3_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V24_LOC), + addmod(mulmod(mload(TABLE4_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(TABLE4_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V25_LOC), mload(TABLE_TYPE_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V26_LOC), mload(ID1_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V27_LOC), mload(ID2_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V28_LOC), mload(ID3_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V29_LOC), mload(ID4_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mload(QUOTIENT_EVAL_LOC), p) + + mstore(0x00, 0x01) // [1].x + mstore(0x20, 0x02) // [1].y + mstore(0x40, sub(p, batch_evaluation)) + // accumulator_2 = -[1].(batch_evaluation) + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + mstore(OPENING_COMMITMENT_SUCCESS_FLAG, success) + } + + /** + * PERFORM PAIRING PREAMBLE + */ + { + let u := mload(C_U_LOC) + let zeta := mload(C_ZETA_LOC) + // VALIDATE PI_Z + { + let x := mload(PI_Z_X_LOC) + let y := mload(PI_Z_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)) + mstore(0x00, x) + mstore(0x20, y) + } + // compute zeta.[PI_Z] and add into accumulator + mstore(0x40, zeta) + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE PI_Z_OMEGA + { + let x := mload(PI_Z_OMEGA_X_LOC) + let y := mload(PI_Z_OMEGA_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(mulmod(u, zeta, p), mload(OMEGA_LOC), p)) + // accumulator_2 = u.zeta.omega.[PI_Z_OMEGA] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // PAIRING_RHS = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, PAIRING_RHS_X_LOC, 0x40)) + + mstore(0x00, mload(PI_Z_X_LOC)) + mstore(0x20, mload(PI_Z_Y_LOC)) + mstore(0x40, mload(PI_Z_OMEGA_X_LOC)) + mstore(0x60, mload(PI_Z_OMEGA_Y_LOC)) + mstore(0x80, u) + success := and(success, staticcall(gas(), 7, 0x40, 0x60, 0x40, 0x40)) + // PAIRING_LHS = [PI_Z] + [PI_Z_OMEGA] * u + success := and(success, staticcall(gas(), 6, 0x00, 0x80, PAIRING_LHS_X_LOC, 0x40)) + // negate lhs y-coordinate + mstore(PAIRING_LHS_Y_LOC, sub(q, mload(PAIRING_LHS_Y_LOC))) + + if mload(CONTAINS_RECURSIVE_PROOF_LOC) { + // VALIDATE RECURSIVE P1 + { + let x := mload(RECURSIVE_P1_X_LOC) + let y := mload(RECURSIVE_P1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + + // compute u.u.[recursive_p1] and write into 0x60 + mstore(0x40, mulmod(u, u, p)) + success := and(success, staticcall(gas(), 7, 0x00, 0x60, 0x60, 0x40)) + // VALIDATE RECURSIVE P2 + { + let x := mload(RECURSIVE_P2_X_LOC) + let y := mload(RECURSIVE_P2_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + // compute u.u.[recursive_p2] and write into 0x00 + // 0x40 still contains u*u + success := and(success, staticcall(gas(), 7, 0x00, 0x60, 0x00, 0x40)) + + // compute u.u.[recursiveP1] + rhs and write into rhs + mstore(0xa0, mload(PAIRING_RHS_X_LOC)) + mstore(0xc0, mload(PAIRING_RHS_Y_LOC)) + success := and(success, staticcall(gas(), 6, 0x60, 0x80, PAIRING_RHS_X_LOC, 0x40)) + + // compute u.u.[recursiveP2] + lhs and write into lhs + mstore(0x40, mload(PAIRING_LHS_X_LOC)) + mstore(0x60, mload(PAIRING_LHS_Y_LOC)) + success := and(success, staticcall(gas(), 6, 0x00, 0x80, PAIRING_LHS_X_LOC, 0x40)) + } + + if iszero(success) { + mstore(0x0, EC_SCALAR_MUL_FAILURE_SELECTOR) + revert(0x00, 0x04) + } + mstore(PAIRING_PREAMBLE_SUCCESS_FLAG, success) + } + + /** + * PERFORM PAIRING + */ + { + // rhs paired with [1]_2 + // lhs paired with [x]_2 + + mstore(0x00, mload(PAIRING_RHS_X_LOC)) + mstore(0x20, mload(PAIRING_RHS_Y_LOC)) + mstore(0x40, 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2) // this is [1]_2 + mstore(0x60, 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed) + mstore(0x80, 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b) + mstore(0xa0, 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa) + + mstore(0xc0, mload(PAIRING_LHS_X_LOC)) + mstore(0xe0, mload(PAIRING_LHS_Y_LOC)) + mstore(0x100, mload(G2X_X0_LOC)) + mstore(0x120, mload(G2X_X1_LOC)) + mstore(0x140, mload(G2X_Y0_LOC)) + mstore(0x160, mload(G2X_Y1_LOC)) + + success := staticcall(gas(), 8, 0x00, 0x180, 0x00, 0x20) + mstore(PAIRING_SUCCESS_FLAG, success) + mstore(RESULT_FLAG, mload(0x00)) + } + if iszero( + and( + and(and(mload(PAIRING_SUCCESS_FLAG), mload(RESULT_FLAG)), mload(PAIRING_PREAMBLE_SUCCESS_FLAG)), + mload(OPENING_COMMITMENT_SUCCESS_FLAG) + ) + ) { + mstore(0x0, PROOF_FAILURE_SELECTOR) + revert(0x00, 0x04) + } + { + mstore(0x00, 0x01) + return(0x00, 0x20) // Proof succeeded! + } + } + } +} + +contract UltraVerifier is BaseUltraVerifier { + function getVerificationKeyHash() public pure override(BaseUltraVerifier) returns (bytes32) { + return UltraVerificationKey.verificationKeyHash(); + } + + function loadVerificationKey(uint256 vk, uint256 _omegaInverseLoc) internal pure virtual override(BaseUltraVerifier) { + UltraVerificationKey.loadVerificationKey(vk, _omegaInverseLoc); + } +} diff --git a/circuits/recursion/src/main.nr b/circuits/recursion/src/main.nr new file mode 100644 index 0000000000000000000000000000000000000000..02b05b9c71a8d87a1c3cf780458427bdb31bdd0e --- /dev/null +++ b/circuits/recursion/src/main.nr @@ -0,0 +1,35 @@ +use dep::std; +fn main( + verification_key : [Field; 114], + proof : [Field; 161], + public_inputs : [Field; 68], + input_aggregation_object: [Field; 16], + key_hash : pub Field, + tx_ids: pub [Field; 16], + old_root: pub Field, + new_root: pub Field, + oracle: pub Field +) -> pub [Field; 16] { + let tx_root_calc: Field = pedersen_tree_four(tx_ids); + assert(oracle == std::hash::pedersen([0])[0]); + let batch_root_calc: Field = std::hash::pedersen([tx_root_calc, oracle])[0]; + let new_root_calc: Field = std::hash::pedersen([batch_root_calc, old_root])[0]; + assert(new_root == new_root_calc); + let vk: [Field] = verification_key; + let p: [Field] = proof; + let pi: [Field] = public_inputs; + std::verify_proof( + vk, + p, + pi, + key_hash, + input_aggregation_object + ) +} +fn pedersen_tree_four(leaves: [Field; 16]) -> Field { + let mut tx_tree: [Field; 16] = leaves; + for l in 0..8 { tx_tree[l] = std::hash::pedersen([tx_tree[2*l], tx_tree[2*l + 1]])[0]; } + for l in 0..4 { tx_tree[l] = std::hash::pedersen([tx_tree[2*l], tx_tree[2*l + 1]])[0]; } + for l in 0..2 { tx_tree[l] = std::hash::pedersen([tx_tree[2*l], tx_tree[2*l + 1]])[0]; } + std::hash::pedersen([tx_tree[0], tx_tree[1]])[0] +} \ No newline at end of file diff --git a/circuits/recursion/target/recursion.json b/circuits/recursion/target/recursion.json new file mode 100644 index 0000000000000000000000000000000000000000..a87eb611207e74cf44baac7642c307c7e8f9ed83 --- /dev/null +++ b/circuits/recursion/target/recursion.json @@ -0,0 +1 @@ +{"backend":"acvm-backend-barretenberg","abi":{"parameters":[{"name":"verification_key","type":{"kind":"array","length":114,"type":{"kind":"field"}},"visibility":"private"},{"name":"proof","type":{"kind":"array","length":161,"type":{"kind":"field"}},"visibility":"private"},{"name":"public_inputs","type":{"kind":"array","length":68,"type":{"kind":"field"}},"visibility":"private"},{"name":"input_aggregation_object","type":{"kind":"array","length":16,"type":{"kind":"field"}},"visibility":"private"},{"name":"key_hash","type":{"kind":"field"},"visibility":"public"},{"name":"tx_ids","type":{"kind":"array","length":16,"type":{"kind":"field"}},"visibility":"public"},{"name":"old_root","type":{"kind":"field"},"visibility":"public"},{"name":"new_root","type":{"kind":"field"},"visibility":"public"},{"name":"oracle","type":{"kind":"field"},"visibility":"public"}],"param_witnesses":{"input_aggregation_object":[344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359],"key_hash":[360],"new_root":[378],"old_root":[377],"oracle":[379],"proof":[115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275],"public_inputs":[276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343],"tx_ids":[361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376],"verification_key":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114]},"return_type":{"kind":"array","length":16,"type":{"kind":"field"}},"return_witnesses":[417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432]},"bytecode":"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"} \ No newline at end of file diff --git a/contracts/Interfaces/IElasticERC20.sol b/contracts/Interfaces/IElasticERC20.sol new file mode 100644 index 0000000000000000000000000000000000000000..f5ac3a1d00eab7f5f892a414d372d0dd4584dd9c --- /dev/null +++ b/contracts/Interfaces/IElasticERC20.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IElasticERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + // Additional mint and burn functions + function burn(address account, uint256 amount) external; + function mint(address account, uint256 amount) external; + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} diff --git a/contracts/TechnicalPreview.sol b/contracts/TechnicalPreview.sol new file mode 100644 index 0000000000000000000000000000000000000000..cd78a1b05975541dc3d1c0d423b3e80d990d0cc3 --- /dev/null +++ b/contracts/TechnicalPreview.sol @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; +pragma experimental ABIEncoderV2; +import "./Interfaces/IElasticERC20.sol"; + +struct Transaction { + bytes32 id; + + bytes32[16] commitments; + bytes32[16] nullifier_hashes; + bytes32[16] recipients; + bytes32[16] withdrawals; + bytes32 deposit; + bytes32[161] proof; + bytes32[16] aggregation_object; +} + +struct BatchPublicInputs { + bytes32 key_hash; + bytes32 oracle; + bytes32 old_root; + bytes32 new_root; + Transaction[] transactions; +} +interface IVerifier { + function verify( + bytes calldata, + bytes32[] calldata + ) external view returns (bool); + function getVerificationKeyHash() external pure returns (bytes32); +} +contract TechnicalPreview { + + IVerifier public verifier; + IElasticERC20 public token; + bytes32[] public validRoots; + bytes32 public keyHash; + uint256 public MAX_FIELD_SIZE = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000; + bytes32 public ZERO_VALUE = 0x016a430aa58685aba1311244a973a3bc358859da86784be51094368e8fb6f720; + mapping(bytes32 => bytes32) public utxoPrevRoots; + mapping(bytes32 => bool) public nullifierHashes; + mapping(bytes32 => bool) public commitments; + mapping(bytes32 => bytes32[]) public utxo; + uint256 public MAX_ITEMS = 16; + mapping(uint256 => Transaction[]) public batch; + uint256 public batchNumber = 0; + bytes32 public merkleRoot = ZERO_VALUE; + constructor(IVerifier _verifier, address _token, bytes32 _keyHash) { + verifier = _verifier; + token = IElasticERC20(_token); + validRoots.push(merkleRoot); + keyHash = _keyHash; + } + function getLatestAggregationObject() public view returns (bytes32[16] memory) { + return batch[batchNumber][batch[batchNumber].length - 1].aggregation_object; + } + function getSpentNullifiers(bytes32[] calldata _nullifierHashes) external view returns (bool[] memory spent) { + uint256 nullifierHashesLength = _nullifierHashes.length; + spent = new bool[](nullifierHashesLength); + for (uint256 i; i < nullifierHashesLength; i++) { + if (isSpent(_nullifierHashes[i])) { + spent[i] = true; + } + } + } + + function getCommitment(bytes32 _commitment) public view returns (bool) { + return commitments[_commitment]; + } + + function getUtxoFromRoot(bytes32 _root) public view returns (bytes32[] memory) { + return utxo[_root]; + } + function getRootFromUtxo(bytes32 _utxo) public view returns (bytes32) { + return utxoPrevRoots[_utxo]; + } + function getValidRoots() public view returns (bytes32[] memory) { + return validRoots; + } + function getCurrentBatch() public view returns (Transaction[] memory) { + return batch[batchNumber]; + } + + + + function enqueue(Transaction calldata _tx) public { + + require(MAX_ITEMS > batch[batchNumber].length, "queue is full"); + batch[batchNumber].push(_tx); + + for (uint256 i = 0; i < 16; i++) { + if (_tx.commitments[i] != ZERO_VALUE) { + require(!commitments[_tx.commitments[i]], "commitment exists"); + commitments[_tx.commitments[i]] = true; + } + + } + } + function publish( + bytes calldata _proof, + BatchPublicInputs calldata _batch + ) public payable { + require(uint256(_batch.old_root) == uint256(merkleRoot), "invalid root"); + BatchPublicInputs memory batchPublicInputs = _batch; + + batchPublicInputs.transactions = batch[batchNumber]; + for (uint256 i = 0; i < batchPublicInputs.transactions.length; i++) { + for (uint256 j = 0; j < 16; j++) { + if (batchPublicInputs.transactions[i].nullifier_hashes[j] != ZERO_VALUE) { + require(!nullifierHashes[batchPublicInputs.transactions[i].nullifier_hashes[j]], "nullifier spent"); + nullifierHashes[batchPublicInputs.transactions[i].nullifier_hashes[j]] = true; + } + + if (batchPublicInputs.transactions[i].commitments[j] != ZERO_VALUE) { + utxo[batchPublicInputs.old_root].push(batchPublicInputs.transactions[i].commitments[j]); + utxoPrevRoots[batchPublicInputs.transactions[i].commitments[j]] = batchPublicInputs.old_root; + } + + if (batchPublicInputs.transactions[i].recipients[j] != ZERO_VALUE) { + token.mint(address(uint160(uint256(batchPublicInputs.transactions[i].recipients[j]))), uint256(batchPublicInputs.transactions[i].withdrawals[j])); + } + } + } + validRoots.push(batchPublicInputs.new_root); + merkleRoot = batchPublicInputs.new_root; + batchNumber++; + + require(verifier.verify(_proof, prepareBatchPublicInputs(_batch)), "invalid proof"); + } + function dropQueue(bytes calldata _preimage) public { + require(keccak256(_preimage) > 0xff00000000000000000000000000000000000000000000000000000000000000); + + for (uint256 i = 0; i < MAX_ITEMS; i++) { + for (uint256 j = 0; j < 16; j++) { + commitments[batch[batchNumber][i].commitments[j]] = false; + } + } + batchNumber++; + } + + function isSpent(bytes32 _nullifierHash) public view returns (bool) { + return nullifierHashes[_nullifierHash]; + } + function verifyProof( + bytes calldata _proof, + bytes32[] memory _publicInputs + ) public view returns (bool) { + return verifier.verify(_proof, _publicInputs); + } + + function prepareBatchPublicInputs(BatchPublicInputs memory input) public view returns (bytes32[] memory) { + bytes32[] memory flatArray = new bytes32[](36); + uint256 idx = 0; + flatArray[idx++] = keyHash; + for (uint256 i = 0; i < 16; i++) { + + if (i < input.transactions.length) { + flatArray[idx++] = input.transactions[i].id; + } else { + flatArray[idx++] = ZERO_VALUE; + } + + } + flatArray[idx++] = input.old_root; + flatArray[idx++] = input.new_root; + flatArray[idx++] = input.oracle; + for (uint256 i = 0; i < 16; i++ ) { + + flatArray[idx++] = input.transactions[(input.transactions.length - 1)].aggregation_object[i]; + } + + for (uint256 i = 0; i < flatArray.length; i++) { + require(uint256(flatArray[i]) < 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000, "too large!"); + } + return flatArray; + } +} \ No newline at end of file diff --git a/contracts/XFTmock.sol b/contracts/XFTmock.sol new file mode 100644 index 0000000000000000000000000000000000000000..cb0c3b095801aa248cf589a20ecc1e0e4a844ad2 --- /dev/null +++ b/contracts/XFTmock.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/access/AccessControl.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract XFTMock is ERC20, AccessControl { + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); + + constructor(string memory _vk) ERC20(_vk, _vk) { + _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); + } + + function mint(address to, uint256 amount) public { + require(hasRole(MINTER_ROLE, msg.sender), "Caller is not a minter"); + _mint(to, amount); + } + + function burn(address from, uint256 amount) public { + require(hasRole(BURNER_ROLE, msg.sender), "Caller is not a burner"); + _burn(from, amount); + } + + +} \ No newline at end of file diff --git a/contracts/plonk_vk.sol b/contracts/plonk_vk.sol new file mode 100644 index 0000000000000000000000000000000000000000..944adf42ee1f15221f8ddb512538424a4cffa8e1 --- /dev/null +++ b/contracts/plonk_vk.sol @@ -0,0 +1,2611 @@ +// Verification Key Hash: 82a0e2cee227b6fb3137880ee66026d05a5aaadd8a7d84079a490ac13c4b86aa +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2022 Aztec +pragma solidity >=0.8.4; + +library UltraVerificationKey { + function verificationKeyHash() internal pure returns(bytes32) { + return 0x82a0e2cee227b6fb3137880ee66026d05a5aaadd8a7d84079a490ac13c4b86aa; + } + + function loadVerificationKey(uint256 _vk, uint256 _omegaInverseLoc) internal pure { + assembly { + mstore(add(_vk, 0x00), 0x0000000000000000000000000000000000000000000000000000000000080000) // vk.circuit_size + mstore(add(_vk, 0x20), 0x0000000000000000000000000000000000000000000000000000000000000024) // vk.num_inputs + mstore(add(_vk, 0x40), 0x2260e724844bca5251829353968e4915305258418357473a5c1d597f613f6cbd) // vk.work_root + mstore(add(_vk, 0x60), 0x3064486657634403844b0eac78ca882cfd284341fcb0615a15cfcd17b14d8201) // vk.domain_inverse + mstore(add(_vk, 0x80), 0x11b071f902eecdc05b4943a2febe1e9cbfa430be1e22a2809d4fffe060608f30) // vk.Q1.x + mstore(add(_vk, 0xa0), 0x13896c8f1e1b73a6636508284e6e55df02238aab3ae04105b345b1dee15b052f) // vk.Q1.y + mstore(add(_vk, 0xc0), 0x0916f8b80ca7b561b859ce7150cc9c1d242334653ab30bfbffe2cd4a54504c61) // vk.Q2.x + mstore(add(_vk, 0xe0), 0x2888dc92bd783f0f56f57c865ab1f1ea41fe8deaed9fc91b091ca3db2b6de3cc) // vk.Q2.y + mstore(add(_vk, 0x100), 0x120a493b68fcc4f791c64972c81acad705c95cd65d36948093ded25ecc4e707f) // vk.Q3.x + mstore(add(_vk, 0x120), 0x241ab99646f6e50ff1dcdccfc41a3feeb1cf75a1f261e851ab06836291b9b943) // vk.Q3.y + mstore(add(_vk, 0x140), 0x01362a1f1ff97b452de0ac04725ac215b171fd862e01ded45f7e806fbefd5fe5) // vk.Q4.x + mstore(add(_vk, 0x160), 0x25d2d06cb1043af7986b2049cbab7f7a88a54c25e4bc8f14433bb39afba872bc) // vk.Q4.y + mstore(add(_vk, 0x180), 0x0f01aed4a6231d33a21c40727645d5d6b163852a6c6833a43e6165199829e213) // vk.Q_M.x + mstore(add(_vk, 0x1a0), 0x019efeb324e468d0a76c33012c3efb6a890d893eccae908c5f137baea3b932f6) // vk.Q_M.y + mstore(add(_vk, 0x1c0), 0x0e277f26853604627f021d7d5740160d47043154ee787b076be2208ac90a9666) // vk.Q_C.x + mstore(add(_vk, 0x1e0), 0x115fb2d9c8ef3fee3113064e54dd299acb3fbe249c52c4eaf7ddd5ea511d4a48) // vk.Q_C.y + mstore(add(_vk, 0x200), 0x0f19ea9fe36453db55a8560084163be7f55004c1c4234e761b67a14e849b6907) // vk.Q_ARITHMETIC.x + mstore(add(_vk, 0x220), 0x142cf7bc7b2aa0f42050ade1488657bc63856be638c1d157a85c256b53f65ef4) // vk.Q_ARITHMETIC.y + mstore(add(_vk, 0x240), 0x034ff0a1c64d1e6af006b0039cf65dfd17c7e905723f266741042c5dde321e81) // vk.QSORT.x + mstore(add(_vk, 0x260), 0x22873eafa0cdb28654784fd8c68119e1a8a99252df586a7485da1feee556fcec) // vk.QSORT.y + mstore(add(_vk, 0x280), 0x23f5f6970dd0cceedb135a3a3f30cfe3b0269d33e3f553317496d29a544b5a62) // vk.Q_ELLIPTIC.x + mstore(add(_vk, 0x2a0), 0x270623e3f1eff355c22d6ddae107d2f99a67d22c7af762e3b8edb6986ddcf1b3) // vk.Q_ELLIPTIC.y + mstore(add(_vk, 0x2c0), 0x06853a42dede8a6279451da2c12bd2c06b630b49ed9cf77f6d1b0a703d419328) // vk.Q_AUX.x + mstore(add(_vk, 0x2e0), 0x01ef8f3f07237a290e826c2597a98bd3fed6e60c8e5be81db1b54c7798bd083f) // vk.Q_AUX.y + mstore(add(_vk, 0x300), 0x276a90bf6eec49cd5e31cb868d714b38805563f24fad21e168f597520b3b1f4e) // vk.SIGMA1.x + mstore(add(_vk, 0x320), 0x22a086ce72b523ab9858d7cd782aee228ff9c18b7c5900412c5b82502fef5b1c) // vk.SIGMA1.y + mstore(add(_vk, 0x340), 0x1a10f3eeb35e51bda8df6b240ac1a6b3fbaf614eaa19e7655cca4a5b2a8f8165) // vk.SIGMA2.x + mstore(add(_vk, 0x360), 0x08cdc78ec23a7ec954f881665ddb53cefe6edee63ee6a3c8415d2cc93b332e25) // vk.SIGMA2.y + mstore(add(_vk, 0x380), 0x06b3916f1f919044a65a6b25c1f572c9e1d2985d2d869559facfb20b9b04d6b1) // vk.SIGMA3.x + mstore(add(_vk, 0x3a0), 0x0a021155576fccaf3612005c6490dba33a8551fd4b90fb7fc0efff45ae50028c) // vk.SIGMA3.y + mstore(add(_vk, 0x3c0), 0x01a34c4fe947c3a2cd85c02df315cc972c02a82111229afa52914ebd30e4fa9f) // vk.SIGMA4.x + mstore(add(_vk, 0x3e0), 0x014e16dbb2b81c45ccc27fb975f62c31c7338fb36f3ed7d9c235ed89e8b832f7) // vk.SIGMA4.y + mstore(add(_vk, 0x400), 0x2fb583c2a09e1762576580dab537fc24c6d34716e68b3d75c106db4f66362648) // vk.TABLE1.x + mstore(add(_vk, 0x420), 0x23f8cf2668d8ebbb7472c0882f38d7c38856632985a78f754297a47faab0a491) // vk.TABLE1.y + mstore(add(_vk, 0x440), 0x17e35f8d4bf88a6803bba08a78a9267d20aff06b0a60f61a4a00aebf9f9fc7b1) // vk.TABLE2.x + mstore(add(_vk, 0x460), 0x0f88c0ad7b6162820c6c75ff4b4be0651f69927737f9dda272ebf26649faa64f) // vk.TABLE2.y + mstore(add(_vk, 0x480), 0x01c8bab7a1bafb2c9056138761ffb59be9fdeb32edaf6538c0315f36f9279c07) // vk.TABLE3.x + mstore(add(_vk, 0x4a0), 0x21d060188a0c759b9debb64fe30393f63ee2bac8830f4b3724b8103e0cea528e) // vk.TABLE3.y + mstore(add(_vk, 0x4c0), 0x0b67cf32c00e76184073a85ed3c263c7021c3abd63b46545a5e9d07e0304ee22) // vk.TABLE4.x + mstore(add(_vk, 0x4e0), 0x07ad8d70ffbae8d1111b6fa8cca4ccc12326812513d81ff1fae90bf27474ce46) // vk.TABLE4.y + mstore(add(_vk, 0x500), 0x1447f09274c0399f9f5b08f4bbd25d7d53dc868b97264be6e6d0f7d740f67767) // vk.TABLE_TYPE.x + mstore(add(_vk, 0x520), 0x037231732044d488092c2a155234ff78b4d75c947125a1d32ec32f4efd7354be) // vk.TABLE_TYPE.y + mstore(add(_vk, 0x540), 0x2deb65c6968a56cf21a01c9d6f9e16655dcf7720d52657631abab33211b667fa) // vk.ID1.x + mstore(add(_vk, 0x560), 0x18a43caeb9516e4401cb61620e8dc51f1a93d3f0ed097c04eab867d56e4df47e) // vk.ID1.y + mstore(add(_vk, 0x580), 0x1e5ee22c88520be7f3023e3fc62deb366524bbd51d781c26aee32df954d9b93f) // vk.ID2.x + mstore(add(_vk, 0x5a0), 0x2fa0775108095bbb4548c02d41b78118f67556a16259ea672cab171727bc8967) // vk.ID2.y + mstore(add(_vk, 0x5c0), 0x1894e9095aebba0c5fc53bb1575cb34e73aaa3ce5fbeea4edefa9f3826933f1d) // vk.ID3.x + mstore(add(_vk, 0x5e0), 0x1212ea71a64f557b3151b499f22322a17d3c3f4513d189f55d2736287231d08d) // vk.ID3.y + mstore(add(_vk, 0x600), 0x100349fc6d8acd4110d377d940f34669a525512b00e92de4ef4af6f5a40aec62) // vk.ID4.x + mstore(add(_vk, 0x620), 0x25e0e421d758a475b523902ae78e4d3908909155bc7cbe48b35a1532e075d54b) // vk.ID4.y + mstore(add(_vk, 0x640), 0x01) // vk.contains_recursive_proof + mstore(add(_vk, 0x660), 20) // vk.recursive_proof_public_input_indices + mstore(add(_vk, 0x680), 0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1) // vk.g2_x.X.c1 + mstore(add(_vk, 0x6a0), 0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0) // vk.g2_x.X.c0 + mstore(add(_vk, 0x6c0), 0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4) // vk.g2_x.Y.c1 + mstore(add(_vk, 0x6e0), 0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55) // vk.g2_x.Y.c0 + mstore(_omegaInverseLoc, 0x06e402c0a314fb67a15cf806664ae1b722dbc0efe66e6c81d98f9924ca535321) // vk.work_root_inverse + } + } +} + +/** + * @title Ultra Plonk proof verification contract + * @dev Top level Plonk proof verification contract, which allows Plonk proof to be verified + */ +abstract contract BaseUltraVerifier { + // VERIFICATION KEY MEMORY LOCATIONS + uint256 internal constant N_LOC = 0x380; + uint256 internal constant NUM_INPUTS_LOC = 0x3a0; + uint256 internal constant OMEGA_LOC = 0x3c0; + uint256 internal constant DOMAIN_INVERSE_LOC = 0x3e0; + uint256 internal constant Q1_X_LOC = 0x400; + uint256 internal constant Q1_Y_LOC = 0x420; + uint256 internal constant Q2_X_LOC = 0x440; + uint256 internal constant Q2_Y_LOC = 0x460; + uint256 internal constant Q3_X_LOC = 0x480; + uint256 internal constant Q3_Y_LOC = 0x4a0; + uint256 internal constant Q4_X_LOC = 0x4c0; + uint256 internal constant Q4_Y_LOC = 0x4e0; + uint256 internal constant QM_X_LOC = 0x500; + uint256 internal constant QM_Y_LOC = 0x520; + uint256 internal constant QC_X_LOC = 0x540; + uint256 internal constant QC_Y_LOC = 0x560; + uint256 internal constant QARITH_X_LOC = 0x580; + uint256 internal constant QARITH_Y_LOC = 0x5a0; + uint256 internal constant QSORT_X_LOC = 0x5c0; + uint256 internal constant QSORT_Y_LOC = 0x5e0; + uint256 internal constant QELLIPTIC_X_LOC = 0x600; + uint256 internal constant QELLIPTIC_Y_LOC = 0x620; + uint256 internal constant QAUX_X_LOC = 0x640; + uint256 internal constant QAUX_Y_LOC = 0x660; + uint256 internal constant SIGMA1_X_LOC = 0x680; + uint256 internal constant SIGMA1_Y_LOC = 0x6a0; + uint256 internal constant SIGMA2_X_LOC = 0x6c0; + uint256 internal constant SIGMA2_Y_LOC = 0x6e0; + uint256 internal constant SIGMA3_X_LOC = 0x700; + uint256 internal constant SIGMA3_Y_LOC = 0x720; + uint256 internal constant SIGMA4_X_LOC = 0x740; + uint256 internal constant SIGMA4_Y_LOC = 0x760; + uint256 internal constant TABLE1_X_LOC = 0x780; + uint256 internal constant TABLE1_Y_LOC = 0x7a0; + uint256 internal constant TABLE2_X_LOC = 0x7c0; + uint256 internal constant TABLE2_Y_LOC = 0x7e0; + uint256 internal constant TABLE3_X_LOC = 0x800; + uint256 internal constant TABLE3_Y_LOC = 0x820; + uint256 internal constant TABLE4_X_LOC = 0x840; + uint256 internal constant TABLE4_Y_LOC = 0x860; + uint256 internal constant TABLE_TYPE_X_LOC = 0x880; + uint256 internal constant TABLE_TYPE_Y_LOC = 0x8a0; + uint256 internal constant ID1_X_LOC = 0x8c0; + uint256 internal constant ID1_Y_LOC = 0x8e0; + uint256 internal constant ID2_X_LOC = 0x900; + uint256 internal constant ID2_Y_LOC = 0x920; + uint256 internal constant ID3_X_LOC = 0x940; + uint256 internal constant ID3_Y_LOC = 0x960; + uint256 internal constant ID4_X_LOC = 0x980; + uint256 internal constant ID4_Y_LOC = 0x9a0; + uint256 internal constant CONTAINS_RECURSIVE_PROOF_LOC = 0x9c0; + uint256 internal constant RECURSIVE_PROOF_PUBLIC_INPUT_INDICES_LOC = 0x9e0; + uint256 internal constant G2X_X0_LOC = 0xa00; + uint256 internal constant G2X_X1_LOC = 0xa20; + uint256 internal constant G2X_Y0_LOC = 0xa40; + uint256 internal constant G2X_Y1_LOC = 0xa60; + + // ### PROOF DATA MEMORY LOCATIONS + uint256 internal constant W1_X_LOC = 0x1200; + uint256 internal constant W1_Y_LOC = 0x1220; + uint256 internal constant W2_X_LOC = 0x1240; + uint256 internal constant W2_Y_LOC = 0x1260; + uint256 internal constant W3_X_LOC = 0x1280; + uint256 internal constant W3_Y_LOC = 0x12a0; + uint256 internal constant W4_X_LOC = 0x12c0; + uint256 internal constant W4_Y_LOC = 0x12e0; + uint256 internal constant S_X_LOC = 0x1300; + uint256 internal constant S_Y_LOC = 0x1320; + uint256 internal constant Z_X_LOC = 0x1340; + uint256 internal constant Z_Y_LOC = 0x1360; + uint256 internal constant Z_LOOKUP_X_LOC = 0x1380; + uint256 internal constant Z_LOOKUP_Y_LOC = 0x13a0; + uint256 internal constant T1_X_LOC = 0x13c0; + uint256 internal constant T1_Y_LOC = 0x13e0; + uint256 internal constant T2_X_LOC = 0x1400; + uint256 internal constant T2_Y_LOC = 0x1420; + uint256 internal constant T3_X_LOC = 0x1440; + uint256 internal constant T3_Y_LOC = 0x1460; + uint256 internal constant T4_X_LOC = 0x1480; + uint256 internal constant T4_Y_LOC = 0x14a0; + + uint256 internal constant W1_EVAL_LOC = 0x1600; + uint256 internal constant W2_EVAL_LOC = 0x1620; + uint256 internal constant W3_EVAL_LOC = 0x1640; + uint256 internal constant W4_EVAL_LOC = 0x1660; + uint256 internal constant S_EVAL_LOC = 0x1680; + uint256 internal constant Z_EVAL_LOC = 0x16a0; + uint256 internal constant Z_LOOKUP_EVAL_LOC = 0x16c0; + uint256 internal constant Q1_EVAL_LOC = 0x16e0; + uint256 internal constant Q2_EVAL_LOC = 0x1700; + uint256 internal constant Q3_EVAL_LOC = 0x1720; + uint256 internal constant Q4_EVAL_LOC = 0x1740; + uint256 internal constant QM_EVAL_LOC = 0x1760; + uint256 internal constant QC_EVAL_LOC = 0x1780; + uint256 internal constant QARITH_EVAL_LOC = 0x17a0; + uint256 internal constant QSORT_EVAL_LOC = 0x17c0; + uint256 internal constant QELLIPTIC_EVAL_LOC = 0x17e0; + uint256 internal constant QAUX_EVAL_LOC = 0x1800; + uint256 internal constant TABLE1_EVAL_LOC = 0x1840; + uint256 internal constant TABLE2_EVAL_LOC = 0x1860; + uint256 internal constant TABLE3_EVAL_LOC = 0x1880; + uint256 internal constant TABLE4_EVAL_LOC = 0x18a0; + uint256 internal constant TABLE_TYPE_EVAL_LOC = 0x18c0; + uint256 internal constant ID1_EVAL_LOC = 0x18e0; + uint256 internal constant ID2_EVAL_LOC = 0x1900; + uint256 internal constant ID3_EVAL_LOC = 0x1920; + uint256 internal constant ID4_EVAL_LOC = 0x1940; + uint256 internal constant SIGMA1_EVAL_LOC = 0x1960; + uint256 internal constant SIGMA2_EVAL_LOC = 0x1980; + uint256 internal constant SIGMA3_EVAL_LOC = 0x19a0; + uint256 internal constant SIGMA4_EVAL_LOC = 0x19c0; + uint256 internal constant W1_OMEGA_EVAL_LOC = 0x19e0; + uint256 internal constant W2_OMEGA_EVAL_LOC = 0x2000; + uint256 internal constant W3_OMEGA_EVAL_LOC = 0x2020; + uint256 internal constant W4_OMEGA_EVAL_LOC = 0x2040; + uint256 internal constant S_OMEGA_EVAL_LOC = 0x2060; + uint256 internal constant Z_OMEGA_EVAL_LOC = 0x2080; + uint256 internal constant Z_LOOKUP_OMEGA_EVAL_LOC = 0x20a0; + uint256 internal constant TABLE1_OMEGA_EVAL_LOC = 0x20c0; + uint256 internal constant TABLE2_OMEGA_EVAL_LOC = 0x20e0; + uint256 internal constant TABLE3_OMEGA_EVAL_LOC = 0x2100; + uint256 internal constant TABLE4_OMEGA_EVAL_LOC = 0x2120; + + uint256 internal constant PI_Z_X_LOC = 0x2300; + uint256 internal constant PI_Z_Y_LOC = 0x2320; + uint256 internal constant PI_Z_OMEGA_X_LOC = 0x2340; + uint256 internal constant PI_Z_OMEGA_Y_LOC = 0x2360; + + // Used for elliptic widget. These are alias names for wire + shifted wire evaluations + uint256 internal constant X1_EVAL_LOC = W2_EVAL_LOC; + uint256 internal constant X2_EVAL_LOC = W1_OMEGA_EVAL_LOC; + uint256 internal constant X3_EVAL_LOC = W2_OMEGA_EVAL_LOC; + uint256 internal constant Y1_EVAL_LOC = W3_EVAL_LOC; + uint256 internal constant Y2_EVAL_LOC = W4_OMEGA_EVAL_LOC; + uint256 internal constant Y3_EVAL_LOC = W3_OMEGA_EVAL_LOC; + uint256 internal constant QBETA_LOC = Q3_EVAL_LOC; + uint256 internal constant QBETA_SQR_LOC = Q4_EVAL_LOC; + uint256 internal constant QSIGN_LOC = Q1_EVAL_LOC; + + // ### CHALLENGES MEMORY OFFSETS + + uint256 internal constant C_BETA_LOC = 0x2600; + uint256 internal constant C_GAMMA_LOC = 0x2620; + uint256 internal constant C_ALPHA_LOC = 0x2640; + uint256 internal constant C_ETA_LOC = 0x2660; + uint256 internal constant C_ETA_SQR_LOC = 0x2680; + uint256 internal constant C_ETA_CUBE_LOC = 0x26a0; + + uint256 internal constant C_ZETA_LOC = 0x26c0; + uint256 internal constant C_CURRENT_LOC = 0x26e0; + uint256 internal constant C_V0_LOC = 0x2700; + uint256 internal constant C_V1_LOC = 0x2720; + uint256 internal constant C_V2_LOC = 0x2740; + uint256 internal constant C_V3_LOC = 0x2760; + uint256 internal constant C_V4_LOC = 0x2780; + uint256 internal constant C_V5_LOC = 0x27a0; + uint256 internal constant C_V6_LOC = 0x27c0; + uint256 internal constant C_V7_LOC = 0x27e0; + uint256 internal constant C_V8_LOC = 0x2800; + uint256 internal constant C_V9_LOC = 0x2820; + uint256 internal constant C_V10_LOC = 0x2840; + uint256 internal constant C_V11_LOC = 0x2860; + uint256 internal constant C_V12_LOC = 0x2880; + uint256 internal constant C_V13_LOC = 0x28a0; + uint256 internal constant C_V14_LOC = 0x28c0; + uint256 internal constant C_V15_LOC = 0x28e0; + uint256 internal constant C_V16_LOC = 0x2900; + uint256 internal constant C_V17_LOC = 0x2920; + uint256 internal constant C_V18_LOC = 0x2940; + uint256 internal constant C_V19_LOC = 0x2960; + uint256 internal constant C_V20_LOC = 0x2980; + uint256 internal constant C_V21_LOC = 0x29a0; + uint256 internal constant C_V22_LOC = 0x29c0; + uint256 internal constant C_V23_LOC = 0x29e0; + uint256 internal constant C_V24_LOC = 0x2a00; + uint256 internal constant C_V25_LOC = 0x2a20; + uint256 internal constant C_V26_LOC = 0x2a40; + uint256 internal constant C_V27_LOC = 0x2a60; + uint256 internal constant C_V28_LOC = 0x2a80; + uint256 internal constant C_V29_LOC = 0x2aa0; + uint256 internal constant C_V30_LOC = 0x2ac0; + + uint256 internal constant C_U_LOC = 0x2b00; + + // ### LOCAL VARIABLES MEMORY OFFSETS + uint256 internal constant DELTA_NUMERATOR_LOC = 0x3000; + uint256 internal constant DELTA_DENOMINATOR_LOC = 0x3020; + uint256 internal constant ZETA_POW_N_LOC = 0x3040; + uint256 internal constant PUBLIC_INPUT_DELTA_LOC = 0x3060; + uint256 internal constant ZERO_POLY_LOC = 0x3080; + uint256 internal constant L_START_LOC = 0x30a0; + uint256 internal constant L_END_LOC = 0x30c0; + uint256 internal constant R_ZERO_EVAL_LOC = 0x30e0; + + uint256 internal constant PLOOKUP_DELTA_NUMERATOR_LOC = 0x3100; + uint256 internal constant PLOOKUP_DELTA_DENOMINATOR_LOC = 0x3120; + uint256 internal constant PLOOKUP_DELTA_LOC = 0x3140; + + uint256 internal constant ACCUMULATOR_X_LOC = 0x3160; + uint256 internal constant ACCUMULATOR_Y_LOC = 0x3180; + uint256 internal constant ACCUMULATOR2_X_LOC = 0x31a0; + uint256 internal constant ACCUMULATOR2_Y_LOC = 0x31c0; + uint256 internal constant PAIRING_LHS_X_LOC = 0x31e0; + uint256 internal constant PAIRING_LHS_Y_LOC = 0x3200; + uint256 internal constant PAIRING_RHS_X_LOC = 0x3220; + uint256 internal constant PAIRING_RHS_Y_LOC = 0x3240; + + // ### SUCCESS FLAG MEMORY LOCATIONS + uint256 internal constant GRAND_PRODUCT_SUCCESS_FLAG = 0x3300; + uint256 internal constant ARITHMETIC_TERM_SUCCESS_FLAG = 0x3020; + uint256 internal constant BATCH_OPENING_SUCCESS_FLAG = 0x3340; + uint256 internal constant OPENING_COMMITMENT_SUCCESS_FLAG = 0x3360; + uint256 internal constant PAIRING_PREAMBLE_SUCCESS_FLAG = 0x3380; + uint256 internal constant PAIRING_SUCCESS_FLAG = 0x33a0; + uint256 internal constant RESULT_FLAG = 0x33c0; + + // misc stuff + uint256 internal constant OMEGA_INVERSE_LOC = 0x3400; + uint256 internal constant C_ALPHA_SQR_LOC = 0x3420; + uint256 internal constant C_ALPHA_CUBE_LOC = 0x3440; + uint256 internal constant C_ALPHA_QUAD_LOC = 0x3460; + uint256 internal constant C_ALPHA_BASE_LOC = 0x3480; + + // ### RECURSION VARIABLE MEMORY LOCATIONS + uint256 internal constant RECURSIVE_P1_X_LOC = 0x3500; + uint256 internal constant RECURSIVE_P1_Y_LOC = 0x3520; + uint256 internal constant RECURSIVE_P2_X_LOC = 0x3540; + uint256 internal constant RECURSIVE_P2_Y_LOC = 0x3560; + + uint256 internal constant PUBLIC_INPUTS_HASH_LOCATION = 0x3580; + + // sub-identity storage + uint256 internal constant PERMUTATION_IDENTITY = 0x3600; + uint256 internal constant PLOOKUP_IDENTITY = 0x3620; + uint256 internal constant ARITHMETIC_IDENTITY = 0x3640; + uint256 internal constant SORT_IDENTITY = 0x3660; + uint256 internal constant ELLIPTIC_IDENTITY = 0x3680; + uint256 internal constant AUX_IDENTITY = 0x36a0; + uint256 internal constant AUX_NON_NATIVE_FIELD_EVALUATION = 0x36c0; + uint256 internal constant AUX_LIMB_ACCUMULATOR_EVALUATION = 0x36e0; + uint256 internal constant AUX_RAM_CONSISTENCY_EVALUATION = 0x3700; + uint256 internal constant AUX_ROM_CONSISTENCY_EVALUATION = 0x3720; + uint256 internal constant AUX_MEMORY_EVALUATION = 0x3740; + + uint256 internal constant QUOTIENT_EVAL_LOC = 0x3760; + uint256 internal constant ZERO_POLY_INVERSE_LOC = 0x3780; + + // when hashing public inputs we use memory at NU_CHALLENGE_INPUT_LOC_A, as the hash input size is unknown at compile time + uint256 internal constant NU_CHALLENGE_INPUT_LOC_A = 0x37a0; + uint256 internal constant NU_CHALLENGE_INPUT_LOC_B = 0x37c0; + uint256 internal constant NU_CHALLENGE_INPUT_LOC_C = 0x37e0; + + bytes4 internal constant PUBLIC_INPUT_INVALID_BN128_G1_POINT_SELECTOR = 0xeba9f4a6; + bytes4 internal constant PUBLIC_INPUT_GE_P_SELECTOR = 0x374a972f; + bytes4 internal constant MOD_EXP_FAILURE_SELECTOR = 0xf894a7bc; + bytes4 internal constant EC_SCALAR_MUL_FAILURE_SELECTOR = 0xf755f369; + bytes4 internal constant PROOF_FAILURE_SELECTOR = 0x0711fcec; + + uint256 internal constant ETA_INPUT_LENGTH = 0xc0; // W1, W2, W3 = 6 * 0x20 bytes + + // We need to hash 41 field elements when generating the NU challenge + // w1, w2, w3, w4, s, z, z_lookup, q1, q2, q3, q4, qm, qc, qarith (14) + // qsort, qelliptic, qaux, sigma1, sigma2, sigma, sigma4, (7) + // table1, table2, table3, table4, tabletype, id1, id2, id3, id4, (9) + // w1_omega, w2_omega, w3_omega, w4_omega, s_omega, z_omega, z_lookup_omega, (7) + // table1_omega, table2_omega, table3_omega, table4_omega (4) + uint256 internal constant NU_INPUT_LENGTH = 0x520; // 0x520 = 41 * 0x20 + + // There are ELEVEN G1 group elements added into the transcript in the `beta` round, that we need to skip over + // W1, W2, W3, W4, S, Z, Z_LOOKUP, T1, T2, T3, T4 + uint256 internal constant NU_CALLDATA_SKIP_LENGTH = 0x2c0; // 11 * 0x40 = 0x2c0 + + uint256 internal constant NEGATIVE_INVERSE_OF_2_MODULO_P = + 0x183227397098d014dc2822db40c0ac2e9419f4243cdcb848a1f0fac9f8000000; + uint256 internal constant LIMB_SIZE = 0x100000000000000000; // 2<<68 + uint256 internal constant SUBLIMB_SHIFT = 0x4000; // 2<<14 + + error PUBLIC_INPUT_COUNT_INVALID(uint256 expected, uint256 actual); + error PUBLIC_INPUT_INVALID_BN128_G1_POINT(); + error PUBLIC_INPUT_GE_P(); + error MOD_EXP_FAILURE(); + error EC_SCALAR_MUL_FAILURE(); + error PROOF_FAILURE(); + + function getVerificationKeyHash() public pure virtual returns (bytes32); + + function loadVerificationKey(uint256 _vk, uint256 _omegaInverseLoc) internal pure virtual; + + /** + * @notice Verify a Ultra Plonk proof + * @param _proof - The serialized proof + * @param _publicInputs - An array of the public inputs + * @return True if proof is valid, reverts otherwise + */ + function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external view returns (bool) { + loadVerificationKey(N_LOC, OMEGA_INVERSE_LOC); + + uint256 requiredPublicInputCount; + assembly { + requiredPublicInputCount := mload(NUM_INPUTS_LOC) + } + if (requiredPublicInputCount != _publicInputs.length) { + revert PUBLIC_INPUT_COUNT_INVALID(requiredPublicInputCount, _publicInputs.length); + } + + assembly { + let q := 21888242871839275222246405745257275088696311157297823662689037894645226208583 // EC group order + let p := 21888242871839275222246405745257275088548364400416034343698204186575808495617 // Prime field order + + /** + * LOAD PROOF FROM CALLDATA + */ + { + let data_ptr := add(calldataload(0x04), 0x24) + + mstore(W1_Y_LOC, mod(calldataload(data_ptr), q)) + mstore(W1_X_LOC, mod(calldataload(add(data_ptr, 0x20)), q)) + + mstore(W2_Y_LOC, mod(calldataload(add(data_ptr, 0x40)), q)) + mstore(W2_X_LOC, mod(calldataload(add(data_ptr, 0x60)), q)) + + mstore(W3_Y_LOC, mod(calldataload(add(data_ptr, 0x80)), q)) + mstore(W3_X_LOC, mod(calldataload(add(data_ptr, 0xa0)), q)) + + mstore(W4_Y_LOC, mod(calldataload(add(data_ptr, 0xc0)), q)) + mstore(W4_X_LOC, mod(calldataload(add(data_ptr, 0xe0)), q)) + + mstore(S_Y_LOC, mod(calldataload(add(data_ptr, 0x100)), q)) + mstore(S_X_LOC, mod(calldataload(add(data_ptr, 0x120)), q)) + mstore(Z_Y_LOC, mod(calldataload(add(data_ptr, 0x140)), q)) + mstore(Z_X_LOC, mod(calldataload(add(data_ptr, 0x160)), q)) + mstore(Z_LOOKUP_Y_LOC, mod(calldataload(add(data_ptr, 0x180)), q)) + mstore(Z_LOOKUP_X_LOC, mod(calldataload(add(data_ptr, 0x1a0)), q)) + mstore(T1_Y_LOC, mod(calldataload(add(data_ptr, 0x1c0)), q)) + mstore(T1_X_LOC, mod(calldataload(add(data_ptr, 0x1e0)), q)) + + mstore(T2_Y_LOC, mod(calldataload(add(data_ptr, 0x200)), q)) + mstore(T2_X_LOC, mod(calldataload(add(data_ptr, 0x220)), q)) + + mstore(T3_Y_LOC, mod(calldataload(add(data_ptr, 0x240)), q)) + mstore(T3_X_LOC, mod(calldataload(add(data_ptr, 0x260)), q)) + + mstore(T4_Y_LOC, mod(calldataload(add(data_ptr, 0x280)), q)) + mstore(T4_X_LOC, mod(calldataload(add(data_ptr, 0x2a0)), q)) + + mstore(W1_EVAL_LOC, mod(calldataload(add(data_ptr, 0x2c0)), p)) + mstore(W2_EVAL_LOC, mod(calldataload(add(data_ptr, 0x2e0)), p)) + mstore(W3_EVAL_LOC, mod(calldataload(add(data_ptr, 0x300)), p)) + mstore(W4_EVAL_LOC, mod(calldataload(add(data_ptr, 0x320)), p)) + mstore(S_EVAL_LOC, mod(calldataload(add(data_ptr, 0x340)), p)) + mstore(Z_EVAL_LOC, mod(calldataload(add(data_ptr, 0x360)), p)) + mstore(Z_LOOKUP_EVAL_LOC, mod(calldataload(add(data_ptr, 0x380)), p)) + mstore(Q1_EVAL_LOC, mod(calldataload(add(data_ptr, 0x3a0)), p)) + mstore(Q2_EVAL_LOC, mod(calldataload(add(data_ptr, 0x3c0)), p)) + mstore(Q3_EVAL_LOC, mod(calldataload(add(data_ptr, 0x3e0)), p)) + mstore(Q4_EVAL_LOC, mod(calldataload(add(data_ptr, 0x400)), p)) + mstore(QM_EVAL_LOC, mod(calldataload(add(data_ptr, 0x420)), p)) + mstore(QC_EVAL_LOC, mod(calldataload(add(data_ptr, 0x440)), p)) + mstore(QARITH_EVAL_LOC, mod(calldataload(add(data_ptr, 0x460)), p)) + mstore(QSORT_EVAL_LOC, mod(calldataload(add(data_ptr, 0x480)), p)) + mstore(QELLIPTIC_EVAL_LOC, mod(calldataload(add(data_ptr, 0x4a0)), p)) + mstore(QAUX_EVAL_LOC, mod(calldataload(add(data_ptr, 0x4c0)), p)) + + mstore(SIGMA1_EVAL_LOC, mod(calldataload(add(data_ptr, 0x4e0)), p)) + mstore(SIGMA2_EVAL_LOC, mod(calldataload(add(data_ptr, 0x500)), p)) + + mstore(SIGMA3_EVAL_LOC, mod(calldataload(add(data_ptr, 0x520)), p)) + mstore(SIGMA4_EVAL_LOC, mod(calldataload(add(data_ptr, 0x540)), p)) + + mstore(TABLE1_EVAL_LOC, mod(calldataload(add(data_ptr, 0x560)), p)) + mstore(TABLE2_EVAL_LOC, mod(calldataload(add(data_ptr, 0x580)), p)) + mstore(TABLE3_EVAL_LOC, mod(calldataload(add(data_ptr, 0x5a0)), p)) + mstore(TABLE4_EVAL_LOC, mod(calldataload(add(data_ptr, 0x5c0)), p)) + mstore(TABLE_TYPE_EVAL_LOC, mod(calldataload(add(data_ptr, 0x5e0)), p)) + + mstore(ID1_EVAL_LOC, mod(calldataload(add(data_ptr, 0x600)), p)) + mstore(ID2_EVAL_LOC, mod(calldataload(add(data_ptr, 0x620)), p)) + mstore(ID3_EVAL_LOC, mod(calldataload(add(data_ptr, 0x640)), p)) + mstore(ID4_EVAL_LOC, mod(calldataload(add(data_ptr, 0x660)), p)) + + mstore(W1_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x680)), p)) + mstore(W2_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x6a0)), p)) + mstore(W3_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x6c0)), p)) + mstore(W4_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x6e0)), p)) + mstore(S_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x700)), p)) + + mstore(Z_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x720)), p)) + + mstore(Z_LOOKUP_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x740)), p)) + mstore(TABLE1_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x760)), p)) + mstore(TABLE2_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x780)), p)) + mstore(TABLE3_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x7a0)), p)) + mstore(TABLE4_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x7c0)), p)) + + mstore(PI_Z_Y_LOC, mod(calldataload(add(data_ptr, 0x7e0)), q)) + mstore(PI_Z_X_LOC, mod(calldataload(add(data_ptr, 0x800)), q)) + + mstore(PI_Z_OMEGA_Y_LOC, mod(calldataload(add(data_ptr, 0x820)), q)) + mstore(PI_Z_OMEGA_X_LOC, mod(calldataload(add(data_ptr, 0x840)), q)) + } + + /** + * LOAD RECURSIVE PROOF INTO MEMORY + */ + { + if mload(CONTAINS_RECURSIVE_PROOF_LOC) { + let public_inputs_ptr := add(calldataload(0x24), 0x24) + let index_counter := add(shl(5, mload(RECURSIVE_PROOF_PUBLIC_INPUT_INDICES_LOC)), public_inputs_ptr) + + let x0 := calldataload(index_counter) + x0 := add(x0, shl(68, calldataload(add(index_counter, 0x20)))) + x0 := add(x0, shl(136, calldataload(add(index_counter, 0x40)))) + x0 := add(x0, shl(204, calldataload(add(index_counter, 0x60)))) + let y0 := calldataload(add(index_counter, 0x80)) + y0 := add(y0, shl(68, calldataload(add(index_counter, 0xa0)))) + y0 := add(y0, shl(136, calldataload(add(index_counter, 0xc0)))) + y0 := add(y0, shl(204, calldataload(add(index_counter, 0xe0)))) + let x1 := calldataload(add(index_counter, 0x100)) + x1 := add(x1, shl(68, calldataload(add(index_counter, 0x120)))) + x1 := add(x1, shl(136, calldataload(add(index_counter, 0x140)))) + x1 := add(x1, shl(204, calldataload(add(index_counter, 0x160)))) + let y1 := calldataload(add(index_counter, 0x180)) + y1 := add(y1, shl(68, calldataload(add(index_counter, 0x1a0)))) + y1 := add(y1, shl(136, calldataload(add(index_counter, 0x1c0)))) + y1 := add(y1, shl(204, calldataload(add(index_counter, 0x1e0)))) + mstore(RECURSIVE_P1_X_LOC, x0) + mstore(RECURSIVE_P1_Y_LOC, y0) + mstore(RECURSIVE_P2_X_LOC, x1) + mstore(RECURSIVE_P2_Y_LOC, y1) + + // validate these are valid bn128 G1 points + if iszero(and(and(lt(x0, q), lt(x1, q)), and(lt(y0, q), lt(y1, q)))) { + mstore(0x00, PUBLIC_INPUT_INVALID_BN128_G1_POINT_SELECTOR) + revert(0x00, 0x04) + } + } + } + + { + /** + * Generate initial challenge + */ + mstore(0x00, shl(224, mload(N_LOC))) + mstore(0x04, shl(224, mload(NUM_INPUTS_LOC))) + let challenge := keccak256(0x00, 0x08) + + /** + * Generate eta challenge + */ + mstore(PUBLIC_INPUTS_HASH_LOCATION, challenge) + // The public input location is stored at 0x24, we then add 0x24 to skip selector and the length of public inputs + let public_inputs_start := add(calldataload(0x24), 0x24) + // copy the public inputs over + let public_input_size := mul(mload(NUM_INPUTS_LOC), 0x20) + calldatacopy(add(PUBLIC_INPUTS_HASH_LOCATION, 0x20), public_inputs_start, public_input_size) + + // copy W1, W2, W3 into challenge. Each point is 0x40 bytes, so load 0xc0 = 3 * 0x40 bytes (ETA input length) + let w_start := add(calldataload(0x04), 0x24) + calldatacopy(add(add(PUBLIC_INPUTS_HASH_LOCATION, 0x20), public_input_size), w_start, ETA_INPUT_LENGTH) + + // Challenge is the old challenge + public inputs + W1, W2, W3 (0x20 + public_input_size + 0xc0) + let challenge_bytes_size := add(0x20, add(public_input_size, ETA_INPUT_LENGTH)) + + challenge := keccak256(PUBLIC_INPUTS_HASH_LOCATION, challenge_bytes_size) + { + let eta := mod(challenge, p) + mstore(C_ETA_LOC, eta) + mstore(C_ETA_SQR_LOC, mulmod(eta, eta, p)) + mstore(C_ETA_CUBE_LOC, mulmod(mload(C_ETA_SQR_LOC), eta, p)) + } + + /** + * Generate beta challenge + */ + mstore(0x00, challenge) + mstore(0x20, mload(W4_Y_LOC)) + mstore(0x40, mload(W4_X_LOC)) + mstore(0x60, mload(S_Y_LOC)) + mstore(0x80, mload(S_X_LOC)) + challenge := keccak256(0x00, 0xa0) + mstore(C_BETA_LOC, mod(challenge, p)) + + /** + * Generate gamma challenge + */ + mstore(0x00, challenge) + mstore8(0x20, 0x01) + challenge := keccak256(0x00, 0x21) + mstore(C_GAMMA_LOC, mod(challenge, p)) + + /** + * Generate alpha challenge + */ + mstore(0x00, challenge) + mstore(0x20, mload(Z_Y_LOC)) + mstore(0x40, mload(Z_X_LOC)) + mstore(0x60, mload(Z_LOOKUP_Y_LOC)) + mstore(0x80, mload(Z_LOOKUP_X_LOC)) + challenge := keccak256(0x00, 0xa0) + mstore(C_ALPHA_LOC, mod(challenge, p)) + + /** + * Compute and store some powers of alpha for future computations + */ + let alpha := mload(C_ALPHA_LOC) + mstore(C_ALPHA_SQR_LOC, mulmod(alpha, alpha, p)) + mstore(C_ALPHA_CUBE_LOC, mulmod(mload(C_ALPHA_SQR_LOC), alpha, p)) + mstore(C_ALPHA_QUAD_LOC, mulmod(mload(C_ALPHA_CUBE_LOC), alpha, p)) + mstore(C_ALPHA_BASE_LOC, alpha) + + /** + * Generate zeta challenge + */ + mstore(0x00, challenge) + mstore(0x20, mload(T1_Y_LOC)) + mstore(0x40, mload(T1_X_LOC)) + mstore(0x60, mload(T2_Y_LOC)) + mstore(0x80, mload(T2_X_LOC)) + mstore(0xa0, mload(T3_Y_LOC)) + mstore(0xc0, mload(T3_X_LOC)) + mstore(0xe0, mload(T4_Y_LOC)) + mstore(0x100, mload(T4_X_LOC)) + + challenge := keccak256(0x00, 0x120) + + mstore(C_ZETA_LOC, mod(challenge, p)) + mstore(C_CURRENT_LOC, challenge) + } + + /** + * EVALUATE FIELD OPERATIONS + */ + + /** + * COMPUTE PUBLIC INPUT DELTA + * ΔPI = âˆáµ¢âˆˆâ„“(wáµ¢ + β σ(i) + γ) / âˆáµ¢âˆˆâ„“(wáµ¢ + β σ'(i) + γ) + */ + { + let beta := mload(C_BETA_LOC) // β + let gamma := mload(C_GAMMA_LOC) // γ + let work_root := mload(OMEGA_LOC) // ω + let numerator_value := 1 + let denominator_value := 1 + + let p_clone := p // move p to the front of the stack + let valid_inputs := true + + // Load the starting point of the public inputs (jump over the selector and the length of public inputs [0x24]) + let public_inputs_ptr := add(calldataload(0x24), 0x24) + + // endpoint_ptr = public_inputs_ptr + num_inputs * 0x20. // every public input is 0x20 bytes + let endpoint_ptr := add(public_inputs_ptr, mul(mload(NUM_INPUTS_LOC), 0x20)) + + // root_1 = β * 0x05 + let root_1 := mulmod(beta, 0x05, p_clone) // k1.β + // root_2 = β * 0x0c + let root_2 := mulmod(beta, 0x0c, p_clone) + // @note 0x05 + 0x07 == 0x0c == external coset generator + + for {} lt(public_inputs_ptr, endpoint_ptr) { public_inputs_ptr := add(public_inputs_ptr, 0x20) } { + /** + * input = public_input[i] + * valid_inputs &= input < p + * temp = input + gamma + * numerator_value *= (β.σ(i) + wáµ¢ + γ) // σ(i) = 0x05.ωⱠ+ * denominator_value *= (β.σ'(i) + wáµ¢ + γ) // σ'(i) = 0x0c.ωⱠ+ * root_1 *= ω + * root_2 *= ω + */ + + let input := calldataload(public_inputs_ptr) + valid_inputs := and(valid_inputs, lt(input, p_clone)) + let temp := addmod(input, gamma, p_clone) + + numerator_value := mulmod(numerator_value, add(root_1, temp), p_clone) + denominator_value := mulmod(denominator_value, add(root_2, temp), p_clone) + + root_1 := mulmod(root_1, work_root, p_clone) + root_2 := mulmod(root_2, work_root, p_clone) + } + + // Revert if not all public inputs are field elements (i.e. < p) + if iszero(valid_inputs) { + mstore(0x00, PUBLIC_INPUT_GE_P_SELECTOR) + revert(0x00, 0x04) + } + + mstore(DELTA_NUMERATOR_LOC, numerator_value) + mstore(DELTA_DENOMINATOR_LOC, denominator_value) + } + + /** + * Compute Plookup delta factor [γ(1 + β)]^{n-k} + * k = num roots cut out of Z_H = 4 + */ + { + let delta_base := mulmod(mload(C_GAMMA_LOC), addmod(mload(C_BETA_LOC), 1, p), p) + let delta_numerator := delta_base + { + let exponent := mload(N_LOC) + let count := 1 + for {} lt(count, exponent) { count := add(count, count) } { + delta_numerator := mulmod(delta_numerator, delta_numerator, p) + } + } + mstore(PLOOKUP_DELTA_NUMERATOR_LOC, delta_numerator) + + let delta_denominator := mulmod(delta_base, delta_base, p) + delta_denominator := mulmod(delta_denominator, delta_denominator, p) + mstore(PLOOKUP_DELTA_DENOMINATOR_LOC, delta_denominator) + } + /** + * Compute lagrange poly and vanishing poly fractions + */ + { + /** + * vanishing_numerator = zeta + * ZETA_POW_N = zeta^n + * vanishing_numerator -= 1 + * accumulating_root = omega_inverse + * work_root = p - accumulating_root + * domain_inverse = domain_inverse + * vanishing_denominator = zeta + work_root + * work_root *= accumulating_root + * vanishing_denominator *= (zeta + work_root) + * work_root *= accumulating_root + * vanishing_denominator *= (zeta + work_root) + * vanishing_denominator *= (zeta + (zeta + accumulating_root)) + * work_root = omega + * lagrange_numerator = vanishing_numerator * domain_inverse + * l_start_denominator = zeta - 1 + * accumulating_root = work_root^2 + * l_end_denominator = accumulating_root^2 * work_root * zeta - 1 + * Note: l_end_denominator term contains a term \omega^5 to cut out 5 roots of unity from vanishing poly + */ + + let zeta := mload(C_ZETA_LOC) + + // compute zeta^n, where n is a power of 2 + let vanishing_numerator := zeta + { + // pow_small + let exponent := mload(N_LOC) + let count := 1 + for {} lt(count, exponent) { count := add(count, count) } { + vanishing_numerator := mulmod(vanishing_numerator, vanishing_numerator, p) + } + } + mstore(ZETA_POW_N_LOC, vanishing_numerator) + vanishing_numerator := addmod(vanishing_numerator, sub(p, 1), p) + + let accumulating_root := mload(OMEGA_INVERSE_LOC) + let work_root := sub(p, accumulating_root) + let domain_inverse := mload(DOMAIN_INVERSE_LOC) + + let vanishing_denominator := addmod(zeta, work_root, p) + work_root := mulmod(work_root, accumulating_root, p) + vanishing_denominator := mulmod(vanishing_denominator, addmod(zeta, work_root, p), p) + work_root := mulmod(work_root, accumulating_root, p) + vanishing_denominator := mulmod(vanishing_denominator, addmod(zeta, work_root, p), p) + vanishing_denominator := + mulmod(vanishing_denominator, addmod(zeta, mulmod(work_root, accumulating_root, p), p), p) + + work_root := mload(OMEGA_LOC) + + let lagrange_numerator := mulmod(vanishing_numerator, domain_inverse, p) + let l_start_denominator := addmod(zeta, sub(p, 1), p) + + accumulating_root := mulmod(work_root, work_root, p) + + let l_end_denominator := + addmod( + mulmod(mulmod(mulmod(accumulating_root, accumulating_root, p), work_root, p), zeta, p), sub(p, 1), p + ) + + /** + * Compute inversions using Montgomery's batch inversion trick + */ + let accumulator := mload(DELTA_DENOMINATOR_LOC) + let t0 := accumulator + accumulator := mulmod(accumulator, vanishing_denominator, p) + let t1 := accumulator + accumulator := mulmod(accumulator, vanishing_numerator, p) + let t2 := accumulator + accumulator := mulmod(accumulator, l_start_denominator, p) + let t3 := accumulator + accumulator := mulmod(accumulator, mload(PLOOKUP_DELTA_DENOMINATOR_LOC), p) + let t4 := accumulator + { + mstore(0, 0x20) + mstore(0x20, 0x20) + mstore(0x40, 0x20) + mstore(0x60, mulmod(accumulator, l_end_denominator, p)) + mstore(0x80, sub(p, 2)) + mstore(0xa0, p) + if iszero(staticcall(gas(), 0x05, 0x00, 0xc0, 0x00, 0x20)) { + mstore(0x0, MOD_EXP_FAILURE_SELECTOR) + revert(0x00, 0x04) + } + accumulator := mload(0x00) + } + + t4 := mulmod(accumulator, t4, p) + accumulator := mulmod(accumulator, l_end_denominator, p) + + t3 := mulmod(accumulator, t3, p) + accumulator := mulmod(accumulator, mload(PLOOKUP_DELTA_DENOMINATOR_LOC), p) + + t2 := mulmod(accumulator, t2, p) + accumulator := mulmod(accumulator, l_start_denominator, p) + + t1 := mulmod(accumulator, t1, p) + accumulator := mulmod(accumulator, vanishing_numerator, p) + + t0 := mulmod(accumulator, t0, p) + accumulator := mulmod(accumulator, vanishing_denominator, p) + + accumulator := mulmod(mulmod(accumulator, accumulator, p), mload(DELTA_DENOMINATOR_LOC), p) + + mstore(PUBLIC_INPUT_DELTA_LOC, mulmod(mload(DELTA_NUMERATOR_LOC), accumulator, p)) + mstore(ZERO_POLY_LOC, mulmod(vanishing_numerator, t0, p)) + mstore(ZERO_POLY_INVERSE_LOC, mulmod(vanishing_denominator, t1, p)) + mstore(L_START_LOC, mulmod(lagrange_numerator, t2, p)) + mstore(PLOOKUP_DELTA_LOC, mulmod(mload(PLOOKUP_DELTA_NUMERATOR_LOC), t3, p)) + mstore(L_END_LOC, mulmod(lagrange_numerator, t4, p)) + } + + /** + * UltraPlonk Widget Ordering: + * + * 1. Permutation widget + * 2. Plookup widget + * 3. Arithmetic widget + * 4. Fixed base widget (?) + * 5. GenPermSort widget + * 6. Elliptic widget + * 7. Auxiliary widget + */ + + /** + * COMPUTE PERMUTATION WIDGET EVALUATION + */ + { + let alpha := mload(C_ALPHA_LOC) + let beta := mload(C_BETA_LOC) + let gamma := mload(C_GAMMA_LOC) + + /** + * t1 = (W1 + gamma + beta * ID1) * (W2 + gamma + beta * ID2) + * t2 = (W3 + gamma + beta * ID3) * (W4 + gamma + beta * ID4) + * result = alpha_base * z_eval * t1 * t2 + * t1 = (W1 + gamma + beta * sigma_1_eval) * (W2 + gamma + beta * sigma_2_eval) + * t2 = (W2 + gamma + beta * sigma_3_eval) * (W3 + gamma + beta * sigma_4_eval) + * result -= (alpha_base * z_omega_eval * t1 * t2) + */ + let t1 := + mulmod( + add(add(mload(W1_EVAL_LOC), gamma), mulmod(beta, mload(ID1_EVAL_LOC), p)), + add(add(mload(W2_EVAL_LOC), gamma), mulmod(beta, mload(ID2_EVAL_LOC), p)), + p + ) + let t2 := + mulmod( + add(add(mload(W3_EVAL_LOC), gamma), mulmod(beta, mload(ID3_EVAL_LOC), p)), + add(add(mload(W4_EVAL_LOC), gamma), mulmod(beta, mload(ID4_EVAL_LOC), p)), + p + ) + let result := mulmod(mload(C_ALPHA_BASE_LOC), mulmod(mload(Z_EVAL_LOC), mulmod(t1, t2, p), p), p) + t1 := + mulmod( + add(add(mload(W1_EVAL_LOC), gamma), mulmod(beta, mload(SIGMA1_EVAL_LOC), p)), + add(add(mload(W2_EVAL_LOC), gamma), mulmod(beta, mload(SIGMA2_EVAL_LOC), p)), + p + ) + t2 := + mulmod( + add(add(mload(W3_EVAL_LOC), gamma), mulmod(beta, mload(SIGMA3_EVAL_LOC), p)), + add(add(mload(W4_EVAL_LOC), gamma), mulmod(beta, mload(SIGMA4_EVAL_LOC), p)), + p + ) + result := + addmod( + result, + sub(p, mulmod(mload(C_ALPHA_BASE_LOC), mulmod(mload(Z_OMEGA_EVAL_LOC), mulmod(t1, t2, p), p), p)), + p + ) + + /** + * alpha_base *= alpha + * result += alpha_base . (L_{n-k}(Ê“) . (z(Ê“.ω) - ∆_{PI})) + * alpha_base *= alpha + * result += alpha_base . (L_1(Ê“)(Z(Ê“) - 1)) + * alpha_Base *= alpha + */ + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p)) + result := + addmod( + result, + mulmod( + mload(C_ALPHA_BASE_LOC), + mulmod( + mload(L_END_LOC), + addmod(mload(Z_OMEGA_EVAL_LOC), sub(p, mload(PUBLIC_INPUT_DELTA_LOC)), p), + p + ), + p + ), + p + ) + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p)) + mstore( + PERMUTATION_IDENTITY, + addmod( + result, + mulmod( + mload(C_ALPHA_BASE_LOC), + mulmod(mload(L_START_LOC), addmod(mload(Z_EVAL_LOC), sub(p, 1), p), p), + p + ), + p + ) + ) + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p)) + } + + /** + * COMPUTE PLOOKUP WIDGET EVALUATION + */ + { + /** + * Goal: f = (w1(z) + q2.w1(zω)) + η(w2(z) + qm.w2(zω)) + η²(w3(z) + qc.w_3(zω)) + q3(z).η³ + * f = η.q3(z) + * f += (w3(z) + qc.w_3(zω)) + * f *= η + * f += (w2(z) + qm.w2(zω)) + * f *= η + * f += (w1(z) + q2.w1(zω)) + */ + let f := mulmod(mload(C_ETA_LOC), mload(Q3_EVAL_LOC), p) + f := + addmod(f, addmod(mload(W3_EVAL_LOC), mulmod(mload(QC_EVAL_LOC), mload(W3_OMEGA_EVAL_LOC), p), p), p) + f := mulmod(f, mload(C_ETA_LOC), p) + f := + addmod(f, addmod(mload(W2_EVAL_LOC), mulmod(mload(QM_EVAL_LOC), mload(W2_OMEGA_EVAL_LOC), p), p), p) + f := mulmod(f, mload(C_ETA_LOC), p) + f := + addmod(f, addmod(mload(W1_EVAL_LOC), mulmod(mload(Q2_EVAL_LOC), mload(W1_OMEGA_EVAL_LOC), p), p), p) + + // t(z) = table4(z).η³ + table3(z).η² + table2(z).η + table1(z) + let t := + addmod( + addmod( + addmod( + mulmod(mload(TABLE4_EVAL_LOC), mload(C_ETA_CUBE_LOC), p), + mulmod(mload(TABLE3_EVAL_LOC), mload(C_ETA_SQR_LOC), p), + p + ), + mulmod(mload(TABLE2_EVAL_LOC), mload(C_ETA_LOC), p), + p + ), + mload(TABLE1_EVAL_LOC), + p + ) + + // t(zw) = table4(zw).η³ + table3(zw).η² + table2(zw).η + table1(zw) + let t_omega := + addmod( + addmod( + addmod( + mulmod(mload(TABLE4_OMEGA_EVAL_LOC), mload(C_ETA_CUBE_LOC), p), + mulmod(mload(TABLE3_OMEGA_EVAL_LOC), mload(C_ETA_SQR_LOC), p), + p + ), + mulmod(mload(TABLE2_OMEGA_EVAL_LOC), mload(C_ETA_LOC), p), + p + ), + mload(TABLE1_OMEGA_EVAL_LOC), + p + ) + + /** + * Goal: numerator = (TABLE_TYPE_EVAL * f(z) + γ) * (t(z) + βt(zω) + γ(β + 1)) * (β + 1) + * gamma_beta_constant = γ(β + 1) + * numerator = f * TABLE_TYPE_EVAL + gamma + * temp0 = t(z) + t(zω) * β + gamma_beta_constant + * numerator *= temp0 + * numerator *= (β + 1) + * temp0 = alpha * l_1 + * numerator += temp0 + * numerator *= z_lookup(z) + * numerator -= temp0 + */ + let gamma_beta_constant := mulmod(mload(C_GAMMA_LOC), addmod(mload(C_BETA_LOC), 1, p), p) + let numerator := addmod(mulmod(f, mload(TABLE_TYPE_EVAL_LOC), p), mload(C_GAMMA_LOC), p) + let temp0 := addmod(addmod(t, mulmod(t_omega, mload(C_BETA_LOC), p), p), gamma_beta_constant, p) + numerator := mulmod(numerator, temp0, p) + numerator := mulmod(numerator, addmod(mload(C_BETA_LOC), 1, p), p) + temp0 := mulmod(mload(C_ALPHA_LOC), mload(L_START_LOC), p) + numerator := addmod(numerator, temp0, p) + numerator := mulmod(numerator, mload(Z_LOOKUP_EVAL_LOC), p) + numerator := addmod(numerator, sub(p, temp0), p) + + /** + * Goal: denominator = z_lookup(zω)*[s(z) + βs(zω) + γ(1 + β)] - [z_lookup(zω) - [γ(1 + β)]^{n-k}]*α²L_end(z) + * note: delta_factor = [γ(1 + β)]^{n-k} + * denominator = s(z) + βs(zω) + γ(β + 1) + * temp1 = α²L_end(z) + * denominator -= temp1 + * denominator *= z_lookup(zω) + * denominator += temp1 * delta_factor + * PLOOKUP_IDENTITY = (numerator - denominator).alpha_base + * alpha_base *= alpha^3 + */ + let denominator := + addmod( + addmod(mload(S_EVAL_LOC), mulmod(mload(S_OMEGA_EVAL_LOC), mload(C_BETA_LOC), p), p), + gamma_beta_constant, + p + ) + let temp1 := mulmod(mload(C_ALPHA_SQR_LOC), mload(L_END_LOC), p) + denominator := addmod(denominator, sub(p, temp1), p) + denominator := mulmod(denominator, mload(Z_LOOKUP_OMEGA_EVAL_LOC), p) + denominator := addmod(denominator, mulmod(temp1, mload(PLOOKUP_DELTA_LOC), p), p) + + mstore(PLOOKUP_IDENTITY, mulmod(addmod(numerator, sub(p, denominator), p), mload(C_ALPHA_BASE_LOC), p)) + + // update alpha + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_CUBE_LOC), p)) + } + + /** + * COMPUTE ARITHMETIC WIDGET EVALUATION + */ + { + /** + * The basic arithmetic gate identity in standard plonk is as follows. + * (w_1 . w_2 . q_m) + (w_1 . q_1) + (w_2 . q_2) + (w_3 . q_3) + (w_4 . q_4) + q_c = 0 + * However, for Ultraplonk, we extend this to support "passing" wires between rows (shown without alpha scaling below): + * q_arith * ( ( (-1/2) * (q_arith - 3) * q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c ) + + * (q_arith - 1)*( α * (q_arith - 2) * (w_1 + w_4 - w_1_omega + q_m) + w_4_omega) ) = 0 + * + * This formula results in several cases depending on q_arith: + * 1. q_arith == 0: Arithmetic gate is completely disabled + * + * 2. q_arith == 1: Everything in the minigate on the right is disabled. The equation is just a standard plonk equation + * with extra wires: q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c = 0 + * + * 3. q_arith == 2: The (w_1 + w_4 - ...) term is disabled. THe equation is: + * (1/2) * q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + w_4_omega = 0 + * It allows defining w_4 at next index (w_4_omega) in terms of current wire values + * + * 4. q_arith == 3: The product of w_1 and w_2 is disabled, but a mini addition gate is enabled. α allows us to split + * the equation into two: + * + * q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + 2 * w_4_omega = 0 + * and + * w_1 + w_4 - w_1_omega + q_m = 0 (we are reusing q_m here) + * + * 5. q_arith > 3: The product of w_1 and w_2 is scaled by (q_arith - 3), while the w_4_omega term is scaled by (q_arith - 1). + * The equation can be split into two: + * + * (q_arith - 3)* q_m * w_1 * w_ 2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + (q_arith - 1) * w_4_omega = 0 + * and + * w_1 + w_4 - w_1_omega + q_m = 0 + * + * The problem that q_m is used both in both equations can be dealt with by appropriately changing selector values at + * the next gate. Then we can treat (q_arith - 1) as a simulated q_6 selector and scale q_m to handle (q_arith - 3) at + * product. + */ + + let w1q1 := mulmod(mload(W1_EVAL_LOC), mload(Q1_EVAL_LOC), p) + let w2q2 := mulmod(mload(W2_EVAL_LOC), mload(Q2_EVAL_LOC), p) + let w3q3 := mulmod(mload(W3_EVAL_LOC), mload(Q3_EVAL_LOC), p) + let w4q3 := mulmod(mload(W4_EVAL_LOC), mload(Q4_EVAL_LOC), p) + + // @todo - Add a explicit test that hits QARITH == 3 + // w1w2qm := (w_1 . w_2 . q_m . (QARITH_EVAL_LOC - 3)) / 2 + let w1w2qm := + mulmod( + mulmod( + mulmod(mulmod(mload(W1_EVAL_LOC), mload(W2_EVAL_LOC), p), mload(QM_EVAL_LOC), p), + addmod(mload(QARITH_EVAL_LOC), sub(p, 3), p), + p + ), + NEGATIVE_INVERSE_OF_2_MODULO_P, + p + ) + + // (w_1 . w_2 . q_m . (q_arith - 3)) / -2) + (w_1 . q_1) + (w_2 . q_2) + (w_3 . q_3) + (w_4 . q_4) + q_c + let identity := + addmod( + mload(QC_EVAL_LOC), addmod(w4q3, addmod(w3q3, addmod(w2q2, addmod(w1q1, w1w2qm, p), p), p), p), p + ) + + // if q_arith == 3 we evaluate an additional mini addition gate (on top of the regular one), where: + // w_1 + w_4 - w_1_omega + q_m = 0 + // we use this gate to save an addition gate when adding or subtracting non-native field elements + // α * (q_arith - 2) * (w_1 + w_4 - w_1_omega + q_m) + let extra_small_addition_gate_identity := + mulmod( + mload(C_ALPHA_LOC), + mulmod( + addmod(mload(QARITH_EVAL_LOC), sub(p, 2), p), + addmod( + mload(QM_EVAL_LOC), + addmod( + sub(p, mload(W1_OMEGA_EVAL_LOC)), addmod(mload(W1_EVAL_LOC), mload(W4_EVAL_LOC), p), p + ), + p + ), + p + ), + p + ) + + // if q_arith == 2 OR q_arith == 3 we add the 4th wire of the NEXT gate into the arithmetic identity + // N.B. if q_arith > 2, this wire value will be scaled by (q_arith - 1) relative to the other gate wires! + // alpha_base * q_arith * (identity + (q_arith - 1) * (w_4_omega + extra_small_addition_gate_identity)) + mstore( + ARITHMETIC_IDENTITY, + mulmod( + mload(C_ALPHA_BASE_LOC), + mulmod( + mload(QARITH_EVAL_LOC), + addmod( + identity, + mulmod( + addmod(mload(QARITH_EVAL_LOC), sub(p, 1), p), + addmod(mload(W4_OMEGA_EVAL_LOC), extra_small_addition_gate_identity, p), + p + ), + p + ), + p + ), + p + ) + ) + + // update alpha + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_SQR_LOC), p)) + } + + /** + * COMPUTE GENPERMSORT WIDGET EVALUATION + */ + { + /** + * D1 = (w2 - w1) + * D2 = (w3 - w2) + * D3 = (w4 - w3) + * D4 = (w1_omega - w4) + * + * α_a = alpha_base + * α_b = alpha_base * α + * α_c = alpha_base * α^2 + * α_d = alpha_base * α^3 + * + * range_accumulator = ( + * D1(D1 - 1)(D1 - 2)(D1 - 3).α_a + + * D2(D2 - 1)(D2 - 2)(D2 - 3).α_b + + * D3(D3 - 1)(D3 - 2)(D3 - 3).α_c + + * D4(D4 - 1)(D4 - 2)(D4 - 3).α_d + + * ) . q_sort + */ + let minus_two := sub(p, 2) + let minus_three := sub(p, 3) + let d1 := addmod(mload(W2_EVAL_LOC), sub(p, mload(W1_EVAL_LOC)), p) + let d2 := addmod(mload(W3_EVAL_LOC), sub(p, mload(W2_EVAL_LOC)), p) + let d3 := addmod(mload(W4_EVAL_LOC), sub(p, mload(W3_EVAL_LOC)), p) + let d4 := addmod(mload(W1_OMEGA_EVAL_LOC), sub(p, mload(W4_EVAL_LOC)), p) + + let range_accumulator := + mulmod( + mulmod( + mulmod(addmod(mulmod(d1, d1, p), sub(p, d1), p), addmod(d1, minus_two, p), p), + addmod(d1, minus_three, p), + p + ), + mload(C_ALPHA_BASE_LOC), + p + ) + range_accumulator := + addmod( + range_accumulator, + mulmod( + mulmod( + mulmod(addmod(mulmod(d2, d2, p), sub(p, d2), p), addmod(d2, minus_two, p), p), + addmod(d2, minus_three, p), + p + ), + mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p), + p + ), + p + ) + range_accumulator := + addmod( + range_accumulator, + mulmod( + mulmod( + mulmod(addmod(mulmod(d3, d3, p), sub(p, d3), p), addmod(d3, minus_two, p), p), + addmod(d3, minus_three, p), + p + ), + mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_SQR_LOC), p), + p + ), + p + ) + range_accumulator := + addmod( + range_accumulator, + mulmod( + mulmod( + mulmod(addmod(mulmod(d4, d4, p), sub(p, d4), p), addmod(d4, minus_two, p), p), + addmod(d4, minus_three, p), + p + ), + mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_CUBE_LOC), p), + p + ), + p + ) + range_accumulator := mulmod(range_accumulator, mload(QSORT_EVAL_LOC), p) + + mstore(SORT_IDENTITY, range_accumulator) + + // update alpha + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_QUAD_LOC), p)) + } + + /** + * COMPUTE ELLIPTIC WIDGET EVALUATION + */ + { + /** + * endo_term = (-x_2) * x_1 * (x_3 * 2 + x_1) * q_beta + * endo_sqr_term = x_2^2 + * endo_sqr_term *= (x_3 - x_1) + * endo_sqr_term *= q_beta^2 + * leftovers = x_2^2 + * leftovers *= x_2 + * leftovers += x_1^2 * (x_3 + x_1) @follow-up Invalid comment in BB widget + * leftovers -= (y_2^2 + y_1^2) + * sign_term = y_2 * y_1 + * sign_term += sign_term + * sign_term *= q_sign + */ + + let endo_term := + mulmod( + mulmod( + mulmod(sub(p, mload(X2_EVAL_LOC)), mload(X1_EVAL_LOC), p), + addmod(addmod(mload(X3_EVAL_LOC), mload(X3_EVAL_LOC), p), mload(X1_EVAL_LOC), p), + p + ), + mload(QBETA_LOC), + p + ) + + let endo_sqr_term := mulmod(mload(X2_EVAL_LOC), mload(X2_EVAL_LOC), p) + endo_sqr_term := mulmod(endo_sqr_term, addmod(mload(X3_EVAL_LOC), sub(p, mload(X1_EVAL_LOC)), p), p) + endo_sqr_term := mulmod(endo_sqr_term, mload(QBETA_SQR_LOC), p) + + let leftovers := mulmod(mload(X2_EVAL_LOC), mload(X2_EVAL_LOC), p) + leftovers := mulmod(leftovers, mload(X2_EVAL_LOC), p) + leftovers := + addmod( + leftovers, + mulmod( + mulmod(mload(X1_EVAL_LOC), mload(X1_EVAL_LOC), p), + addmod(mload(X3_EVAL_LOC), mload(X1_EVAL_LOC), p), + p + ), + p + ) + leftovers := + addmod( + leftovers, + sub( + p, + addmod( + mulmod(mload(Y2_EVAL_LOC), mload(Y2_EVAL_LOC), p), + mulmod(mload(Y1_EVAL_LOC), mload(Y1_EVAL_LOC), p), + p + ) + ), + p + ) + + let sign_term := mulmod(mload(Y2_EVAL_LOC), mload(Y1_EVAL_LOC), p) + sign_term := addmod(sign_term, sign_term, p) + sign_term := mulmod(sign_term, mload(QSIGN_LOC), p) + + /** + * x_identity = endo_term + endo_sqr_term + sign_term + leftovers + * x_identity *= alpha_base + * endo_term = (x_2 * q_beta) * (y_3 + y_1) + * sign_term = -((y2 * q_sign) * (x_1 + x_3)) + * leftovers = - x1 * (y_3 + y_1) + y_1 * (x_1 - x_3) + * y_identity = (endo_term + sign_term + leftovers) * (alpha_base * α) + */ + + let x_identity := addmod(addmod(endo_term, endo_sqr_term, p), addmod(sign_term, leftovers, p), p) + x_identity := mulmod(x_identity, mload(C_ALPHA_BASE_LOC), p) + endo_term := + mulmod( + mulmod(mload(X2_EVAL_LOC), mload(QBETA_LOC), p), + addmod(mload(Y3_EVAL_LOC), mload(Y1_EVAL_LOC), p), + p + ) + sign_term := + sub( + p, + mulmod( + mulmod(mload(Y2_EVAL_LOC), mload(QSIGN_LOC), p), + addmod(mload(X1_EVAL_LOC), sub(p, mload(X3_EVAL_LOC)), p), + p + ) + ) + leftovers := + addmod( + sub(p, mulmod(mload(X1_EVAL_LOC), addmod(mload(Y3_EVAL_LOC), mload(Y1_EVAL_LOC), p), p)), + mulmod(mload(Y1_EVAL_LOC), addmod(mload(X1_EVAL_LOC), sub(p, mload(X3_EVAL_LOC)), p), p), + p + ) + let y_identity := + mulmod( + addmod(addmod(endo_term, sign_term, p), leftovers, p), + mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p), + p + ) + + // ELLIPTIC_IDENTITY = (x_identity + y_identity) * Q_ELLIPTIC_EVAL + mstore(ELLIPTIC_IDENTITY, mulmod(addmod(x_identity, y_identity, p), mload(QELLIPTIC_EVAL_LOC), p)) + + // update alpha + // The paper says to use ALPHA^2, we use ALPHA^4 this is a small oversight in the prover protocol + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_QUAD_LOC), p)) + } + + /** + * COMPUTE AUXILIARY WIDGET EVALUATION + */ + { + { + /** + * Non native field arithmetic gate 2 + * _ _ + * / _ _ _ 14 \ + * q_2 . q_4 | (w_1 . w_2) + (w_1 . w_2) + (w_1 . w_4 + w_2 . w_3 - w_3) . 2 - w_3 - w_4 | + * \_ _/ + * + * limb_subproduct = w_1 . w_2_omega + w_1_omega . w_2 + * non_native_field_gate_2 = w_1 * w_4 + w_4 * w_3 - w_3_omega + * non_native_field_gate_2 = non_native_field_gate_2 * limb_size + * non_native_field_gate_2 -= w_4_omega + * non_native_field_gate_2 += limb_subproduct + * non_native_field_gate_2 *= q_4 + * limb_subproduct *= limb_size + * limb_subproduct += w_1_omega * w_2_omega + * non_native_field_gate_1 = (limb_subproduct + w_3 + w_4) * q_3 + * non_native_field_gate_3 = (limb_subproduct + w_4 - (w_3_omega + w_4_omega)) * q_m + * non_native_field_identity = (non_native_field_gate_1 + non_native_field_gate_2 + non_native_field_gate_3) * q_2 + */ + + let limb_subproduct := + addmod( + mulmod(mload(W1_EVAL_LOC), mload(W2_OMEGA_EVAL_LOC), p), + mulmod(mload(W1_OMEGA_EVAL_LOC), mload(W2_EVAL_LOC), p), + p + ) + + let non_native_field_gate_2 := + addmod( + addmod( + mulmod(mload(W1_EVAL_LOC), mload(W4_EVAL_LOC), p), + mulmod(mload(W2_EVAL_LOC), mload(W3_EVAL_LOC), p), + p + ), + sub(p, mload(W3_OMEGA_EVAL_LOC)), + p + ) + non_native_field_gate_2 := mulmod(non_native_field_gate_2, LIMB_SIZE, p) + non_native_field_gate_2 := addmod(non_native_field_gate_2, sub(p, mload(W4_OMEGA_EVAL_LOC)), p) + non_native_field_gate_2 := addmod(non_native_field_gate_2, limb_subproduct, p) + non_native_field_gate_2 := mulmod(non_native_field_gate_2, mload(Q4_EVAL_LOC), p) + limb_subproduct := mulmod(limb_subproduct, LIMB_SIZE, p) + limb_subproduct := + addmod(limb_subproduct, mulmod(mload(W1_OMEGA_EVAL_LOC), mload(W2_OMEGA_EVAL_LOC), p), p) + let non_native_field_gate_1 := + mulmod( + addmod(limb_subproduct, sub(p, addmod(mload(W3_EVAL_LOC), mload(W4_EVAL_LOC), p)), p), + mload(Q3_EVAL_LOC), + p + ) + let non_native_field_gate_3 := + mulmod( + addmod( + addmod(limb_subproduct, mload(W4_EVAL_LOC), p), + sub(p, addmod(mload(W3_OMEGA_EVAL_LOC), mload(W4_OMEGA_EVAL_LOC), p)), + p + ), + mload(QM_EVAL_LOC), + p + ) + let non_native_field_identity := + mulmod( + addmod(addmod(non_native_field_gate_1, non_native_field_gate_2, p), non_native_field_gate_3, p), + mload(Q2_EVAL_LOC), + p + ) + + mstore(AUX_NON_NATIVE_FIELD_EVALUATION, non_native_field_identity) + } + + { + /** + * limb_accumulator_1 = w_2_omega; + * limb_accumulator_1 *= SUBLIMB_SHIFT; + * limb_accumulator_1 += w_1_omega; + * limb_accumulator_1 *= SUBLIMB_SHIFT; + * limb_accumulator_1 += w_3; + * limb_accumulator_1 *= SUBLIMB_SHIFT; + * limb_accumulator_1 += w_2; + * limb_accumulator_1 *= SUBLIMB_SHIFT; + * limb_accumulator_1 += w_1; + * limb_accumulator_1 -= w_4; + * limb_accumulator_1 *= q_4; + */ + let limb_accumulator_1 := mulmod(mload(W2_OMEGA_EVAL_LOC), SUBLIMB_SHIFT, p) + limb_accumulator_1 := addmod(limb_accumulator_1, mload(W1_OMEGA_EVAL_LOC), p) + limb_accumulator_1 := mulmod(limb_accumulator_1, SUBLIMB_SHIFT, p) + limb_accumulator_1 := addmod(limb_accumulator_1, mload(W3_EVAL_LOC), p) + limb_accumulator_1 := mulmod(limb_accumulator_1, SUBLIMB_SHIFT, p) + limb_accumulator_1 := addmod(limb_accumulator_1, mload(W2_EVAL_LOC), p) + limb_accumulator_1 := mulmod(limb_accumulator_1, SUBLIMB_SHIFT, p) + limb_accumulator_1 := addmod(limb_accumulator_1, mload(W1_EVAL_LOC), p) + limb_accumulator_1 := addmod(limb_accumulator_1, sub(p, mload(W4_EVAL_LOC)), p) + limb_accumulator_1 := mulmod(limb_accumulator_1, mload(Q4_EVAL_LOC), p) + + /** + * limb_accumulator_2 = w_3_omega; + * limb_accumulator_2 *= SUBLIMB_SHIFT; + * limb_accumulator_2 += w_2_omega; + * limb_accumulator_2 *= SUBLIMB_SHIFT; + * limb_accumulator_2 += w_1_omega; + * limb_accumulator_2 *= SUBLIMB_SHIFT; + * limb_accumulator_2 += w_4; + * limb_accumulator_2 *= SUBLIMB_SHIFT; + * limb_accumulator_2 += w_3; + * limb_accumulator_2 -= w_4_omega; + * limb_accumulator_2 *= q_m; + */ + let limb_accumulator_2 := mulmod(mload(W3_OMEGA_EVAL_LOC), SUBLIMB_SHIFT, p) + limb_accumulator_2 := addmod(limb_accumulator_2, mload(W2_OMEGA_EVAL_LOC), p) + limb_accumulator_2 := mulmod(limb_accumulator_2, SUBLIMB_SHIFT, p) + limb_accumulator_2 := addmod(limb_accumulator_2, mload(W1_OMEGA_EVAL_LOC), p) + limb_accumulator_2 := mulmod(limb_accumulator_2, SUBLIMB_SHIFT, p) + limb_accumulator_2 := addmod(limb_accumulator_2, mload(W4_EVAL_LOC), p) + limb_accumulator_2 := mulmod(limb_accumulator_2, SUBLIMB_SHIFT, p) + limb_accumulator_2 := addmod(limb_accumulator_2, mload(W3_EVAL_LOC), p) + limb_accumulator_2 := addmod(limb_accumulator_2, sub(p, mload(W4_OMEGA_EVAL_LOC)), p) + limb_accumulator_2 := mulmod(limb_accumulator_2, mload(QM_EVAL_LOC), p) + + mstore( + AUX_LIMB_ACCUMULATOR_EVALUATION, + mulmod(addmod(limb_accumulator_1, limb_accumulator_2, p), mload(Q3_EVAL_LOC), p) + ) + } + + { + /** + * memory_record_check = w_3; + * memory_record_check *= eta; + * memory_record_check += w_2; + * memory_record_check *= eta; + * memory_record_check += w_1; + * memory_record_check *= eta; + * memory_record_check += q_c; + * + * partial_record_check = memory_record_check; + * + * memory_record_check -= w_4; + */ + + let memory_record_check := mulmod(mload(W3_EVAL_LOC), mload(C_ETA_LOC), p) + memory_record_check := addmod(memory_record_check, mload(W2_EVAL_LOC), p) + memory_record_check := mulmod(memory_record_check, mload(C_ETA_LOC), p) + memory_record_check := addmod(memory_record_check, mload(W1_EVAL_LOC), p) + memory_record_check := mulmod(memory_record_check, mload(C_ETA_LOC), p) + memory_record_check := addmod(memory_record_check, mload(QC_EVAL_LOC), p) + + let partial_record_check := memory_record_check + memory_record_check := addmod(memory_record_check, sub(p, mload(W4_EVAL_LOC)), p) + + mstore(AUX_MEMORY_EVALUATION, memory_record_check) + + // index_delta = w_1_omega - w_1 + let index_delta := addmod(mload(W1_OMEGA_EVAL_LOC), sub(p, mload(W1_EVAL_LOC)), p) + // record_delta = w_4_omega - w_4 + let record_delta := addmod(mload(W4_OMEGA_EVAL_LOC), sub(p, mload(W4_EVAL_LOC)), p) + // index_is_monotonically_increasing = index_delta * (index_delta - 1) + let index_is_monotonically_increasing := mulmod(index_delta, addmod(index_delta, sub(p, 1), p), p) + + // adjacent_values_match_if_adjacent_indices_match = record_delta * (1 - index_delta) + let adjacent_values_match_if_adjacent_indices_match := + mulmod(record_delta, addmod(1, sub(p, index_delta), p), p) + + // AUX_ROM_CONSISTENCY_EVALUATION = ((adjacent_values_match_if_adjacent_indices_match * alpha) + index_is_monotonically_increasing) * alpha + partial_record_check + mstore( + AUX_ROM_CONSISTENCY_EVALUATION, + addmod( + mulmod( + addmod( + mulmod(adjacent_values_match_if_adjacent_indices_match, mload(C_ALPHA_LOC), p), + index_is_monotonically_increasing, + p + ), + mload(C_ALPHA_LOC), + p + ), + memory_record_check, + p + ) + ) + + { + /** + * next_gate_access_type = w_3_omega; + * next_gate_access_type *= eta; + * next_gate_access_type += w_2_omega; + * next_gate_access_type *= eta; + * next_gate_access_type += w_1_omega; + * next_gate_access_type *= eta; + * next_gate_access_type = w_4_omega - next_gate_access_type; + */ + let next_gate_access_type := mulmod(mload(W3_OMEGA_EVAL_LOC), mload(C_ETA_LOC), p) + next_gate_access_type := addmod(next_gate_access_type, mload(W2_OMEGA_EVAL_LOC), p) + next_gate_access_type := mulmod(next_gate_access_type, mload(C_ETA_LOC), p) + next_gate_access_type := addmod(next_gate_access_type, mload(W1_OMEGA_EVAL_LOC), p) + next_gate_access_type := mulmod(next_gate_access_type, mload(C_ETA_LOC), p) + next_gate_access_type := addmod(mload(W4_OMEGA_EVAL_LOC), sub(p, next_gate_access_type), p) + + // value_delta = w_3_omega - w_3 + let value_delta := addmod(mload(W3_OMEGA_EVAL_LOC), sub(p, mload(W3_EVAL_LOC)), p) + // adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation = (1 - index_delta) * value_delta * (1 - next_gate_access_type); + + let adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation := + mulmod( + addmod(1, sub(p, index_delta), p), + mulmod(value_delta, addmod(1, sub(p, next_gate_access_type), p), p), + p + ) + + // AUX_RAM_CONSISTENCY_EVALUATION + + /** + * access_type = w_4 - partial_record_check + * access_check = access_type^2 - access_type + * next_gate_access_type_is_boolean = next_gate_access_type^2 - next_gate_access_type + * RAM_consistency_check_identity = adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation; + * RAM_consistency_check_identity *= alpha; + * RAM_consistency_check_identity += index_is_monotonically_increasing; + * RAM_consistency_check_identity *= alpha; + * RAM_consistency_check_identity += next_gate_access_type_is_boolean; + * RAM_consistency_check_identity *= alpha; + * RAM_consistency_check_identity += access_check; + */ + + let access_type := addmod(mload(W4_EVAL_LOC), sub(p, partial_record_check), p) + let access_check := mulmod(access_type, addmod(access_type, sub(p, 1), p), p) + let next_gate_access_type_is_boolean := + mulmod(next_gate_access_type, addmod(next_gate_access_type, sub(p, 1), p), p) + let RAM_cci := + mulmod( + adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation, + mload(C_ALPHA_LOC), + p + ) + RAM_cci := addmod(RAM_cci, index_is_monotonically_increasing, p) + RAM_cci := mulmod(RAM_cci, mload(C_ALPHA_LOC), p) + RAM_cci := addmod(RAM_cci, next_gate_access_type_is_boolean, p) + RAM_cci := mulmod(RAM_cci, mload(C_ALPHA_LOC), p) + RAM_cci := addmod(RAM_cci, access_check, p) + + mstore(AUX_RAM_CONSISTENCY_EVALUATION, RAM_cci) + } + + { + // timestamp_delta = w_2_omega - w_2 + let timestamp_delta := addmod(mload(W2_OMEGA_EVAL_LOC), sub(p, mload(W2_EVAL_LOC)), p) + + // RAM_timestamp_check_identity = (1 - index_delta) * timestamp_delta - w_3 + let RAM_timestamp_check_identity := + addmod( + mulmod(timestamp_delta, addmod(1, sub(p, index_delta), p), p), sub(p, mload(W3_EVAL_LOC)), p + ) + + /** + * memory_identity = ROM_consistency_check_identity * q_2; + * memory_identity += RAM_timestamp_check_identity * q_4; + * memory_identity += memory_record_check * q_m; + * memory_identity *= q_1; + * memory_identity += (RAM_consistency_check_identity * q_arith); + * + * auxiliary_identity = memory_identity + non_native_field_identity + limb_accumulator_identity; + * auxiliary_identity *= q_aux; + * auxiliary_identity *= alpha_base; + */ + let memory_identity := mulmod(mload(AUX_ROM_CONSISTENCY_EVALUATION), mload(Q2_EVAL_LOC), p) + memory_identity := + addmod(memory_identity, mulmod(RAM_timestamp_check_identity, mload(Q4_EVAL_LOC), p), p) + memory_identity := + addmod(memory_identity, mulmod(mload(AUX_MEMORY_EVALUATION), mload(QM_EVAL_LOC), p), p) + memory_identity := mulmod(memory_identity, mload(Q1_EVAL_LOC), p) + memory_identity := + addmod( + memory_identity, mulmod(mload(AUX_RAM_CONSISTENCY_EVALUATION), mload(QARITH_EVAL_LOC), p), p + ) + + let auxiliary_identity := addmod(memory_identity, mload(AUX_NON_NATIVE_FIELD_EVALUATION), p) + auxiliary_identity := addmod(auxiliary_identity, mload(AUX_LIMB_ACCUMULATOR_EVALUATION), p) + auxiliary_identity := mulmod(auxiliary_identity, mload(QAUX_EVAL_LOC), p) + auxiliary_identity := mulmod(auxiliary_identity, mload(C_ALPHA_BASE_LOC), p) + + mstore(AUX_IDENTITY, auxiliary_identity) + + // update alpha + mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_CUBE_LOC), p)) + } + } + } + + { + /** + * quotient = ARITHMETIC_IDENTITY + * quotient += PERMUTATION_IDENTITY + * quotient += PLOOKUP_IDENTITY + * quotient += SORT_IDENTITY + * quotient += ELLIPTIC_IDENTITY + * quotient += AUX_IDENTITY + * quotient *= ZERO_POLY_INVERSE + */ + mstore( + QUOTIENT_EVAL_LOC, + mulmod( + addmod( + addmod( + addmod( + addmod( + addmod(mload(PERMUTATION_IDENTITY), mload(PLOOKUP_IDENTITY), p), + mload(ARITHMETIC_IDENTITY), + p + ), + mload(SORT_IDENTITY), + p + ), + mload(ELLIPTIC_IDENTITY), + p + ), + mload(AUX_IDENTITY), + p + ), + mload(ZERO_POLY_INVERSE_LOC), + p + ) + ) + } + + /** + * GENERATE NU AND SEPARATOR CHALLENGES + */ + { + let current_challenge := mload(C_CURRENT_LOC) + // get a calldata pointer that points to the start of the data we want to copy + let calldata_ptr := add(calldataload(0x04), 0x24) + + calldata_ptr := add(calldata_ptr, NU_CALLDATA_SKIP_LENGTH) + + mstore(NU_CHALLENGE_INPUT_LOC_A, current_challenge) + mstore(NU_CHALLENGE_INPUT_LOC_B, mload(QUOTIENT_EVAL_LOC)) + calldatacopy(NU_CHALLENGE_INPUT_LOC_C, calldata_ptr, NU_INPUT_LENGTH) + + // hash length = (0x20 + num field elements), we include the previous challenge in the hash + let challenge := keccak256(NU_CHALLENGE_INPUT_LOC_A, add(NU_INPUT_LENGTH, 0x40)) + + mstore(C_V0_LOC, mod(challenge, p)) + // We need THIRTY-ONE independent nu challenges! + mstore(0x00, challenge) + mstore8(0x20, 0x01) + mstore(C_V1_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x02) + mstore(C_V2_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x03) + mstore(C_V3_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x04) + mstore(C_V4_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x05) + mstore(C_V5_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x06) + mstore(C_V6_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x07) + mstore(C_V7_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x08) + mstore(C_V8_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x09) + mstore(C_V9_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x0a) + mstore(C_V10_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x0b) + mstore(C_V11_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x0c) + mstore(C_V12_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x0d) + mstore(C_V13_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x0e) + mstore(C_V14_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x0f) + mstore(C_V15_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x10) + mstore(C_V16_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x11) + mstore(C_V17_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x12) + mstore(C_V18_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x13) + mstore(C_V19_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x14) + mstore(C_V20_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x15) + mstore(C_V21_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x16) + mstore(C_V22_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x17) + mstore(C_V23_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x18) + mstore(C_V24_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x19) + mstore(C_V25_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x1a) + mstore(C_V26_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x1b) + mstore(C_V27_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x1c) + mstore(C_V28_LOC, mod(keccak256(0x00, 0x21), p)) + mstore8(0x20, 0x1d) + mstore(C_V29_LOC, mod(keccak256(0x00, 0x21), p)) + + // @follow-up - Why are both v29 and v30 using appending 0x1d to the prior challenge and hashing, should it not change? + mstore8(0x20, 0x1d) + challenge := keccak256(0x00, 0x21) + mstore(C_V30_LOC, mod(challenge, p)) + + // separator + mstore(0x00, challenge) + mstore(0x20, mload(PI_Z_Y_LOC)) + mstore(0x40, mload(PI_Z_X_LOC)) + mstore(0x60, mload(PI_Z_OMEGA_Y_LOC)) + mstore(0x80, mload(PI_Z_OMEGA_X_LOC)) + + mstore(C_U_LOC, mod(keccak256(0x00, 0xa0), p)) + } + + let success := 0 + // VALIDATE T1 + { + let x := mload(T1_X_LOC) + let y := mload(T1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)) + mstore(ACCUMULATOR_X_LOC, x) + mstore(add(ACCUMULATOR_X_LOC, 0x20), y) + } + // VALIDATE T2 + { + let x := mload(T2_X_LOC) // 0x1400 + let y := mload(T2_Y_LOC) // 0x1420 + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(ZETA_POW_N_LOC)) + // accumulator_2 = [T2].zeta^n + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = [T1] + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE T3 + { + let x := mload(T3_X_LOC) + let y := mload(T3_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(mload(ZETA_POW_N_LOC), mload(ZETA_POW_N_LOC), p)) + // accumulator_2 = [T3].zeta^{2n} + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE T4 + { + let x := mload(T4_X_LOC) + let y := mload(T4_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(mulmod(mload(ZETA_POW_N_LOC), mload(ZETA_POW_N_LOC), p), mload(ZETA_POW_N_LOC), p)) + // accumulator_2 = [T4].zeta^{3n} + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE W1 + { + let x := mload(W1_X_LOC) + let y := mload(W1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V0_LOC), p)) + // accumulator_2 = v0.(u + 1).[W1] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE W2 + { + let x := mload(W2_X_LOC) + let y := mload(W2_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V1_LOC), p)) + // accumulator_2 = v1.(u + 1).[W2] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE W3 + { + let x := mload(W3_X_LOC) + let y := mload(W3_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V2_LOC), p)) + // accumulator_2 = v2.(u + 1).[W3] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE W4 + { + let x := mload(W4_X_LOC) + let y := mload(W4_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V3_LOC), p)) + // accumulator_2 = v3.(u + 1).[W4] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE S + { + let x := mload(S_X_LOC) + let y := mload(S_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V4_LOC), p)) + // accumulator_2 = v4.(u + 1).[S] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE Z + { + let x := mload(Z_X_LOC) + let y := mload(Z_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V5_LOC), p)) + // accumulator_2 = v5.(u + 1).[Z] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE Z_LOOKUP + { + let x := mload(Z_LOOKUP_X_LOC) + let y := mload(Z_LOOKUP_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V6_LOC), p)) + // accumulator_2 = v6.(u + 1).[Z_LOOKUP] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE Q1 + { + let x := mload(Q1_X_LOC) + let y := mload(Q1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V7_LOC)) + // accumulator_2 = v7.[Q1] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE Q2 + { + let x := mload(Q2_X_LOC) + let y := mload(Q2_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V8_LOC)) + // accumulator_2 = v8.[Q2] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE Q3 + { + let x := mload(Q3_X_LOC) + let y := mload(Q3_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V9_LOC)) + // accumulator_2 = v9.[Q3] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE Q4 + { + let x := mload(Q4_X_LOC) + let y := mload(Q4_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V10_LOC)) + // accumulator_2 = v10.[Q4] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE QM + { + let x := mload(QM_X_LOC) + let y := mload(QM_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V11_LOC)) + // accumulator_2 = v11.[Q;] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE QC + { + let x := mload(QC_X_LOC) + let y := mload(QC_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V12_LOC)) + // accumulator_2 = v12.[QC] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE QARITH + { + let x := mload(QARITH_X_LOC) + let y := mload(QARITH_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V13_LOC)) + // accumulator_2 = v13.[QARITH] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE QSORT + { + let x := mload(QSORT_X_LOC) + let y := mload(QSORT_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V14_LOC)) + // accumulator_2 = v14.[QSORT] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE QELLIPTIC + { + let x := mload(QELLIPTIC_X_LOC) + let y := mload(QELLIPTIC_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V15_LOC)) + // accumulator_2 = v15.[QELLIPTIC] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE QAUX + { + let x := mload(QAUX_X_LOC) + let y := mload(QAUX_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V16_LOC)) + // accumulator_2 = v15.[Q_AUX] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE SIGMA1 + { + let x := mload(SIGMA1_X_LOC) + let y := mload(SIGMA1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V17_LOC)) + // accumulator_2 = v17.[sigma1] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE SIGMA2 + { + let x := mload(SIGMA2_X_LOC) + let y := mload(SIGMA2_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V18_LOC)) + // accumulator_2 = v18.[sigma2] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE SIGMA3 + { + let x := mload(SIGMA3_X_LOC) + let y := mload(SIGMA3_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V19_LOC)) + // accumulator_2 = v19.[sigma3] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE SIGMA4 + { + let x := mload(SIGMA4_X_LOC) + let y := mload(SIGMA4_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V20_LOC)) + // accumulator_2 = v20.[sigma4] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE TABLE1 + { + let x := mload(TABLE1_X_LOC) + let y := mload(TABLE1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V21_LOC), p)) + // accumulator_2 = u.[table1] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE TABLE2 + { + let x := mload(TABLE2_X_LOC) + let y := mload(TABLE2_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V22_LOC), p)) + // accumulator_2 = u.[table2] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE TABLE3 + { + let x := mload(TABLE3_X_LOC) + let y := mload(TABLE3_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V23_LOC), p)) + // accumulator_2 = u.[table3] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE TABLE4 + { + let x := mload(TABLE4_X_LOC) + let y := mload(TABLE4_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V24_LOC), p)) + // accumulator_2 = u.[table4] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE TABLE_TYPE + { + let x := mload(TABLE_TYPE_X_LOC) + let y := mload(TABLE_TYPE_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V25_LOC)) + // accumulator_2 = v25.[TableType] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE ID1 + { + let x := mload(ID1_X_LOC) + let y := mload(ID1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V26_LOC)) + // accumulator_2 = v26.[ID1] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE ID2 + { + let x := mload(ID2_X_LOC) + let y := mload(ID2_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V27_LOC)) + // accumulator_2 = v27.[ID2] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE ID3 + { + let x := mload(ID3_X_LOC) + let y := mload(ID3_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V28_LOC)) + // accumulator_2 = v28.[ID3] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE ID4 + { + let x := mload(ID4_X_LOC) + let y := mload(ID4_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mload(C_V29_LOC)) + // accumulator_2 = v29.[ID4] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + /** + * COMPUTE BATCH EVALUATION SCALAR MULTIPLIER + */ + { + /** + * batch_evaluation = v0 * (w_1_omega * u + w_1_eval) + * batch_evaluation += v1 * (w_2_omega * u + w_2_eval) + * batch_evaluation += v2 * (w_3_omega * u + w_3_eval) + * batch_evaluation += v3 * (w_4_omega * u + w_4_eval) + * batch_evaluation += v4 * (s_omega_eval * u + s_eval) + * batch_evaluation += v5 * (z_omega_eval * u + z_eval) + * batch_evaluation += v6 * (z_lookup_omega_eval * u + z_lookup_eval) + */ + let batch_evaluation := + mulmod( + mload(C_V0_LOC), + addmod(mulmod(mload(W1_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(W1_EVAL_LOC), p), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V1_LOC), + addmod(mulmod(mload(W2_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(W2_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V2_LOC), + addmod(mulmod(mload(W3_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(W3_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V3_LOC), + addmod(mulmod(mload(W4_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(W4_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V4_LOC), + addmod(mulmod(mload(S_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(S_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V5_LOC), + addmod(mulmod(mload(Z_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(Z_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V6_LOC), + addmod(mulmod(mload(Z_LOOKUP_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(Z_LOOKUP_EVAL_LOC), p), + p + ), + p + ) + + /** + * batch_evaluation += v7 * Q1_EVAL + * batch_evaluation += v8 * Q2_EVAL + * batch_evaluation += v9 * Q3_EVAL + * batch_evaluation += v10 * Q4_EVAL + * batch_evaluation += v11 * QM_EVAL + * batch_evaluation += v12 * QC_EVAL + * batch_evaluation += v13 * QARITH_EVAL + * batch_evaluation += v14 * QSORT_EVAL_LOC + * batch_evaluation += v15 * QELLIPTIC_EVAL_LOC + * batch_evaluation += v16 * QAUX_EVAL_LOC + * batch_evaluation += v17 * SIGMA1_EVAL_LOC + * batch_evaluation += v18 * SIGMA2_EVAL_LOC + * batch_evaluation += v19 * SIGMA3_EVAL_LOC + * batch_evaluation += v20 * SIGMA4_EVAL_LOC + */ + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V7_LOC), mload(Q1_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V8_LOC), mload(Q2_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V9_LOC), mload(Q3_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V10_LOC), mload(Q4_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V11_LOC), mload(QM_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V12_LOC), mload(QC_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V13_LOC), mload(QARITH_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V14_LOC), mload(QSORT_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V15_LOC), mload(QELLIPTIC_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V16_LOC), mload(QAUX_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V17_LOC), mload(SIGMA1_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V18_LOC), mload(SIGMA2_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V19_LOC), mload(SIGMA3_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V20_LOC), mload(SIGMA4_EVAL_LOC), p), p) + + /** + * batch_evaluation += v21 * (table1(zw) * u + table1(z)) + * batch_evaluation += v22 * (table2(zw) * u + table2(z)) + * batch_evaluation += v23 * (table3(zw) * u + table3(z)) + * batch_evaluation += v24 * (table4(zw) * u + table4(z)) + * batch_evaluation += v25 * table_type_eval + * batch_evaluation += v26 * id1_eval + * batch_evaluation += v27 * id2_eval + * batch_evaluation += v28 * id3_eval + * batch_evaluation += v29 * id4_eval + * batch_evaluation += quotient_eval + */ + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V21_LOC), + addmod(mulmod(mload(TABLE1_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(TABLE1_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V22_LOC), + addmod(mulmod(mload(TABLE2_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(TABLE2_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V23_LOC), + addmod(mulmod(mload(TABLE3_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(TABLE3_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := + addmod( + batch_evaluation, + mulmod( + mload(C_V24_LOC), + addmod(mulmod(mload(TABLE4_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(TABLE4_EVAL_LOC), p), + p + ), + p + ) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V25_LOC), mload(TABLE_TYPE_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V26_LOC), mload(ID1_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V27_LOC), mload(ID2_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V28_LOC), mload(ID3_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V29_LOC), mload(ID4_EVAL_LOC), p), p) + batch_evaluation := addmod(batch_evaluation, mload(QUOTIENT_EVAL_LOC), p) + + mstore(0x00, 0x01) // [1].x + mstore(0x20, 0x02) // [1].y + mstore(0x40, sub(p, batch_evaluation)) + // accumulator_2 = -[1].(batch_evaluation) + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + mstore(OPENING_COMMITMENT_SUCCESS_FLAG, success) + } + + /** + * PERFORM PAIRING PREAMBLE + */ + { + let u := mload(C_U_LOC) + let zeta := mload(C_ZETA_LOC) + // VALIDATE PI_Z + { + let x := mload(PI_Z_X_LOC) + let y := mload(PI_Z_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)) + mstore(0x00, x) + mstore(0x20, y) + } + // compute zeta.[PI_Z] and add into accumulator + mstore(0x40, zeta) + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40)) + + // VALIDATE PI_Z_OMEGA + { + let x := mload(PI_Z_OMEGA_X_LOC) + let y := mload(PI_Z_OMEGA_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + mstore(0x40, mulmod(mulmod(u, zeta, p), mload(OMEGA_LOC), p)) + // accumulator_2 = u.zeta.omega.[PI_Z_OMEGA] + success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)) + // PAIRING_RHS = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, PAIRING_RHS_X_LOC, 0x40)) + + mstore(0x00, mload(PI_Z_X_LOC)) + mstore(0x20, mload(PI_Z_Y_LOC)) + mstore(0x40, mload(PI_Z_OMEGA_X_LOC)) + mstore(0x60, mload(PI_Z_OMEGA_Y_LOC)) + mstore(0x80, u) + success := and(success, staticcall(gas(), 7, 0x40, 0x60, 0x40, 0x40)) + // PAIRING_LHS = [PI_Z] + [PI_Z_OMEGA] * u + success := and(success, staticcall(gas(), 6, 0x00, 0x80, PAIRING_LHS_X_LOC, 0x40)) + // negate lhs y-coordinate + mstore(PAIRING_LHS_Y_LOC, sub(q, mload(PAIRING_LHS_Y_LOC))) + + if mload(CONTAINS_RECURSIVE_PROOF_LOC) { + // VALIDATE RECURSIVE P1 + { + let x := mload(RECURSIVE_P1_X_LOC) + let y := mload(RECURSIVE_P1_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + + // compute u.u.[recursive_p1] and write into 0x60 + mstore(0x40, mulmod(u, u, p)) + success := and(success, staticcall(gas(), 7, 0x00, 0x60, 0x60, 0x40)) + // VALIDATE RECURSIVE P2 + { + let x := mload(RECURSIVE_P2_X_LOC) + let y := mload(RECURSIVE_P2_Y_LOC) + let xx := mulmod(x, x, q) + // validate on curve + success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) + mstore(0x00, x) + mstore(0x20, y) + } + // compute u.u.[recursive_p2] and write into 0x00 + // 0x40 still contains u*u + success := and(success, staticcall(gas(), 7, 0x00, 0x60, 0x00, 0x40)) + + // compute u.u.[recursiveP1] + rhs and write into rhs + mstore(0xa0, mload(PAIRING_RHS_X_LOC)) + mstore(0xc0, mload(PAIRING_RHS_Y_LOC)) + success := and(success, staticcall(gas(), 6, 0x60, 0x80, PAIRING_RHS_X_LOC, 0x40)) + + // compute u.u.[recursiveP2] + lhs and write into lhs + mstore(0x40, mload(PAIRING_LHS_X_LOC)) + mstore(0x60, mload(PAIRING_LHS_Y_LOC)) + success := and(success, staticcall(gas(), 6, 0x00, 0x80, PAIRING_LHS_X_LOC, 0x40)) + } + + if iszero(success) { + mstore(0x0, EC_SCALAR_MUL_FAILURE_SELECTOR) + revert(0x00, 0x04) + } + mstore(PAIRING_PREAMBLE_SUCCESS_FLAG, success) + } + + /** + * PERFORM PAIRING + */ + { + // rhs paired with [1]_2 + // lhs paired with [x]_2 + + mstore(0x00, mload(PAIRING_RHS_X_LOC)) + mstore(0x20, mload(PAIRING_RHS_Y_LOC)) + mstore(0x40, 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2) // this is [1]_2 + mstore(0x60, 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed) + mstore(0x80, 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b) + mstore(0xa0, 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa) + + mstore(0xc0, mload(PAIRING_LHS_X_LOC)) + mstore(0xe0, mload(PAIRING_LHS_Y_LOC)) + mstore(0x100, mload(G2X_X0_LOC)) + mstore(0x120, mload(G2X_X1_LOC)) + mstore(0x140, mload(G2X_Y0_LOC)) + mstore(0x160, mload(G2X_Y1_LOC)) + + success := staticcall(gas(), 8, 0x00, 0x180, 0x00, 0x20) + mstore(PAIRING_SUCCESS_FLAG, success) + mstore(RESULT_FLAG, mload(0x00)) + } + if iszero( + and( + and(and(mload(PAIRING_SUCCESS_FLAG), mload(RESULT_FLAG)), mload(PAIRING_PREAMBLE_SUCCESS_FLAG)), + mload(OPENING_COMMITMENT_SUCCESS_FLAG) + ) + ) { + mstore(0x0, PROOF_FAILURE_SELECTOR) + revert(0x00, 0x04) + } + { + mstore(0x00, 0x01) + return(0x00, 0x20) // Proof succeeded! + } + } + } +} + +contract UltraVerifier is BaseUltraVerifier { + function getVerificationKeyHash() public pure override(BaseUltraVerifier) returns (bytes32) { + return UltraVerificationKey.verificationKeyHash(); + } + + function loadVerificationKey(uint256 vk, uint256 _omegaInverseLoc) internal pure virtual override(BaseUltraVerifier) { + UltraVerificationKey.loadVerificationKey(vk, _omegaInverseLoc); + } +} diff --git a/hardhat.config.js b/hardhat.config.js new file mode 100644 index 0000000000000000000000000000000000000000..c5bcb2dfcc396c04ae8814abdae36f314b9924ec --- /dev/null +++ b/hardhat.config.js @@ -0,0 +1,22 @@ +require("@nomicfoundation/hardhat-toolbox"); + +/** @type import('hardhat/config').HardhatUserConfig */ +module.exports = { + solidity: { + version: '0.8.10', + settings: { + evmVersion: 'london', + optimizer: { enabled: true, runs: 5000 }, + }, + }, + networks: { + hardhat: { + blockGasLimit: 10000000, + gasPrice: 10, + hardfork: 'istanbul', + }, + }, + mocha: { + timeout: 200000 + } +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000000000000000000000000000000000000..4f706341c65eaff9f90994e9d526726ffd9d7fea --- /dev/null +++ b/package.json @@ -0,0 +1,61 @@ +{ + "name": "noir-starter", + "version": "1.0.0", + "description": "A template repository to get started with writing zero knowledge programs with Noir.", + "scripts": { + "dev": "next", + "build": "next build", + "start": "next start", + "bbjs": "node ./scripts/bbjs.mjs", + "test": "vitest" + }, + "dependencies": { + "@aztec/bb.js": "0.7.2", + "@noir-lang/acvm_js": "0.26.1", + "@typechain/ethers-v5": "^10.1.0", + "axios": "^1.3.4", + "dotenv": "^16.0.3", + "ethers": "^5.7.2", + "fflate": "^0.8.0", + "next": "latest", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-toastify": "^9.1.1", + "ts-node": "^10.9.1", + "typechain": "^8.1.0", + "@chainlink/contracts": "^0.6.1", + "@openzeppelin/contracts": "^4.7.1", + "@uniswap/v3-core": "^1.0.1", + "bigint-buffer": "^1.1.5", + "circomlibjs": "^0.1.7", + "keccak256": "^1.0.6", + "sequelize": "^6.32.0", + "sqlite3": "^5.1.6" + }, + "devDependencies": { + "@types/node": "^18.15.5", + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.9", + "vitest": "^0.31.4", + "@ethersproject/abi": "^5.6.4", + "@ethersproject/providers": "^5.6.8", + "@nomicfoundation/hardhat-chai-matchers": "^1.0.2", + "@nomicfoundation/hardhat-network-helpers": "^1.0.3", + "@nomicfoundation/hardhat-toolbox": "^1.0.2", + "@nomiclabs/hardhat-ethers": "^2.2.2", + "@nomiclabs/hardhat-etherscan": "^3.1.0", + "@openzeppelin/hardhat-upgrades": "^1.19.1", + "@typechain/hardhat": "^6.1.2", + "@types/memdown": "^3.0.0", + "@types/mocha": "^9.1.1", + "chai": "^4.3.6", + "ethers": "^5.6.9", + "hardhat": "^2.10.1", + "hardhat-gas-reporter": "^1.0.8", + "snarkjs": "^0.4.24", + "solidity-coverage": "^0.7.21", + "ts-node": "^10.9.1", + "typechain": "^8.1.0", + "typescript": "^4.7.4" + } +} diff --git a/scripts/bbjs.mjs b/scripts/bbjs.mjs new file mode 100644 index 0000000000000000000000000000000000000000..fa85f58262c093d0c0e0d56f345fe54d8be85b99 --- /dev/null +++ b/scripts/bbjs.mjs @@ -0,0 +1,2 @@ +import { execSync } from 'child_process' +execSync(`cd ./node_modules/@aztec/bb.js/dest/node/ && sed -i 's/{ RawBuffer }/*/g' index.d.ts && sed -i 's/{ RawBuffer }/*/g' index.js`) \ No newline at end of file diff --git a/test/index.test.ts b/test/index.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..985f067fd7c7fd6f97acd7309823e85ca2c80794 --- /dev/null +++ b/test/index.test.ts @@ -0,0 +1,485 @@ +import pkg from 'hardhat'; +import { getSigners, getContractFactory } from '@nomiclabs/hardhat-ethers/src/internal/helpers' +import { expect } from 'chai'; +import { BigNumber } from 'ethers'; +import { Noir } from '../utils/noir'; +import { execSync } from 'child_process'; +import { MerkleTree } from '../utils/MerkleTree' +import { + generateUTXO, + generateTestTransaction, + generateTestPublish, + generateDataToml, + randomBytesFr, + generateTreeProof, + treeConfig, + dumpToml, + dumpTomlRecursive +} from '../utils/test_helpers'; +import fs from "fs" +// @ts-ignore +import { Fr } from '@aztec/bb.js'; + +import mainCircuit from '../circuits/main/target/main.json'; +import recursiveCircuit from '../circuits/recursion/target/recursion.json'; + +import { beforeAll, describe } from 'vitest'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/src/signers'; + +let recompile: boolean = true; +let preprocessing: boolean = false; +let signers: SignerWithAddress[], recipients, recipients_new + +beforeAll(async () => { + signers = await getSigners(pkg); + recipients = new Array(16).fill(`0x` + signers[1].address.slice(2).padStart(64, "0")) + recipients_new = new Array(16).fill(`0x` + signers[2].address.slice(2).padStart(64, "0")) + + if (recompile) { + execSync(`cd ./circuits/main/ && nargo check && nargo compile && nargo codegen-verifier`) + execSync(`cd ./circuits/recursion/ && nargo check && nargo compile && nargo codegen-verifier && cp ./contract/recursion/plonk_vk.sol ../../contracts/plonk_vk.sol`) + execSync(`npx hardhat compile`) + } + +}); + +describe('It compiles noir program code, receiving circuit bytes and abi object.', async () => { + const noirInstances: { main: Noir, recursive: Noir } = { + main: new Noir(mainCircuit), + recursive: new Noir(recursiveCircuit) + } + + const { main: noir } = noirInstances; + await noir.init(); + + let recursiveInputs: string[] = []; + let trees = { + utxo_tree: new MerkleTree(treeConfig.utxoDepth, noir.api), + tx_tree: new MerkleTree(treeConfig.txDepth, noir.api), + historic_tree: new MerkleTree(treeConfig.stateDepth, noir.api), + newHistoricRoot: "" + } + + await trees.utxo_tree.init(); + await trees.tx_tree.init(); + await trees.historic_tree.init(); + + let utxoIn: any[] = [] + let utxoOut: any[] = [] + let treeProof: any[] = [] + let data = await generateDataToml("0", "0", trees, noir.api) + let amountPublic = { + amountIn: BigInt(0), + amountOut: new Array(16).fill(BigInt(0)) + } + let contractInputs: any[] + + let Verifier, verifierContract, TechnicalPreview, technicalPreviewContract, XFTMock, xftMockContract; + let dataToml + + beforeAll(async () => { + Verifier = await getContractFactory(pkg, "UltraVerifier"); + verifierContract = await Verifier.deploy(); + let verifierAddr = await verifierContract.deployed(); + console.log(`Verifier deployed to ${verifierAddr.address}`); + let vk_hash = await verifierContract.getVerificationKeyHash(); + + XFTMock = await getContractFactory(pkg, "XFTMock"); + xftMockContract = await XFTMock.deploy(vk_hash); + let xftMockContractAddr = await xftMockContract.deployed(); + console.log(`Token deployed to ${xftMockContractAddr.address}`); + + TechnicalPreview = await getContractFactory(pkg, "TechnicalPreview"); + technicalPreviewContract = await TechnicalPreview.deploy(verifierContract.address, xftMockContract.address, "0x2f51641a7c20eec5405aedc1309dccfd3841bfd54e87d32957daa0371904fb11"); + await xftMockContract.grantRole(await xftMockContract.MINTER_ROLE(), technicalPreviewContract.address); + + let merkle_root = await technicalPreviewContract.merkleRoot() + let utxo_leaves = await technicalPreviewContract.getUtxoFromRoot(merkle_root); + for (let i = 0; i < utxo_leaves.length; i++) { + await trees.utxo_tree.insert(utxo_leaves[i]); + } + + let historic_leaves = await technicalPreviewContract.getValidRoots(); + for (let i = 0; i < historic_leaves.length; i++) { + await trees.historic_tree.insert(historic_leaves[i]); + } + }) + + + it('Should generate valid proof for correct input (deposit)', async () => { + + console.log("** Generating Test Batch **") + let batchSize0 = 1 + let secret0: Fr[] = [] + for (let s = 0; s < batchSize0; s++) { + secret0.push(randomBytesFr(32)) + } + let amountsOutUTXO = new Array(1).fill(BigInt(1e18)) + utxoOut = await generateUTXO(batchSize0, amountsOutUTXO, secret0, noir.api); + amountPublic.amountIn = BigInt(1e18) + await generateTestTransaction(utxoIn, utxoOut, trees, treeProof, amountPublic, data, recipients, noir.api, technicalPreviewContract) + + console.log("** Generating Transaction Proof #1 (Deposit) **") + await generateTestPublish(trees, data, noir.api) + dumpToml(data) + const input = [ + data.current_root, + data.deposit, + ...data.withdrawals, + ...data.commitment_out, + ...data.recipients, + data.oracle, + ...data.old_root_proof, + ...data.nullifier_hashes, + ...data.secrets, + ...data.utxo_in, + ...data.utxo_out, + ...data.indexes, + ...data.hash_path + ]; + + const public_input = [ + data.current_root, + data.deposit, + ...data.withdrawals, + ...data.commitment_out, + ...data.recipients, + data.oracle, + ...data.nullifier_hashes + ] + + const witness = await noir.generateWitness(input); + const proof = await noir.generateInnerProof(witness); + + expect(proof instanceof Uint8Array).to.be.true; + + const verified = await noir.verifyInnerProof(proof); + expect(verified).to.be.true; + + const numPublicInputs = public_input.length + 1; + const { proofAsFields, vkAsFields, vkHash } = await noir.generateInnerProofArtifacts( + proof, + numPublicInputs, + ); + + const publicInputs = proofAsFields.slice(0, numPublicInputs) + + expect(vkAsFields).to.be.of.length(114); + expect(vkHash).to.be.a('string'); + + const aggregationObject = Array(16).fill( + '0x0000000000000000000000000000000000000000000000000000000000000000', + ); + recursiveInputs = [ + ...vkAsFields.map(e => e.toString()), + ...proofAsFields, + ...publicInputs, + ...aggregationObject, + vkHash.toString(), + ...data.tx_in, + data.old_root, + data.new_root, + data.oracle, + ]; + + dataToml = { + verification_key: vkAsFields.map(e => e.toString()), + proof: proofAsFields, + public_inputs: publicInputs, + input_aggregation_object: aggregationObject, + key_hash: vkHash.toString(), + tx_ids: data.tx_in, + old_root: data.old_root, + new_root: data.new_root, + oracle: data.oracle, + } + + + const deposit = publicInputs[1] + const withdrawals = publicInputs.slice(2, 18) + const commitment_out = publicInputs.slice(18, 34) + const recipientPI = publicInputs.slice(34, 50) + const nullifier_hashes = publicInputs.slice(51, 67) + const id = publicInputs[67] + + contractInputs = [ + id, + commitment_out, + nullifier_hashes, + recipientPI, + withdrawals, + deposit + ] + + const savedOutputs = { + data: data, + dataToml: dataToml, + recursiveInputs: recursiveInputs, + contractInputs: contractInputs + } + + fs.writeFileSync('./outputs.json', JSON.stringify(savedOutputs)); // Caches outputs + await noir.destroy(); + }); + + it('Should verify proof within a proof', async () => { + const { recursive: noir } = noirInstances; + await noir.init(); + + const savedOutputs = JSON.parse(fs.readFileSync('./outputs.json').toString()) + data = savedOutputs.data + dataToml = savedOutputs.dataToml + recursiveInputs = savedOutputs.recursiveInputs + contractInputs = savedOutputs.contractInputs + + const proofInput = recursiveInputs.slice(114, 275); + const witness = await noir.generateWitness(recursiveInputs); + const proof = await noir.generateOuterProof(witness); + expect(proof instanceof Uint8Array).to.be.true; + + const numPublicInputs = 36; + const { proofAsFields } = await noir.generateInnerProofArtifacts( + proof, + numPublicInputs, + ); + + const verified = await noir.verifyOuterProof(proof); + console.log(verified); + + contractInputs.push(proofInput) + contractInputs.push(proofAsFields.slice(numPublicInputs - 16, numPublicInputs)) + + await technicalPreviewContract.enqueue(contractInputs) + + const batch = await technicalPreviewContract.getCurrentBatch(); + fs.writeFileSync('./batch.json', JSON.stringify(batch)) + + dumpTomlRecursive(dataToml) + execSync(`cd ./circuits/recursion/ && nargo prove`) + const proofString = `0x` + fs.readFileSync('./circuits/recursion/proofs/recursion.proof').toString() + + + const batchPublicInputs = [ + dataToml.key_hash, + dataToml.oracle, + dataToml.old_root, + dataToml.new_root, + batch + ] + + await technicalPreviewContract.publish(proofString, batchPublicInputs) + utxoIn = utxoOut + await noir.destroy(); + }); + + it('Should generate valid proof for correct input (withdrawal)', async () => { + const { main: noir } = noirInstances + await noir.init(); + + trees.utxo_tree = new MerkleTree(treeConfig.utxoDepth, noir.api) + trees.tx_tree = new MerkleTree(treeConfig.txDepth, noir.api) + trees.historic_tree = new MerkleTree(treeConfig.stateDepth, noir.api) + await trees.utxo_tree.init() + await trees.tx_tree.init() + await trees.historic_tree.init() + + console.log("Populating Historic Tree") + const historicRoots = await technicalPreviewContract.getValidRoots() + for (let r = 0; r < historicRoots.length; r++) { + await trees.historic_tree.insert(historicRoots[r]) + } + + console.log("** Generating Transaction Proof #2 (Withdraw/Transfer) **") + + let amountsOutUTXO = new Array(1).fill(BigInt(5e17)) + treeProof = [] + for (let i = 0; i < utxoIn.length; i++) { + let utxoProof = await generateTreeProof(utxoIn[i], noir.api, technicalPreviewContract) + treeProof.push(utxoProof); + } + + console.log("Tree proof generated") + + let batchSize0 = 1 + let secret0: Fr[] = [] + for (let s = 0; s < batchSize0; s++) { + secret0.push(randomBytesFr(32)) + } + + amountPublic.amountIn = BigInt(0); + amountPublic.amountOut = new Array(5).fill(1e17); + + utxoOut = await generateUTXO(batchSize0, amountsOutUTXO, secret0, noir.api); + + let oldRoot = dataToml.old_root + let newRoot = dataToml.new_root + data = await generateDataToml(oldRoot, newRoot, trees, noir.api) + + await generateTestTransaction(utxoIn, utxoOut, trees, treeProof, amountPublic, data, recipients_new, noir.api, technicalPreviewContract) + console.log("Test transaction generated") + + await generateTestPublish(trees, data, noir.api) + console.log("Test publish generated") + dumpToml(data) + const input = [ + data.current_root, + data.deposit, + ...data.withdrawals, + ...data.commitment_out, + ...data.recipients, + data.oracle, + ...data.old_root_proof, + ...data.nullifier_hashes, + ...data.secrets, + ...data.utxo_in, + ...data.utxo_out, + ...data.indexes, + ...data.hash_path + ]; + + const public_input = [ + data.current_root, + data.deposit, + ...data.withdrawals, + ...data.commitment_out, + ...data.recipients, + data.oracle, + ...data.nullifier_hashes + ] + + console.log("Input formatted") + + const witness = await noir.generateWitness(input); + console.log("witness calculated") + const proof = await noir.generateInnerProof(witness); + console.log("Proof generated") + + expect(proof instanceof Uint8Array).to.be.true; + + const verified = await noir.verifyInnerProof(proof); + expect(verified).to.be.true; + + const numPublicInputs = public_input.length + 1; const { proofAsFields, vkAsFields, vkHash } = await noir.generateInnerProofArtifacts( + proof, + numPublicInputs, + ); + + const publicInputs = proofAsFields.slice(0, numPublicInputs) + + expect(vkAsFields).to.be.of.length(114); + expect(vkHash).to.be.a('string'); + + const aggregationObject = Array(16).fill( + '0x0000000000000000000000000000000000000000000000000000000000000000', + ); + recursiveInputs = [ + ...vkAsFields.map(e => e.toString()), + ...proofAsFields, + ...publicInputs, + ...aggregationObject, + vkHash.toString(), + ...data.tx_in, + data.old_root, + data.new_root, + data.oracle, + ]; + + dataToml = { + verification_key: vkAsFields.map(e => e.toString()), + proof: proofAsFields, + public_inputs: publicInputs, + input_aggregation_object: aggregationObject, + key_hash: vkHash.toString(), + tx_ids: data.tx_in, + old_root: data.old_root, + new_root: data.new_root, + oracle: data.oracle, + } + + + const current_root = publicInputs[0] + const deposit = publicInputs[1] + const withdrawals = publicInputs.slice(2, 18) + const commitment_out = publicInputs.slice(18, 34) + const recipientPI = publicInputs.slice(34, 50) + const oracle = publicInputs[50] + const nullifier_hashes = publicInputs.slice(51, 67) + const id = publicInputs[67] + + contractInputs = [ + id, + commitment_out, + nullifier_hashes, + recipientPI, + withdrawals, + deposit + ] + + const savedOutputs = { + data: data, + dataToml: dataToml, + recursiveInputs: recursiveInputs, + contractInputs: contractInputs + } + + fs.writeFileSync('./outputs.json', JSON.stringify(savedOutputs)); + + + await noir.destroy(); + }); + + it('Should verify proof within a proof (withdrawal)', async () => { + const { recursive: noir } = noirInstances; + await noir.init(); + + const savedOutputs = JSON.parse(fs.readFileSync('./outputs.json').toString()) + data = savedOutputs.data + dataToml = savedOutputs.dataToml + recursiveInputs = savedOutputs.recursiveInputs + contractInputs = savedOutputs.contractInputs + + const proofInput = recursiveInputs.slice(114, 275); + const witness = await noir.generateWitness(recursiveInputs); + const proof = await noir.generateOuterProof(witness); + expect(proof instanceof Uint8Array).to.be.true; + + const numPublicInputs = 36; + const { proofAsFields } = await noir.generateInnerProofArtifacts( + proof, + numPublicInputs, + ); + + const verified = await noir.verifyOuterProof(proof); + console.log(verified); + + contractInputs.push(proofInput) + contractInputs.push(proofAsFields.slice(numPublicInputs - 16, numPublicInputs)) + + await technicalPreviewContract.enqueue(contractInputs) + + const batch = await technicalPreviewContract.getCurrentBatch(); + fs.writeFileSync('./batch.json', JSON.stringify(batch)) + + dumpTomlRecursive(dataToml) + execSync(`cd ./circuits/recursion/ && nargo prove`) + const proofString = `0x` + fs.readFileSync('./circuits/recursion/proofs/recursion.proof').toString() + + + const batchPublicInputs = [ + dataToml.key_hash, + dataToml.oracle, + dataToml.old_root, + dataToml.new_root, + batch + ] + + await technicalPreviewContract.publish(proofString, batchPublicInputs) + utxoIn = utxoOut + + const balanceOut = await xftMockContract.balanceOf(signers[2].address) + + expect(balanceOut).to.be.equal(BigNumber.from(BigInt(5e17).toString())) + await noir.destroy(); + }); + +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..e1a0744678538fb4403ce43eb3c35cc9604c0570 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "target": "es2020", + "useDefineForClassFields": true, + "lib": [ + "DOM", + "DOM.Iterable", + "ESNext" + ], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "CommonJS", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "noImplicitAny": false, + "strictNullChecks": true, + "incremental": true + }, + "exclude": [ + "dist", + "node_modules" + ], + "include": [ + "./test", + "./src", + "./scripts" + ], +} diff --git a/utils/MerkleTree.ts b/utils/MerkleTree.ts new file mode 100644 index 0000000000000000000000000000000000000000..19671d868af9de54aa1b5c8cb3ee5b09c83801c1 --- /dev/null +++ b/utils/MerkleTree.ts @@ -0,0 +1,182 @@ +// @ts-ignore +import { Fr } from '@aztec/bb.js'; +// @ts-ignore -- no types + +export async function pedersenLeftRight( + barretenberg: any, + left: string, + right: string) { + + let leftBuffer = Fr.fromBufferReduce(Buffer.from(left.slice(2), 'hex')); + let rightBuffer = Fr.fromBufferReduce(Buffer.from(right.slice(2), 'hex')); + let hashRes = await barretenberg.pedersenPlookupCompressFields(leftBuffer, rightBuffer); + return hashRes.toString() +} + +export class MerkleTree { + zeroValue = "0xf35fcb490b7ea67c3ac26ed530fa5d8dfe8be344e7177ebb63fe02723fb6f725"; // sha256("Momiji") + levels: number; + defaultLeaves: string[] + hashLeftRight: any; + storage: Map<string, string>; + zeros: string[]; + totalLeaves: number; + barretenberg: any; + + constructor( + levels, + barretenberg, + defaultLeaves = [], + hashLeftRight = pedersenLeftRight) { + this.levels = levels; + this.hashLeftRight = hashLeftRight; + this.storage = new Map(); + this.zeros = []; + this.totalLeaves = 0; + this.barretenberg = barretenberg; + this.defaultLeaves = defaultLeaves + } + + async init() { + // build zeros depends on tree levels + let currentZero = this.zeroValue; + this.zeros.push(currentZero); + for (let i = 0; i < this.levels; i++) { + currentZero = await this.hashLeftRight(this.barretenberg, currentZero, currentZero); + this.zeros.push(currentZero); + } + + if (this.defaultLeaves.length > 0) { + this.totalLeaves = this.defaultLeaves.length; + + // store leaves with key value pair + let level = 0; + this.defaultLeaves.forEach((leaf, index) => { + this.storage.set(MerkleTree.indexToKey(level, index), leaf); + }); + + // build tree with initial leaves + level++; + let numberOfNodesInLevel = Math.ceil(this.totalLeaves / 2); + for (level; level <= this.levels; level++) { + for (let i = 0; i < numberOfNodesInLevel; i++) { + const leftKey = MerkleTree.indexToKey(level - 1, 2 * i); + const rightKey = MerkleTree.indexToKey(level - 1, 2 * i + 1); + + const left = this.storage.get(leftKey); + const right = this.storage.get(rightKey) || this.zeros[level - 1]; + if (!left) throw new Error("leftKey not found"); + + const node = await this.hashLeftRight(this.barretenberg, left, right); + this.storage.set(MerkleTree.indexToKey(level, i), node); + } + numberOfNodesInLevel = Math.ceil(numberOfNodesInLevel / 2); + } + } + } + + static indexToKey(level, index) { + return `${level}-${index}`; + } + + getIndex(leaf) { + for (const [key, value] of this.storage) { + if (value === leaf) { + return Number(key.split("-")[1]); + } + } + return -1; + } + + root() { + return this.storage.get(MerkleTree.indexToKey(this.levels, 0)) || this.zeros[this.levels]; + } + + proof(indexOfLeaf) { + let pathElements: string[] = []; + let pathIndices: number[] = []; + + const leaf = this.storage.get(MerkleTree.indexToKey(0, indexOfLeaf)); + if (!leaf) throw new Error("leaf not found"); + + // store sibling into pathElements and target's indices into pathIndices + const handleIndex = (level, currentIndex, siblingIndex) => { + const siblingValue = this.storage.get(MerkleTree.indexToKey(level, siblingIndex)) || this.zeros[level]; + pathElements.push(siblingValue); + pathIndices.push(currentIndex % 2); + }; + + this.traverse(indexOfLeaf, handleIndex); + + return { + root: this.root(), + pathElements, + pathIndices, + leaf: leaf, + }; + } + + async insert(leaf) { + const index = this.totalLeaves; + await this.update(index, leaf, true); + this.totalLeaves++; + } + + async update(index, newLeaf, isInsert = false) { + if (!isInsert && index >= this.totalLeaves) { + throw Error("Use insert method for new elements."); + } else if (isInsert && index < this.totalLeaves) { + throw Error("Use update method for existing elements."); + } + + let keyValueToStore: any[] = []; + let currentElement = newLeaf; + + const handleIndex = async (level, currentIndex, siblingIndex) => { + const siblingElement = this.storage.get(MerkleTree.indexToKey(level, siblingIndex)) || this.zeros[level]; + + let left; + let right; + if (currentIndex % 2 === 0) { + left = currentElement; + right = siblingElement; + } else { + left = siblingElement; + right = currentElement; + } + + keyValueToStore.push({ + key: MerkleTree.indexToKey(level, currentIndex), + value: currentElement, + }); + currentElement = await this.hashLeftRight(this.barretenberg, left, right); + }; + await this.traverse(index, handleIndex); + + // push root to the end + keyValueToStore.push({ + key: MerkleTree.indexToKey(this.levels, 0), + value: currentElement, + }); + + keyValueToStore.forEach(o => { + this.storage.set(o.key, o.value); + }); + } + + // traverse from leaf to root with handler for target node and sibling node + async traverse(indexOfLeaf, handler) { + let currentIndex = indexOfLeaf; + for (let i = 0; i < this.levels; i++) { + let siblingIndex; + if (currentIndex % 2 === 0) { + siblingIndex = currentIndex + 1; + } else { + siblingIndex = currentIndex - 1; + } + + await handler(i, currentIndex, siblingIndex); + currentIndex = Math.floor(currentIndex / 2); + } + } +} \ No newline at end of file diff --git a/utils/noir.ts b/utils/noir.ts new file mode 100644 index 0000000000000000000000000000000000000000..4c5bc7c1daea5881f1cf2e9c85a942f2a6f40e80 --- /dev/null +++ b/utils/noir.ts @@ -0,0 +1,155 @@ +import { decompressSync } from 'fflate'; +// @ts-ignore +import { Barretenberg, Crs, RawBuffer, Fr } from '@aztec/bb.js'; +import { executeCircuit, compressWitness } from '@noir-lang/acvm_js'; +import { ethers } from 'ethers'; // I'm lazy so I'm using ethers to pad my input +import { Ptr } from '@aztec/bb.js/dest/browser/types'; + + +export class Noir { + circuit: any; + acir: string = ''; + acirBufferCompressed: Uint8Array = Uint8Array.from([]); + acirBufferUncompressed: Uint8Array = Uint8Array.from([]); + + api = {} as Barretenberg; + acirComposer = {} as Ptr; + + constructor(circuit: Object) { + this.circuit = circuit; + } + + async init() { + const isBrowser = typeof window !== 'undefined'; + if (isBrowser) { + const { default: initACVM } = await import('@noir-lang/acvm_js'); + await initACVM(); + } + + this.acirBufferCompressed = Buffer.from(this.circuit.bytecode, 'base64'); + this.acirBufferUncompressed = decompressSync(this.acirBufferCompressed); + + this.api = await Barretenberg.new(4); + + const [exact, total, subgroup] = await this.api.acirGetCircuitSizes( + this.acirBufferUncompressed, + ); + const subgroupSize = Math.pow(2, Math.ceil(Math.log2(total))); + const crs = await Crs.new(subgroupSize + 1); + await this.api.commonInitSlabAllocator(subgroupSize); + await this.api.srsInitSrs( + new RawBuffer(crs.getG1Data()), + crs.numPoints, + new RawBuffer(crs.getG2Data()), + ); + + this.acirComposer = await this.api.acirNewAcirComposer(subgroupSize); + } + + // Generates the intermediate witnesses by using `input` + // as the initial set of witnesses and executing these + // against the circuit. + async generateWitness(input: any): Promise<Uint8Array> { + const initialWitness = new Map<number, string>(); + for (let i = 1; i <= input.length; i++) { + initialWitness.set(i, ethers.utils.hexZeroPad(input[i - 1], 32)); + } + + const witnessMap = await executeCircuit(this.acirBufferCompressed, initialWitness, () => { + throw Error('unexpected oracle'); + }); + + const witnessBuff = compressWitness(witnessMap); + return witnessBuff; + } + + // Generates an inner proof. This is the proof that will be verified + // in another circuit. + // + // We set isRecursive to true, which will tell the backend to + // generate the proof using components that will make the proof + // easier to verify in a circuit. + async generateInnerProof(witness: Uint8Array) { + const makeEasyToVerifyInCircuit = true; + return this.generateProof(witness, makeEasyToVerifyInCircuit); + } + + // Generates artifacts that will be passed to the circuit that will verify this proof. + // + // Instead of passing the proof and verification key as a byte array, we pass them + // as fields which makes it cheaper to verify in a circuit. + // + // The number of public inputs denotes how many public inputs are in the inner proof. + async generateInnerProofArtifacts(proof: Uint8Array, numOfPublicInputs: number = 0) { + console.log('serializing proof'); + const proofAsFields = await this.api.acirSerializeProofIntoFields( + this.acirComposer, + proof, + numOfPublicInputs, + ); + console.log('proof serialized'); + console.log('serializing vk'); + await this.api.acirInitVerificationKey(this.acirComposer); + // Note: If you don't init verification key, `acirSerializeVerificationKeyIntoFields`` will just hang on serialization + const vk = await this.api.acirSerializeVerificationKeyIntoFields(this.acirComposer); + console.log('vk serialized'); + + return { + proofAsFields: proofAsFields.map(p => p.toString()), + vkAsFields: vk[0].map(vk => vk.toString()), + vkHash: vk[1].toString(), + }; + } + + // Generate an outer proof. This is the proof for the circuit which will verify + // inner proofs. + // + // The settings for this proof are the same as the settings for a "normal" proof + // ie one that is not in the recursive setting. + async generateOuterProof(witness: Uint8Array) { + const makeEasyToVerifyInCircuit = false; + return this.generateProof(witness, makeEasyToVerifyInCircuit); + } + + async generateProof(witness: Uint8Array, makeEasyToVerifyInCircuit: boolean) { + console.log('Creating outer proof'); + + const decompressedWitness = decompressSync(witness); + + const proof = await this.api.acirCreateProof( + this.acirComposer, + this.acirBufferUncompressed, + decompressedWitness, + makeEasyToVerifyInCircuit, + ); + + return proof; + } + + async verifyInnerProof(proof: Uint8Array) { + const makeEasyToVerifyInCircuit = true; + return this.verifyProof(proof, makeEasyToVerifyInCircuit); + } + + async verifyOuterProof(proof: Uint8Array) { + const makeEasyToVerifyInCircuit = false; + console.log('verifying outer proof'); + const verified = await this.verifyProof(proof, makeEasyToVerifyInCircuit); + console.log(verified); + return verified; + } + + async verifyProof(proof: Uint8Array, makeEasyToVerifyInCircuit: boolean) { + await this.api.acirInitVerificationKey(this.acirComposer); + const verified = await this.api.acirVerifyProof( + this.acirComposer, + proof, + makeEasyToVerifyInCircuit, + ); + return verified; + } + + async destroy() { + await this.api.destroy(); + } +} diff --git a/utils/test_helpers.ts b/utils/test_helpers.ts new file mode 100644 index 0000000000000000000000000000000000000000..940c9cecb19e8ebb43aa521dc228ebd571ade0ae --- /dev/null +++ b/utils/test_helpers.ts @@ -0,0 +1,272 @@ +import { randomBytes } from 'crypto' +import { readFileSync } from 'fs'; +import { MerkleTree } from './MerkleTree'; +import { keccak256 } from "@ethersproject/keccak256/lib/index.js"; +import fs from 'fs' +// @ts-ignore +import { Fr } from '@aztec/bb.js'; +const ZERO_VALUE = "0xf35fcb490b7ea67c3ac26ed530fa5d8dfe8be344e7177ebb63fe02723fb6f725"; +const MAX_VALUE = Fr.MAX_VALUE; +export const treeConfig = { + utxoDepth: 4, + txDepth: 4, + stateDepth: 9 +} + +export const circuitConfig = { + main: "./src/main.nr" +} + +const treeSum = [treeConfig.utxoDepth, treeConfig.txDepth, treeConfig.stateDepth].reduce((a, b) => a + b) + +function evalWithScopePrefix(js) { + let public_inputs = {}; + js.split('\n').forEach(line => { + const trimmedLine = line.trim(); + if (trimmedLine.length > 0) { + eval(`public_inputs.` + trimmedLine); + } + }); + return public_inputs; +} + +export function randomBytesFr(numBytes) { + const bytes = randomBytes(numBytes) + const bytesFr = Fr.fromBufferReduce(bytes) + return bytesFr +} + +export const format = (data) => { + if (typeof data === "string") return `"${data}"`; + if (data.length === 0) return "[]"; + return `[\n "${data.join('",\n "')}"\n]`; +} + +export const dumpToml = (data) => { + let toml: any = []; + Object.entries(data).forEach(([key, value]) => { + toml.push(`${key} = ${format(value)}`); + }); + toml = toml.join('\n'); + fs.writeFileSync('./circuits/main/Prover.toml', toml); +} + +export const dumpTomlRecursive = (data) => { + let toml: any = []; + Object.entries(data).forEach(([key, value]) => { + toml.push(`${key} = ${format(value)}`); + }); + toml = toml.join('\n'); + fs.writeFileSync('./circuits/recursion/Prover.toml', toml); +} + +export function path_to_uint8array(path) { + let buffer = readFileSync(path); + return new Uint8Array(buffer); +} +const toFixedHex = (number, pad0x, length = 32) => { + let hexString = number.toString(16).padStart(length * 2, '0'); + return (pad0x ? `0x` + hexString : hexString); +} + +export function getSolidityHash(asset) { + return keccak256(asset); +} + +export function generateHashPathInput(hash_path) { + let hash_path_input: string[] = []; + for (var i = 0; i < hash_path.length; i++) { + hash_path_input.push(`0x` + hash_path[i]); + } + return hash_path_input; +} + +export async function generateUTXO(batchSize, amounts, _secrets, BarretenbergApi) { + let utxos: any[] = [] + for (let i = 0; i < batchSize; i++) { + let amountBN = amounts[i] + let utxo = { + secret: _secrets[i], + owner: await BarretenbergApi.pedersenPlookupCompress([_secrets[i]]), + amountBN: amountBN, + amount: Fr.fromString(toFixedHex(Number(amountBN.toString()), true)), + assetType: Fr.fromBufferReduce(Buffer.from(getSolidityHash(0), 'hex')), + } + utxos.push(utxo) + } + return utxos +} + +export async function generateTreeProof(utxoIn, BarretenbergApi, contract) { + let trees = { + utxo_tree: new MerkleTree(treeConfig.utxoDepth, BarretenbergApi), + tx_tree: new MerkleTree(treeConfig.txDepth, BarretenbergApi), + historic_tree: new MerkleTree(treeConfig.stateDepth, BarretenbergApi), + } + await trees.utxo_tree.init() + await trees.tx_tree.init() + await trees.historic_tree.init() + let commitment = await BarretenbergApi.pedersenPlookupCompress( + [utxoIn.owner, utxoIn.amount, utxoIn.assetType] + ) + commitment = commitment.toString() + let old_root = await contract.getRootFromUtxo(commitment) + let utxo_list = await contract.getUtxoFromRoot(old_root) + let historic_roots = await contract.getValidRoots() + let oracle = await BarretenbergApi.pedersenPlookupCompress([new Fr(0n)]) + for (let i = 0; i < utxo_list.length; i++) { + await trees.utxo_tree.insert(utxo_list[i]); + } + let utxo_root = trees.utxo_tree.root() + await trees.tx_tree.insert(utxo_root); + let tx_root_Fr = Fr.fromString(trees.tx_tree.root()) + let batch = await BarretenbergApi.pedersenPlookupCompress([tx_root_Fr, oracle]) + let new_root = await BarretenbergApi.pedersenPlookupCompress([batch, Fr.fromString(old_root)]) + new_root = new_root.toString() + for (let i = 0; i < historic_roots.length; i++) { + await trees.historic_tree.insert(historic_roots[i]); + } + let proofs = { + utxo: { + leaf: commitment, + index: trees.utxo_tree.getIndex(commitment), + root: utxo_root, + hash_path: trees.utxo_tree.proof( + trees.utxo_tree.getIndex(commitment) + ).pathElements + }, + tx: { + leaf: utxo_root, + index: trees.tx_tree.getIndex(utxo_root), + root: trees.tx_tree.root(), + hash_path: trees.tx_tree.proof( + trees.tx_tree.getIndex(utxo_root) + ).pathElements + }, + historic: { + leaf: new_root, + index: trees.historic_tree.getIndex(new_root), + root: trees.historic_tree.root(), + hash_path: trees.historic_tree.proof( + trees.historic_tree.getIndex(new_root) + ).pathElements + } + } + return proofs +} + +export async function generateDataToml(oldRoot, newRoot, trees, api) { + let zeroHex = toFixedHex(0, true) + let zeroHash = await api.pedersenPlookupCompress([Fr.fromString(zeroHex)]) + const data = { + tx_in: new Array(16).fill(ZERO_VALUE), + secrets: new Array(16).fill(zeroHex), + utxo_in: new Array(48).fill(zeroHex), + utxo_out: new Array(48).fill(zeroHex), + oracle: zeroHash.toString(), + old_root_proof: new Array(16).fill(zeroHex), + old_root: ZERO_VALUE, + new_root: ZERO_VALUE, + current_root: trees.historic_tree.root(), + indexes: new Array(48).fill(zeroHex), + hash_path: new Array(16 * treeSum).fill(zeroHex), + commitment_out: new Array(16).fill(ZERO_VALUE), + deposit: zeroHex, + withdrawals: new Array(16).fill(zeroHex), + nullifier_hashes: new Array(16).fill(ZERO_VALUE), + recipients: new Array(16).fill(zeroHex) + } + if (oldRoot !== "0") data.old_root = oldRoot; + if (newRoot !== "0") data.new_root = newRoot; + return data +} + +export async function generateTestTransaction(utxoIn, utxoOut, trees, treeProof, amountPublic, data, recipients, BarretenbergApi, contract) { + let utxoInLen = utxoIn.length + let utxoOutLen = utxoOut.length + for (let i = 0; i < utxoInLen; i++) { + let ownerHex = utxoIn[i].owner.toString(); + let amountHex = utxoIn[i].amount.toString(); + let assetTypeHex = utxoIn[i].assetType.toString(); + let note_commitment = await BarretenbergApi.pedersenPlookupCompress([utxoIn[i].owner, utxoIn[i].amount, utxoIn[i].assetType]); + let utxoLeaf = note_commitment.toString() + data.secrets[i] = utxoIn[i].secret.toString() + data.nullifier_hashes[i] = (await BarretenbergApi.pedersenPlookupCompress([utxoIn[i].secret, utxoIn[i].secret])).toString() + data.old_root_proof[i] = await contract.utxoPrevRoots(utxoLeaf) + data.utxo_in[i * 3 + 0] = ownerHex + data.utxo_in[i * 3 + 1] = amountHex + data.utxo_in[i * 3 + 2] = assetTypeHex + data.indexes[i * 4 + 0] = treeProof[i].utxo.index + data.indexes[i * 4 + 1] = treeProof[i].tx.index + data.indexes[i * 4 + 2] = treeProof[i].historic.index + let utxoPath = treeProof[i].utxo.hash_path + let txPath = treeProof[i].tx.hash_path + let historicPath = treeProof[i].historic.hash_path + for (let j = 0; j < utxoPath.length; j++) { + data.hash_path[i * treeSum + 0 + j] = utxoPath[j] + data.hash_path[i * treeSum + treeConfig.utxoDepth + j] = txPath[j] + } + for (let k = 0; k < historicPath.length; k++) { + data.hash_path[i * treeSum + treeConfig.utxoDepth + treeConfig.txDepth + k] = historicPath[k] + } + } + for (let i = 0; i < utxoOutLen; i++) { + let ownerHex = utxoOut[i].owner.toString(); + let amountHex = utxoOut[i].amount.toString(); + let assetTypeHex = utxoOut[i].assetType.toString(); + let note_commitment = await BarretenbergApi.pedersenPlookupCompress([utxoOut[i].owner, utxoOut[i].amount, utxoOut[i].assetType]); + let utxoLeaf = note_commitment.toString() + await trees.utxo_tree.insert(utxoLeaf) + data.utxo_out[i * 3 + 0] = ownerHex + data.utxo_out[i * 3 + 1] = amountHex + data.utxo_out[i * 3 + 2] = assetTypeHex + data.commitment_out[i] = utxoLeaf + } + data.tx_in[0] = trees.utxo_tree.root() + data.deposit = toFixedHex(Number(amountPublic.amountIn.toString()), true) + for (let w = 0; w < amountPublic.amountOut.length; w++) { + data.withdrawals[w] = toFixedHex(Number(amountPublic.amountOut[w].toString()), true) + } + for (let r = 0; r < recipients.length; r++) { + data.recipients[r] = recipients[r]; + } + data.current_root = trees.historic_tree.root() +} + +export async function generateTestPublish(trees, data, api) { + let utxoTree = trees.utxo_tree + let txTree = trees.tx_tree + let historicTree = trees.historic_tree + let utxoRoot = utxoTree.root() + await txTree.insert(utxoRoot) + let txRoot = txTree.root() + let txRootFr = Fr.fromBufferReduce(Buffer.from(txRoot.slice(2), 'hex')) + let oracleFr = Fr.fromBufferReduce(toFixedHex(0, true)) + let oracleHash = await api.pedersenPlookupCompress([oracleFr]) + let batch = await api.pedersenPlookupCompressFields(txRootFr, oracleHash) + let oldHistoricRoot = Fr.fromBufferReduce(Buffer.from(data.new_root.slice(2), 'hex')) + let newHistoricRoot = await api.pedersenPlookupCompress([batch, oldHistoricRoot]) + let newHistoricRootHex = newHistoricRoot.toString() + await historicTree.insert(newHistoricRootHex) + data.old_root = oldHistoricRoot.toString() + data.new_root = newHistoricRootHex + trees.utxoTreeOld = trees.utxo_tree + trees.txTreeOld = trees.tx_tree + trees.newHistoricRoot = newHistoricRootHex + const input = [ + data.current_root, + data.deposit, + data.withdrawals, + data.commitment_out, + data.recipients, + data.oracle, + data.old_root_proof, + data.nullifier_hashes, + data.secrets, + data.utxo_in, + data.utxo_out, + data.indexes, + data.hash_path + ]; + dumpToml(data) +} \ No newline at end of file diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..b2eb1f14c3499fbc902ae4ef7190a10f8dd5b70d --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,15 @@ +/// <reference types="vitest" /> + +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + root: '.', + test: { + testTimeout: 600000000, + clearMocks: true, + globals: true, + setupFiles: ['dotenv/config'], + watchExclude: ['node_modules', 'artifacts', 'cache'], + forceRerunTriggers: ['circuits/**/*.sol'], + }, +});