diff --git a/.prettierrc.json b/.prettierrc.json index ba2609fc8f..5dbd009d19 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -9,12 +9,19 @@ "bracketSpacing": true, "arrowParens": "always", "proseWrap": "always", + "plugins": ["prettier-plugin-solidity"], "overrides": [ { "files": ["*.mdx", "*.md"], "options": { "printWidth": 100 } + }, + { + "files": "*.sol", + "options": { + "singleQuote": false + } } ] } diff --git a/crates/evm-helpers/tests/fixtures/emit_logs.sol b/crates/evm-helpers/tests/fixtures/emit_logs.sol index b42e73d54f..5f70f8b800 100644 --- a/crates/evm-helpers/tests/fixtures/emit_logs.sol +++ b/crates/evm-helpers/tests/fixtures/emit_logs.sol @@ -7,28 +7,28 @@ pragma solidity >=0.4.24; contract EmitLogs { - event ValueChanged(address indexed author, uint256 count, string value); - event PublishMessage(string value); + event ValueChanged(address indexed author, uint256 count, string value); + event PublishMessage(string value); - string _value; + string _value; - uint256 count = 0; + uint256 count = 0; - constructor() { - _value = ""; - } + constructor() { + _value = ""; + } - function getValue() public view returns (string memory) { - return _value; - } + function getValue() public view returns (string memory) { + return _value; + } - function setValue(string memory value) public { - count++; - emit ValueChanged(msg.sender, count, value); - _value = value; - } + function setValue(string memory value) public { + count++; + emit ValueChanged(msg.sender, count, value); + _value = value; + } - function emitPublishMessage(string memory value) public { - emit PublishMessage(value); - } + function emitPublishMessage(string memory value) public { + emit PublishMessage(value); + } } diff --git a/crates/evm-helpers/tests/fixtures/fake_enclave.sol b/crates/evm-helpers/tests/fixtures/fake_enclave.sol index fe4c06645c..38e1c9d481 100644 --- a/crates/evm-helpers/tests/fixtures/fake_enclave.sol +++ b/crates/evm-helpers/tests/fixtures/fake_enclave.sol @@ -7,100 +7,70 @@ pragma solidity >=0.4.24; contract FakeEnclave { - event E3Activated( - uint256 e3Id, - uint256 expiration, - bytes committeePublicKey - ); - event InputPublished( - uint256 indexed e3Id, - bytes data, - uint256 inputHash, - uint256 index - ); - event CiphertextOutputPublished( - uint256 indexed e3Id, - bytes ciphertextOutput - ); - event PlaintextOutputPublished(uint256 indexed e3Id, bytes plaintextOutput); - event CommitteePublished(uint256 indexed e3Id, bytes publicKey); + event E3Activated(uint256 e3Id, uint256 expiration, bytes committeePublicKey); + event InputPublished(uint256 indexed e3Id, bytes data, uint256 inputHash, uint256 index); + event CiphertextOutputPublished(uint256 indexed e3Id, bytes ciphertextOutput); + event PlaintextOutputPublished(uint256 indexed e3Id, bytes plaintextOutput); + event CommitteePublished(uint256 indexed e3Id, bytes publicKey); - // Emit E3Activated event with passed test data - function emitE3Activated( - uint256 e3Id, - uint256 expiration, - bytes memory committeePublicKey - ) public { - emit E3Activated(e3Id, expiration, committeePublicKey); - } + // Emit E3Activated event with passed test data + function emitE3Activated(uint256 e3Id, uint256 expiration, bytes memory committeePublicKey) public { + emit E3Activated(e3Id, expiration, committeePublicKey); + } - // Emit InputPublished event with passed test data - function emitInputPublished( - uint256 e3Id, - bytes memory data, - uint256 inputHash, - uint256 index - ) public { - emit InputPublished(e3Id, data, inputHash, index); - } + // Emit InputPublished event with passed test data + function emitInputPublished(uint256 e3Id, bytes memory data, uint256 inputHash, uint256 index) public { + emit InputPublished(e3Id, data, inputHash, index); + } - // Emit CiphertextOutputPublished event with passed test data - function emitCiphertextOutputPublished( - uint256 e3Id, - bytes memory ciphertextOutput - ) public { - emit CiphertextOutputPublished(e3Id, ciphertextOutput); - } + // Emit CiphertextOutputPublished event with passed test data + function emitCiphertextOutputPublished(uint256 e3Id, bytes memory ciphertextOutput) public { + emit CiphertextOutputPublished(e3Id, ciphertextOutput); + } - // Emit PlaintextOutputPublished event with passed test data - function emitPlaintextOutputPublished( - uint256 e3Id, - bytes memory plaintextOutput - ) public { - emit PlaintextOutputPublished(e3Id, plaintextOutput); - } + // Emit PlaintextOutputPublished event with passed test data + function emitPlaintextOutputPublished(uint256 e3Id, bytes memory plaintextOutput) public { + emit PlaintextOutputPublished(e3Id, plaintextOutput); + } - // Emit CommitteePublished event with passed test data - function emitCommitteePublished( - uint256 e3Id, - bytes memory publicKey - ) public { - emit CommitteePublished(e3Id, publicKey); - } + // Emit CommitteePublished event with passed test data + function emitCommitteePublished(uint256 e3Id, bytes memory publicKey) public { + emit CommitteePublished(e3Id, publicKey); + } - function getE3(uint256 _e3Id) external view returns (E3 memory e3) { - e3 = E3({ - seed: 123456789012, - threshold: [uint32(2), uint32(3)], - requestBlock: 18750000, - startWindow: [uint256(18750100), uint256(18750200)], - duration: 100, - expiration: block.timestamp + 1 days, - encryptionSchemeId: bytes32(keccak256("AES-256-GCM")), - e3Program: 0x7F3E4df648B8Cb96C1D343be976b91B97CaD5c21, - decryptionVerifier: 0x4B0D8c2E5f7a6c832f8b16d3aB0e7F5d9E9B24b1, - e3ProgramParams: abi.encode(42, "testParams"), - customParams: abi.encode("custom_params"), - committeePublicKey: bytes32(keccak256("committee_public_key")), - ciphertextOutput: bytes32(keccak256("encrypted_data")), - plaintextOutput: abi.encode("decrypted_result") - }); - } + function getE3(uint256 _e3Id) external view returns (E3 memory e3) { + e3 = E3({ + seed: 123456789012, + threshold: [uint32(2), uint32(3)], + requestBlock: 18750000, + startWindow: [uint256(18750100), uint256(18750200)], + duration: 100, + expiration: block.timestamp + 1 days, + encryptionSchemeId: bytes32(keccak256("AES-256-GCM")), + e3Program: 0x7F3E4df648B8Cb96C1D343be976b91B97CaD5c21, + decryptionVerifier: 0x4B0D8c2E5f7a6c832f8b16d3aB0e7F5d9E9B24b1, + e3ProgramParams: abi.encode(42, "testParams"), + customParams: abi.encode("custom_params"), + committeePublicKey: bytes32(keccak256("committee_public_key")), + ciphertextOutput: bytes32(keccak256("encrypted_data")), + plaintextOutput: abi.encode("decrypted_result") + }); + } } struct E3 { - uint256 seed; - uint32[2] threshold; - uint256 requestBlock; - uint256[2] startWindow; - uint256 duration; - uint256 expiration; - bytes32 encryptionSchemeId; - address e3Program; - bytes e3ProgramParams; - bytes customParams; - address decryptionVerifier; - bytes32 committeePublicKey; - bytes32 ciphertextOutput; - bytes plaintextOutput; + uint256 seed; + uint32[2] threshold; + uint256 requestBlock; + uint256[2] startWindow; + uint256 duration; + uint256 expiration; + bytes32 encryptionSchemeId; + address e3Program; + bytes e3ProgramParams; + bytes customParams; + address decryptionVerifier; + bytes32 committeePublicKey; + bytes32 ciphertextOutput; + bytes plaintextOutput; } diff --git a/crates/evm/tests/fixtures/emit_logs.sol b/crates/evm/tests/fixtures/emit_logs.sol index 118787b985..72d659b5d3 100644 --- a/crates/evm/tests/fixtures/emit_logs.sol +++ b/crates/evm/tests/fixtures/emit_logs.sol @@ -7,24 +7,23 @@ pragma solidity >=0.4.24; contract EmitLogs { + event ValueChanged(address indexed author, uint256 count, string value); - event ValueChanged(address indexed author, uint256 count, string value); + string _value; - string _value; + uint256 count = 0; - uint256 count = 0; + constructor() { + _value = ""; + } - constructor() { - _value = ""; - } + function getValue() public view returns (string memory) { + return _value; + } - function getValue() view public returns (string memory) { - return _value; - } - - function setValue(string memory value) public { - count++; - emit ValueChanged(msg.sender, count, value); - _value = value; - } + function setValue(string memory value) public { + count++; + emit ValueChanged(msg.sender, count, value); + _value = value; + } } diff --git a/crates/indexer/tests/fixtures/emit_logs.sol b/crates/indexer/tests/fixtures/emit_logs.sol index b42e73d54f..5f70f8b800 100644 --- a/crates/indexer/tests/fixtures/emit_logs.sol +++ b/crates/indexer/tests/fixtures/emit_logs.sol @@ -7,28 +7,28 @@ pragma solidity >=0.4.24; contract EmitLogs { - event ValueChanged(address indexed author, uint256 count, string value); - event PublishMessage(string value); + event ValueChanged(address indexed author, uint256 count, string value); + event PublishMessage(string value); - string _value; + string _value; - uint256 count = 0; + uint256 count = 0; - constructor() { - _value = ""; - } + constructor() { + _value = ""; + } - function getValue() public view returns (string memory) { - return _value; - } + function getValue() public view returns (string memory) { + return _value; + } - function setValue(string memory value) public { - count++; - emit ValueChanged(msg.sender, count, value); - _value = value; - } + function setValue(string memory value) public { + count++; + emit ValueChanged(msg.sender, count, value); + _value = value; + } - function emitPublishMessage(string memory value) public { - emit PublishMessage(value); - } + function emitPublishMessage(string memory value) public { + emit PublishMessage(value); + } } diff --git a/crates/indexer/tests/fixtures/fake_enclave.sol b/crates/indexer/tests/fixtures/fake_enclave.sol index fe4c06645c..38e1c9d481 100644 --- a/crates/indexer/tests/fixtures/fake_enclave.sol +++ b/crates/indexer/tests/fixtures/fake_enclave.sol @@ -7,100 +7,70 @@ pragma solidity >=0.4.24; contract FakeEnclave { - event E3Activated( - uint256 e3Id, - uint256 expiration, - bytes committeePublicKey - ); - event InputPublished( - uint256 indexed e3Id, - bytes data, - uint256 inputHash, - uint256 index - ); - event CiphertextOutputPublished( - uint256 indexed e3Id, - bytes ciphertextOutput - ); - event PlaintextOutputPublished(uint256 indexed e3Id, bytes plaintextOutput); - event CommitteePublished(uint256 indexed e3Id, bytes publicKey); + event E3Activated(uint256 e3Id, uint256 expiration, bytes committeePublicKey); + event InputPublished(uint256 indexed e3Id, bytes data, uint256 inputHash, uint256 index); + event CiphertextOutputPublished(uint256 indexed e3Id, bytes ciphertextOutput); + event PlaintextOutputPublished(uint256 indexed e3Id, bytes plaintextOutput); + event CommitteePublished(uint256 indexed e3Id, bytes publicKey); - // Emit E3Activated event with passed test data - function emitE3Activated( - uint256 e3Id, - uint256 expiration, - bytes memory committeePublicKey - ) public { - emit E3Activated(e3Id, expiration, committeePublicKey); - } + // Emit E3Activated event with passed test data + function emitE3Activated(uint256 e3Id, uint256 expiration, bytes memory committeePublicKey) public { + emit E3Activated(e3Id, expiration, committeePublicKey); + } - // Emit InputPublished event with passed test data - function emitInputPublished( - uint256 e3Id, - bytes memory data, - uint256 inputHash, - uint256 index - ) public { - emit InputPublished(e3Id, data, inputHash, index); - } + // Emit InputPublished event with passed test data + function emitInputPublished(uint256 e3Id, bytes memory data, uint256 inputHash, uint256 index) public { + emit InputPublished(e3Id, data, inputHash, index); + } - // Emit CiphertextOutputPublished event with passed test data - function emitCiphertextOutputPublished( - uint256 e3Id, - bytes memory ciphertextOutput - ) public { - emit CiphertextOutputPublished(e3Id, ciphertextOutput); - } + // Emit CiphertextOutputPublished event with passed test data + function emitCiphertextOutputPublished(uint256 e3Id, bytes memory ciphertextOutput) public { + emit CiphertextOutputPublished(e3Id, ciphertextOutput); + } - // Emit PlaintextOutputPublished event with passed test data - function emitPlaintextOutputPublished( - uint256 e3Id, - bytes memory plaintextOutput - ) public { - emit PlaintextOutputPublished(e3Id, plaintextOutput); - } + // Emit PlaintextOutputPublished event with passed test data + function emitPlaintextOutputPublished(uint256 e3Id, bytes memory plaintextOutput) public { + emit PlaintextOutputPublished(e3Id, plaintextOutput); + } - // Emit CommitteePublished event with passed test data - function emitCommitteePublished( - uint256 e3Id, - bytes memory publicKey - ) public { - emit CommitteePublished(e3Id, publicKey); - } + // Emit CommitteePublished event with passed test data + function emitCommitteePublished(uint256 e3Id, bytes memory publicKey) public { + emit CommitteePublished(e3Id, publicKey); + } - function getE3(uint256 _e3Id) external view returns (E3 memory e3) { - e3 = E3({ - seed: 123456789012, - threshold: [uint32(2), uint32(3)], - requestBlock: 18750000, - startWindow: [uint256(18750100), uint256(18750200)], - duration: 100, - expiration: block.timestamp + 1 days, - encryptionSchemeId: bytes32(keccak256("AES-256-GCM")), - e3Program: 0x7F3E4df648B8Cb96C1D343be976b91B97CaD5c21, - decryptionVerifier: 0x4B0D8c2E5f7a6c832f8b16d3aB0e7F5d9E9B24b1, - e3ProgramParams: abi.encode(42, "testParams"), - customParams: abi.encode("custom_params"), - committeePublicKey: bytes32(keccak256("committee_public_key")), - ciphertextOutput: bytes32(keccak256("encrypted_data")), - plaintextOutput: abi.encode("decrypted_result") - }); - } + function getE3(uint256 _e3Id) external view returns (E3 memory e3) { + e3 = E3({ + seed: 123456789012, + threshold: [uint32(2), uint32(3)], + requestBlock: 18750000, + startWindow: [uint256(18750100), uint256(18750200)], + duration: 100, + expiration: block.timestamp + 1 days, + encryptionSchemeId: bytes32(keccak256("AES-256-GCM")), + e3Program: 0x7F3E4df648B8Cb96C1D343be976b91B97CaD5c21, + decryptionVerifier: 0x4B0D8c2E5f7a6c832f8b16d3aB0e7F5d9E9B24b1, + e3ProgramParams: abi.encode(42, "testParams"), + customParams: abi.encode("custom_params"), + committeePublicKey: bytes32(keccak256("committee_public_key")), + ciphertextOutput: bytes32(keccak256("encrypted_data")), + plaintextOutput: abi.encode("decrypted_result") + }); + } } struct E3 { - uint256 seed; - uint32[2] threshold; - uint256 requestBlock; - uint256[2] startWindow; - uint256 duration; - uint256 expiration; - bytes32 encryptionSchemeId; - address e3Program; - bytes e3ProgramParams; - bytes customParams; - address decryptionVerifier; - bytes32 committeePublicKey; - bytes32 ciphertextOutput; - bytes plaintextOutput; + uint256 seed; + uint32[2] threshold; + uint256 requestBlock; + uint256[2] startWindow; + uint256 duration; + uint256 expiration; + bytes32 encryptionSchemeId; + address e3Program; + bytes e3ProgramParams; + bytes customParams; + address decryptionVerifier; + bytes32 committeePublicKey; + bytes32 ciphertextOutput; + bytes plaintextOutput; } diff --git a/crates/support/contracts/ImageID.sol b/crates/support/contracts/ImageID.sol index 47d74034f6..c0f19e618d 100644 --- a/crates/support/contracts/ImageID.sol +++ b/crates/support/contracts/ImageID.sol @@ -19,5 +19,5 @@ pragma solidity ^0.8.20; library ImageID { - bytes32 public constant PROGRAM_ID = bytes32(0x3ec51be1b9917603f2c6c82dee266daf822c51dbdca293d491f0c6bdad620cbc); + bytes32 public constant PROGRAM_ID = bytes32(0x3ec51be1b9917603f2c6c82dee266daf822c51dbdca293d491f0c6bdad620cbc); } diff --git a/crates/support/tests/Elf.sol b/crates/support/tests/Elf.sol index 4647a56f75..e2a1e945cd 100644 --- a/crates/support/tests/Elf.sol +++ b/crates/support/tests/Elf.sol @@ -19,6 +19,6 @@ pragma solidity ^0.8.20; library Elf { - string public constant PROGRAM_PATH = - "/home/ace/main/gnosis/enclave/crates/support/target/riscv-guest/methods/guests/riscv32im-risc0-zkvm-elf/release/program.bin"; + string public constant PROGRAM_PATH = + "/home/ace/main/gnosis/enclave/crates/support/target/riscv-guest/methods/guests/riscv32im-risc0-zkvm-elf/release/program.bin"; } diff --git a/docs/pages/building-with-enclave.mdx b/docs/pages/building-with-enclave.mdx index e38be09eb4..ac40182aad 100644 --- a/docs/pages/building-with-enclave.mdx +++ b/docs/pages/building-with-enclave.mdx @@ -19,21 +19,21 @@ maintaining the security and privacy guarantees of the protocol. ```solidity contract Enclave { - // address of the Ciphernode registry. - CiphernodeRegistry public ciphernodeRegistry; + // address of the Ciphernode registry. + CiphernodeRegistry public ciphernodeRegistry; - // Mapping of allowed E3 Programs. - mapping(IE3Program e3Program => bool allowed) public e3Programs; + // Mapping of allowed E3 Programs. + mapping(IE3Program e3Program => bool allowed) public e3Programs; - // Mapping of E3s. - mapping(uint256 e3Id => E3 e3) public e3s; + // Mapping of E3s. + mapping(uint256 e3Id => E3 e3) public e3s; - // Mapping of input merkle trees. - mapping(uint256 e3Id => LeanIMTData imt) public inputs; + // Mapping of input merkle trees. + mapping(uint256 e3Id => LeanIMTData imt) public inputs; - // Mapping of enabled encryption schemes. - mapping(bytes32 encryptionSchemeId => IDecryptionVerifier decryptionVerifier) - public decryptionVerifiers; + // Mapping of enabled encryption schemes. + mapping(bytes32 encryptionSchemeId => IDecryptionVerifier decryptionVerifier) + public decryptionVerifiers; } ``` @@ -121,10 +121,7 @@ Your E3 Program must include logic that validates user inputs. When publishing a contracts will call the `validateInput()` function on your Program contract. ```solidity -function validateInput( - address sender, - bytes memory params - ) external returns (bytes memory input); +function validateInput(address sender, bytes memory params) external returns (bytes memory input); ``` At a minimum, this function should validate a proof that the given ciphertext is a valid encryption @@ -140,27 +137,13 @@ to the E3's public key. It is also recommended to bundle in proofs to validate: ### Key Events ```solidity -event E3Requested( - uint256 indexed e3Id, - address indexed requester, - address e3ProgramAddress -); - -event InputSubmitted( - uint256 indexed e3Id, - address indexed sender, - bytes32 inputHash -); - -event CiphertextOutputPublished( - uint256 indexed e3Id, - bytes32 ciphertextHash -); - -event PlaintextOutputPublished( - uint256 indexed e3Id, - bytes plaintext -); +event E3Requested(uint256 indexed e3Id, address indexed requester, address e3ProgramAddress); + +event InputSubmitted(uint256 indexed e3Id, address indexed sender, bytes32 inputHash); + +event CiphertextOutputPublished(uint256 indexed e3Id, bytes32 ciphertextHash); + +event PlaintextOutputPublished(uint256 indexed e3Id, bytes plaintext); ``` ### Result Publication Flow diff --git a/docs/pages/computation-flow.mdx b/docs/pages/computation-flow.mdx index 0ba600a3ae..6015c5e4c7 100644 --- a/docs/pages/computation-flow.mdx +++ b/docs/pages/computation-flow.mdx @@ -62,10 +62,7 @@ During this phase, Data Providers — who may include individual users, applicat Program. ```solidity - function validateInput( - address sender, - bytes memory data - ) external returns (bytes memory input); + function validateInput(address sender, bytes memory data) external returns (bytes memory input); ``` ### Phase 4: Execution @@ -80,9 +77,9 @@ publishes the encrypted output back to Enclave contract. ```solidity function publishCiphertextOutput( - uint256 e3Id, - bytes memory ciphertextOutput, - bytes memory proof + uint256 e3Id, + bytes memory ciphertextOutput, + bytes memory proof ) external returns (bool success); ``` diff --git a/docs/pages/write-e3-contract.mdx b/docs/pages/write-e3-contract.mdx index ccc4b2f131..0e09cbe04d 100644 --- a/docs/pages/write-e3-contract.mdx +++ b/docs/pages/write-e3-contract.mdx @@ -23,14 +23,12 @@ parameters when a new E3 instance is created and another to verify the output of #### `validate` ```solidity - function validate( - uint256 e3Id, - uint256 seed, - bytes calldata e3ProgramParams, - bytes calldata computeProviderParams - ) - external - returns (bytes32 encryptionSchemeId); +function validate( + uint256 e3Id, + uint256 seed, + bytes calldata e3ProgramParams, + bytes calldata computeProviderParams +) external returns (bytes32 encryptionSchemeId); ``` When a new instance of your E3 Program is requested, the `validate` function is called to validate @@ -51,11 +49,11 @@ or check out the #### `verify` ```solidity - function verify( - uint256 e3Id, - bytes32 ciphertextOutput, - bytes memory proof - ) external returns (bool success); +function verify( + uint256 e3Id, + bytes32 ciphertextOutput, + bytes memory proof +) external returns (bool success); ``` The verify function intakes the output of the E3 computation and the accompanying proof generated by @@ -81,9 +79,9 @@ your Data Provider. The function should be implemented in the E3 Program contrac pragma solidity >=0.8.27; function validateInput(address sender, bytes memory data) external returns (bytes memory input) { - // Decode the input data - // Verify associated ZKPs - // Return the validated input - return data; // Placeholder: replace with actual validated input + // Decode the input data + // Verify associated ZKPs + // Return the validated input + return data; // Placeholder: replace with actual validated input } ``` diff --git a/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol b/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol index 5aa0b0d41e..6032ace6b7 100644 --- a/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol +++ b/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol @@ -5,14 +5,14 @@ // or FITNESS FOR A PARTICULAR PURPOSE. pragma solidity >=0.8.27; -import { IRiscZeroVerifier } from 'risc0/IRiscZeroVerifier.sol'; -import { Ownable } from '@openzeppelin/contracts/access/Ownable.sol'; -import { IE3Program } from '@enclave-e3/contracts/contracts/interfaces/IE3Program.sol'; -import { IEnclave } from '@enclave-e3/contracts/contracts/interfaces/IEnclave.sol'; -import { E3 } from '@enclave-e3/contracts/contracts/interfaces/IE3.sol'; -import { LazyIMTData, InternalLazyIMT, PoseidonT3 } from '@zk-kit/lazy-imt.sol/InternalLazyIMT.sol'; +import { IRiscZeroVerifier } from "risc0/IRiscZeroVerifier.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { IE3Program } from "@enclave-e3/contracts/contracts/interfaces/IE3Program.sol"; +import { IEnclave } from "@enclave-e3/contracts/contracts/interfaces/IEnclave.sol"; +import { E3 } from "@enclave-e3/contracts/contracts/interfaces/IE3.sol"; +import { LazyIMTData, InternalLazyIMT, PoseidonT3 } from "@zk-kit/lazy-imt.sol/InternalLazyIMT.sol"; -import { HonkVerifier } from './CRISPVerifier.sol'; +import { HonkVerifier } from "./CRISPVerifier.sol"; contract CRISPProgram is IE3Program, Ownable { using InternalLazyIMT for LazyIMTData; @@ -27,7 +27,7 @@ contract CRISPProgram is IE3Program, Ownable { } // Constants - bytes32 public constant ENCRYPTION_SCHEME_ID = keccak256('fhe.rs:BFV'); + bytes32 public constant ENCRYPTION_SCHEME_ID = keccak256("fhe.rs:BFV"); // The depth of the input merkle tree uint8 public constant TREE_DEPTH = 20; diff --git a/examples/CRISP/packages/crisp-contracts/contracts/CRISPVerifier.sol b/examples/CRISP/packages/crisp-contracts/contracts/CRISPVerifier.sol index c9a1507a90..d25b1ebddb 100644 --- a/examples/CRISP/packages/crisp-contracts/contracts/CRISPVerifier.sol +++ b/examples/CRISP/packages/crisp-contracts/contracts/CRISPVerifier.sol @@ -10,143 +10,143 @@ uint256 constant LOG_N = 18; uint256 constant NUMBER_OF_PUBLIC_INPUTS = 18; uint256 constant VK_HASH = 0x273095662660d8e7d9b0ce77efc7b801201bcff5a4b6ae830ac2d29d35edf02f; library HonkVerificationKey { - function loadVerificationKey() internal pure returns (Honk.VerificationKey memory) { - Honk.VerificationKey memory vk = Honk.VerificationKey({ - circuitSize: uint256(262144), - logCircuitSize: uint256(18), - publicInputsSize: uint256(18), - ql: Honk.G1Point({ - x: uint256(0x0b3a15c09c6cd6378688fe32b8673e0e63276ac92c87d75806ead72c2fb07005), - y: uint256(0x15b8e272d10e42a25f36d375c46ae60177b4e3ee1d88ebc88b6991098ef6371d) - }), - qr: Honk.G1Point({ - x: uint256(0x032369e59250ab7fb450e91335e1cccaf129e728bdf0d7b1f2e548bc236ee153), - y: uint256(0x2636bcbd594d8913c25b82201b2ea949cacabfcc9e1e73274c19645ed86a8d5c) - }), - qo: Honk.G1Point({ - x: uint256(0x0a3e9646ab6c913816cc41600696c0ad5a7e007d69d850881cb657e4f97b300f), - y: uint256(0x021ee0bf065957355a71c109e6a7463da8035a033e073a084d446efc7114b172) - }), - q4: Honk.G1Point({ - x: uint256(0x297d10382efbe4a6ff54c5d55ce77e60ab23f3cc07ba671a307b85c30f19fc9c), - y: uint256(0x0de6167b10f95d114c504fbf83bf2dec971f65017592d094efc91938fbdc1c88) - }), - qm: Honk.G1Point({ - x: uint256(0x20b9440b84e5c5da4bf31110df15c5d9a75d8cf3fdc8befe64d3575ba77f27e5), - y: uint256(0x287bc752183bf8307a6edcda087ac6d6dbe9c2f9274a6ff6db9a164363723d4c) - }), - qc: Honk.G1Point({ - x: uint256(0x182b575a737c2f4b592e9892d4fca139715a2095d93a5e1ecb497f5d84ca9b1c), - y: uint256(0x117fbfa96346028947c5c7f52a5de5f3744db93bc25a8988edee3856cc259568) - }), - qLookup: Honk.G1Point({ - x: uint256(0x21a54a89cbeebd91bcfe99fc3628c8d7f91853e51a99c70416534381bbee5dbe), - y: uint256(0x11c15c3410c26e292aafa7edec4cad982fc7d2008e2bf6c7bbc0ef98ce0f409d) - }), - qArith: Honk.G1Point({ - x: uint256(0x05b80c4f04232708ffc0db7d0ffd8b8f2b5461739e06a19a0901842eafbd9876), - y: uint256(0x09cd299a5bfddc489a0de8b10629945114344273b52027da3c5add71ab1a3ef7) - }), - qDeltaRange: Honk.G1Point({ - x: uint256(0x29ccace19d83cbafaef5257f1da6a853de5d385e843dc7f489b7ebbbbf9998e1), - y: uint256(0x15d2bd9b2e462544616393a763900e60c078404592c8b1290d026d9f4dbe8c91) - }), - qElliptic: Honk.G1Point({ - x: uint256(0x1c62de2699b515145d55307dc7a6aa617babd5dc82a3867ccd41e064ef392cbf), - y: uint256(0x12bd3f8a5925134309fc95127b0930b67d0da3db65645543d10d908ce3a76ebe) - }), - qMemory: Honk.G1Point({ - x: uint256(0x11ebbe2ae222656502a4041e9f9cac9ee8cb394939cf15a7fa6ab3b84493045b), - y: uint256(0x132a663274fb6148286277f2cfffe4755ad8c3bcbe9c7b984eab093a63b93dd1) - }), - qNnf: Honk.G1Point({ - x: uint256(0x22c29d34b23cb31e40452b366d1cb8cb28c8705bcc3a1b13f0c6712e82412209), - y: uint256(0x0961c137fe7d5b085c93bbf88f7cb6aa455bf1f43f33b84eac2516f05bba8a10) - }), - qPoseidon2External: Honk.G1Point({ - x: uint256(0x0a2f3d441e72b4ba47a52af7907a50e133d0000e0408f73824c5f4c16dfd266b), - y: uint256(0x126a45b02f1b1fd6d1263a8b95880d32eb8263fa065fef17b468c146e1104472) - }), - qPoseidon2Internal: Honk.G1Point({ - x: uint256(0x29aa901a312bbd744e23a9d6b4cb03094b68712e8b2103dd0f0bc1a3734429c1), - y: uint256(0x05865867225863a5bb2453e83decacc0c8a560343f704ba1ca729072c96c1324) - }), - s1: Honk.G1Point({ - x: uint256(0x151641064b3fd0000962f179efd1449709d3c835fdc4eb414f09d467e464539f), - y: uint256(0x0ffbe512ea8787aa98ec88f6f2594cc8f5063fda900fd1d1751ad8cd5291e8da) - }), - s2: Honk.G1Point({ - x: uint256(0x08bc59b7fda430b06b6f3c3f23b8a94cdb7efe679f890589eb8f8ca71953f7d3), - y: uint256(0x28da1f216c3e8f18e533d0549b77d203c34fbb2844e58852c7eebb146d351f2b) - }), - s3: Honk.G1Point({ - x: uint256(0x0f16b1d24b7e7a3077602aebe916195a1cd6c27950ffbd2a299e44f9c048dc85), - y: uint256(0x061ad2f712c27084f5c4d236e98fd56662e008f4d8ac47df80eff72a01cacc63) - }), - s4: Honk.G1Point({ - x: uint256(0x2c5af630072ab262a4c336635fa9e819bc618898b4d21985a4158a62ee0788e3), - y: uint256(0x191fdaa41a68c92ead63b0fec59f9203bf7a1e91f546893e2b4a53447a55569e) - }), - t1: Honk.G1Point({ - x: uint256(0x08a5ba822823e5f21f5585f7d90f070aaad388561d817362c819850cccf82580), - y: uint256(0x2d296fb3ec6c283d6f822a7e7f9edbe350516a4f9cba53be9dc8ac6240d0559c) - }), - t2: Honk.G1Point({ - x: uint256(0x201b4ffc4068dd22cc3a99a1ef5bc10e2be7841ed934ad5ea5247f992687c29b), - y: uint256(0x28351d4eacb149a545035052b1b2081b7e8c3ffa751c5bc31483b653f95cb6ca) - }), - t3: Honk.G1Point({ - x: uint256(0x0d1a271b6b84d9a2d8953885c3b2d13d10aa96a483eeb4c7a41d65c19d69d638), - y: uint256(0x2a40aaa4bc03f75cbc60cc97a07b3e8885d4c99101b026f18219c82ee71443c4) - }), - t4: Honk.G1Point({ - x: uint256(0x18216d5e69c40817c81feefd02de1aa548f7bf9d9ce4d671e96b22f368709ed5), - y: uint256(0x1e5e5f5acbdcd05a0ebffacea7a5426da9ec26a79cbb95692c9e9a499ff0155a) - }), - id1: Honk.G1Point({ - x: uint256(0x0c2f70f20e2de95b0f6ec1490826bdfbd2cf0a15b121b82cd761199e75d8eab0), - y: uint256(0x147cc6cf140d4150b2504450a6bc8bb9fef44a0d5ad9d7e2a2b602400991540a) - }), - id2: Honk.G1Point({ - x: uint256(0x26d35ecb74bec60c821cbc20a4caa6ad12a00e7cec5c517f4cc80bf546ea8c2f), - y: uint256(0x1326cdc051e2a50a81daa188d5d2703aa2c2a76ae78ebd6b72976aca02f5311c) - }), - id3: Honk.G1Point({ - x: uint256(0x2b255d2428986d981d1d957f952f228a28d1de61f1b75e9632f1be401a07c51f), - y: uint256(0x242e388f0507d45d6c79df71089aca604efbbf70ea96b83bd145365925549cde) - }), - id4: Honk.G1Point({ - x: uint256(0x0fc7ce8073e35452bc4dd7bfea2a4f6225868c116152a46ef536525e00221bcf), - y: uint256(0x2a3c706ce6c39aef7bd6466af3bb2d7ee1744f3f04c459a854ba79eefde15663) - }), - lagrangeFirst: Honk.G1Point({ - x: uint256(0x0000000000000000000000000000000000000000000000000000000000000001), - y: uint256(0x0000000000000000000000000000000000000000000000000000000000000002) - }), - lagrangeLast: Honk.G1Point({ - x: uint256(0x19f021f91bfb225362bce9967a6cfa705efac1e76aa85106ad0a63952b30d51d), - y: uint256(0x2d0f619e640b4147046d1cb69a2ee61d683752dbde8a2e30b5073d16de5f33d0) - }) - }); - return vk; - } + function loadVerificationKey() internal pure returns (Honk.VerificationKey memory) { + Honk.VerificationKey memory vk = Honk.VerificationKey({ + circuitSize: uint256(262144), + logCircuitSize: uint256(18), + publicInputsSize: uint256(18), + ql: Honk.G1Point({ + x: uint256(0x0b3a15c09c6cd6378688fe32b8673e0e63276ac92c87d75806ead72c2fb07005), + y: uint256(0x15b8e272d10e42a25f36d375c46ae60177b4e3ee1d88ebc88b6991098ef6371d) + }), + qr: Honk.G1Point({ + x: uint256(0x032369e59250ab7fb450e91335e1cccaf129e728bdf0d7b1f2e548bc236ee153), + y: uint256(0x2636bcbd594d8913c25b82201b2ea949cacabfcc9e1e73274c19645ed86a8d5c) + }), + qo: Honk.G1Point({ + x: uint256(0x0a3e9646ab6c913816cc41600696c0ad5a7e007d69d850881cb657e4f97b300f), + y: uint256(0x021ee0bf065957355a71c109e6a7463da8035a033e073a084d446efc7114b172) + }), + q4: Honk.G1Point({ + x: uint256(0x297d10382efbe4a6ff54c5d55ce77e60ab23f3cc07ba671a307b85c30f19fc9c), + y: uint256(0x0de6167b10f95d114c504fbf83bf2dec971f65017592d094efc91938fbdc1c88) + }), + qm: Honk.G1Point({ + x: uint256(0x20b9440b84e5c5da4bf31110df15c5d9a75d8cf3fdc8befe64d3575ba77f27e5), + y: uint256(0x287bc752183bf8307a6edcda087ac6d6dbe9c2f9274a6ff6db9a164363723d4c) + }), + qc: Honk.G1Point({ + x: uint256(0x182b575a737c2f4b592e9892d4fca139715a2095d93a5e1ecb497f5d84ca9b1c), + y: uint256(0x117fbfa96346028947c5c7f52a5de5f3744db93bc25a8988edee3856cc259568) + }), + qLookup: Honk.G1Point({ + x: uint256(0x21a54a89cbeebd91bcfe99fc3628c8d7f91853e51a99c70416534381bbee5dbe), + y: uint256(0x11c15c3410c26e292aafa7edec4cad982fc7d2008e2bf6c7bbc0ef98ce0f409d) + }), + qArith: Honk.G1Point({ + x: uint256(0x05b80c4f04232708ffc0db7d0ffd8b8f2b5461739e06a19a0901842eafbd9876), + y: uint256(0x09cd299a5bfddc489a0de8b10629945114344273b52027da3c5add71ab1a3ef7) + }), + qDeltaRange: Honk.G1Point({ + x: uint256(0x29ccace19d83cbafaef5257f1da6a853de5d385e843dc7f489b7ebbbbf9998e1), + y: uint256(0x15d2bd9b2e462544616393a763900e60c078404592c8b1290d026d9f4dbe8c91) + }), + qElliptic: Honk.G1Point({ + x: uint256(0x1c62de2699b515145d55307dc7a6aa617babd5dc82a3867ccd41e064ef392cbf), + y: uint256(0x12bd3f8a5925134309fc95127b0930b67d0da3db65645543d10d908ce3a76ebe) + }), + qMemory: Honk.G1Point({ + x: uint256(0x11ebbe2ae222656502a4041e9f9cac9ee8cb394939cf15a7fa6ab3b84493045b), + y: uint256(0x132a663274fb6148286277f2cfffe4755ad8c3bcbe9c7b984eab093a63b93dd1) + }), + qNnf: Honk.G1Point({ + x: uint256(0x22c29d34b23cb31e40452b366d1cb8cb28c8705bcc3a1b13f0c6712e82412209), + y: uint256(0x0961c137fe7d5b085c93bbf88f7cb6aa455bf1f43f33b84eac2516f05bba8a10) + }), + qPoseidon2External: Honk.G1Point({ + x: uint256(0x0a2f3d441e72b4ba47a52af7907a50e133d0000e0408f73824c5f4c16dfd266b), + y: uint256(0x126a45b02f1b1fd6d1263a8b95880d32eb8263fa065fef17b468c146e1104472) + }), + qPoseidon2Internal: Honk.G1Point({ + x: uint256(0x29aa901a312bbd744e23a9d6b4cb03094b68712e8b2103dd0f0bc1a3734429c1), + y: uint256(0x05865867225863a5bb2453e83decacc0c8a560343f704ba1ca729072c96c1324) + }), + s1: Honk.G1Point({ + x: uint256(0x151641064b3fd0000962f179efd1449709d3c835fdc4eb414f09d467e464539f), + y: uint256(0x0ffbe512ea8787aa98ec88f6f2594cc8f5063fda900fd1d1751ad8cd5291e8da) + }), + s2: Honk.G1Point({ + x: uint256(0x08bc59b7fda430b06b6f3c3f23b8a94cdb7efe679f890589eb8f8ca71953f7d3), + y: uint256(0x28da1f216c3e8f18e533d0549b77d203c34fbb2844e58852c7eebb146d351f2b) + }), + s3: Honk.G1Point({ + x: uint256(0x0f16b1d24b7e7a3077602aebe916195a1cd6c27950ffbd2a299e44f9c048dc85), + y: uint256(0x061ad2f712c27084f5c4d236e98fd56662e008f4d8ac47df80eff72a01cacc63) + }), + s4: Honk.G1Point({ + x: uint256(0x2c5af630072ab262a4c336635fa9e819bc618898b4d21985a4158a62ee0788e3), + y: uint256(0x191fdaa41a68c92ead63b0fec59f9203bf7a1e91f546893e2b4a53447a55569e) + }), + t1: Honk.G1Point({ + x: uint256(0x08a5ba822823e5f21f5585f7d90f070aaad388561d817362c819850cccf82580), + y: uint256(0x2d296fb3ec6c283d6f822a7e7f9edbe350516a4f9cba53be9dc8ac6240d0559c) + }), + t2: Honk.G1Point({ + x: uint256(0x201b4ffc4068dd22cc3a99a1ef5bc10e2be7841ed934ad5ea5247f992687c29b), + y: uint256(0x28351d4eacb149a545035052b1b2081b7e8c3ffa751c5bc31483b653f95cb6ca) + }), + t3: Honk.G1Point({ + x: uint256(0x0d1a271b6b84d9a2d8953885c3b2d13d10aa96a483eeb4c7a41d65c19d69d638), + y: uint256(0x2a40aaa4bc03f75cbc60cc97a07b3e8885d4c99101b026f18219c82ee71443c4) + }), + t4: Honk.G1Point({ + x: uint256(0x18216d5e69c40817c81feefd02de1aa548f7bf9d9ce4d671e96b22f368709ed5), + y: uint256(0x1e5e5f5acbdcd05a0ebffacea7a5426da9ec26a79cbb95692c9e9a499ff0155a) + }), + id1: Honk.G1Point({ + x: uint256(0x0c2f70f20e2de95b0f6ec1490826bdfbd2cf0a15b121b82cd761199e75d8eab0), + y: uint256(0x147cc6cf140d4150b2504450a6bc8bb9fef44a0d5ad9d7e2a2b602400991540a) + }), + id2: Honk.G1Point({ + x: uint256(0x26d35ecb74bec60c821cbc20a4caa6ad12a00e7cec5c517f4cc80bf546ea8c2f), + y: uint256(0x1326cdc051e2a50a81daa188d5d2703aa2c2a76ae78ebd6b72976aca02f5311c) + }), + id3: Honk.G1Point({ + x: uint256(0x2b255d2428986d981d1d957f952f228a28d1de61f1b75e9632f1be401a07c51f), + y: uint256(0x242e388f0507d45d6c79df71089aca604efbbf70ea96b83bd145365925549cde) + }), + id4: Honk.G1Point({ + x: uint256(0x0fc7ce8073e35452bc4dd7bfea2a4f6225868c116152a46ef536525e00221bcf), + y: uint256(0x2a3c706ce6c39aef7bd6466af3bb2d7ee1744f3f04c459a854ba79eefde15663) + }), + lagrangeFirst: Honk.G1Point({ + x: uint256(0x0000000000000000000000000000000000000000000000000000000000000001), + y: uint256(0x0000000000000000000000000000000000000000000000000000000000000002) + }), + lagrangeLast: Honk.G1Point({ + x: uint256(0x19f021f91bfb225362bce9967a6cfa705efac1e76aa85106ad0a63952b30d51d), + y: uint256(0x2d0f619e640b4147046d1cb69a2ee61d683752dbde8a2e30b5073d16de5f33d0) + }) + }); + return vk; + } } pragma solidity ^0.8.27; interface IVerifier { - function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external returns (bool); + function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external returns (bool); } type Fr is uint256; -using {add as +} for Fr global; -using {sub as -} for Fr global; -using {mul as *} for Fr global; +using { add as + } for Fr global; +using { sub as - } for Fr global; +using { mul as * } for Fr global; -using {exp as ^} for Fr global; -using {notEqual as !=} for Fr global; -using {equal as ==} for Fr global; +using { exp as ^ } for Fr global; +using { notEqual as != } for Fr global; +using { equal as == } for Fr global; uint256 constant SUBGROUP_SIZE = 256; uint256 constant MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617; // Prime field order @@ -159,135 +159,135 @@ Fr constant ZERO = Fr.wrap(0); // Instantiation library FrLib { - function from(uint256 value) internal pure returns (Fr) { - unchecked { - return Fr.wrap(value % MODULUS); - } - } - - function fromBytes32(bytes32 value) internal pure returns (Fr) { - unchecked { - return Fr.wrap(uint256(value) % MODULUS); - } - } - - function toBytes32(Fr value) internal pure returns (bytes32) { - unchecked { - return bytes32(Fr.unwrap(value)); - } - } - - function invert(Fr value) internal view returns (Fr) { - uint256 v = Fr.unwrap(value); - uint256 result; - - // Call the modexp precompile to invert in the field - assembly { - let free := mload(0x40) - mstore(free, 0x20) - mstore(add(free, 0x20), 0x20) - mstore(add(free, 0x40), 0x20) - mstore(add(free, 0x60), v) - mstore(add(free, 0x80), sub(MODULUS, 2)) - mstore(add(free, 0xa0), MODULUS) - let success := staticcall(gas(), 0x05, free, 0xc0, 0x00, 0x20) - if iszero(success) { - revert(0, 0) - } - result := mload(0x00) - mstore(0x40, add(free, 0x80)) - } - - return Fr.wrap(result); - } - - function pow(Fr base, uint256 v) internal view returns (Fr) { - uint256 b = Fr.unwrap(base); - uint256 result; - - // Call the modexp precompile to invert in the field - assembly { - let free := mload(0x40) - mstore(free, 0x20) - mstore(add(free, 0x20), 0x20) - mstore(add(free, 0x40), 0x20) - mstore(add(free, 0x60), b) - mstore(add(free, 0x80), v) - mstore(add(free, 0xa0), MODULUS) - let success := staticcall(gas(), 0x05, free, 0xc0, 0x00, 0x20) - if iszero(success) { - revert(0, 0) - } - result := mload(0x00) - mstore(0x40, add(free, 0x80)) - } - - return Fr.wrap(result); - } - - function div(Fr numerator, Fr denominator) internal view returns (Fr) { - unchecked { - return numerator * invert(denominator); - } - } - - function sqr(Fr value) internal pure returns (Fr) { - unchecked { - return value * value; - } - } - - function unwrap(Fr value) internal pure returns (uint256) { - unchecked { - return Fr.unwrap(value); - } - } - - function neg(Fr value) internal pure returns (Fr) { - unchecked { - return Fr.wrap(MODULUS - Fr.unwrap(value)); - } + function from(uint256 value) internal pure returns (Fr) { + unchecked { + return Fr.wrap(value % MODULUS); + } + } + + function fromBytes32(bytes32 value) internal pure returns (Fr) { + unchecked { + return Fr.wrap(uint256(value) % MODULUS); + } + } + + function toBytes32(Fr value) internal pure returns (bytes32) { + unchecked { + return bytes32(Fr.unwrap(value)); + } + } + + function invert(Fr value) internal view returns (Fr) { + uint256 v = Fr.unwrap(value); + uint256 result; + + // Call the modexp precompile to invert in the field + assembly { + let free := mload(0x40) + mstore(free, 0x20) + mstore(add(free, 0x20), 0x20) + mstore(add(free, 0x40), 0x20) + mstore(add(free, 0x60), v) + mstore(add(free, 0x80), sub(MODULUS, 2)) + mstore(add(free, 0xa0), MODULUS) + let success := staticcall(gas(), 0x05, free, 0xc0, 0x00, 0x20) + if iszero(success) { + revert(0, 0) + } + result := mload(0x00) + mstore(0x40, add(free, 0x80)) + } + + return Fr.wrap(result); + } + + function pow(Fr base, uint256 v) internal view returns (Fr) { + uint256 b = Fr.unwrap(base); + uint256 result; + + // Call the modexp precompile to invert in the field + assembly { + let free := mload(0x40) + mstore(free, 0x20) + mstore(add(free, 0x20), 0x20) + mstore(add(free, 0x40), 0x20) + mstore(add(free, 0x60), b) + mstore(add(free, 0x80), v) + mstore(add(free, 0xa0), MODULUS) + let success := staticcall(gas(), 0x05, free, 0xc0, 0x00, 0x20) + if iszero(success) { + revert(0, 0) + } + result := mload(0x00) + mstore(0x40, add(free, 0x80)) + } + + return Fr.wrap(result); + } + + function div(Fr numerator, Fr denominator) internal view returns (Fr) { + unchecked { + return numerator * invert(denominator); } + } + + function sqr(Fr value) internal pure returns (Fr) { + unchecked { + return value * value; + } + } + + function unwrap(Fr value) internal pure returns (uint256) { + unchecked { + return Fr.unwrap(value); + } + } + + function neg(Fr value) internal pure returns (Fr) { + unchecked { + return Fr.wrap(MODULUS - Fr.unwrap(value)); + } + } } // Free functions function add(Fr a, Fr b) pure returns (Fr) { - unchecked { - return Fr.wrap(addmod(Fr.unwrap(a), Fr.unwrap(b), MODULUS)); - } + unchecked { + return Fr.wrap(addmod(Fr.unwrap(a), Fr.unwrap(b), MODULUS)); + } } function mul(Fr a, Fr b) pure returns (Fr) { - unchecked { - return Fr.wrap(mulmod(Fr.unwrap(a), Fr.unwrap(b), MODULUS)); - } + unchecked { + return Fr.wrap(mulmod(Fr.unwrap(a), Fr.unwrap(b), MODULUS)); + } } function sub(Fr a, Fr b) pure returns (Fr) { - unchecked { - return Fr.wrap(addmod(Fr.unwrap(a), MODULUS - Fr.unwrap(b), MODULUS)); - } + unchecked { + return Fr.wrap(addmod(Fr.unwrap(a), MODULUS - Fr.unwrap(b), MODULUS)); + } } function exp(Fr base, Fr exponent) pure returns (Fr) { - if (Fr.unwrap(exponent) == 0) return Fr.wrap(1); - // Implement exponent with a loop as we will overflow otherwise - for (uint256 i = 1; i < Fr.unwrap(exponent); i += i) { - base = base * base; - } - return base; + if (Fr.unwrap(exponent) == 0) return Fr.wrap(1); + // Implement exponent with a loop as we will overflow otherwise + for (uint256 i = 1; i < Fr.unwrap(exponent); i += i) { + base = base * base; + } + return base; } function notEqual(Fr a, Fr b) pure returns (bool) { - unchecked { - return Fr.unwrap(a) != Fr.unwrap(b); - } + unchecked { + return Fr.unwrap(a) != Fr.unwrap(b); + } } function equal(Fr a, Fr b) pure returns (bool) { - unchecked { - return Fr.unwrap(a) == Fr.unwrap(b); - } + unchecked { + return Fr.unwrap(a) == Fr.unwrap(b); + } } uint256 constant CONST_PROOF_SIZE_LOG_N = 28; @@ -308,1332 +308,1325 @@ uint256 constant NUMBER_OF_ALPHAS = NUMBER_OF_SUBRELATIONS - 1; // ENUM FOR WIRES enum WIRE { - Q_M, - Q_C, - Q_L, - Q_R, - Q_O, - Q_4, - Q_LOOKUP, - Q_ARITH, - Q_RANGE, - Q_ELLIPTIC, - Q_MEMORY, - Q_NNF, - Q_POSEIDON2_EXTERNAL, - Q_POSEIDON2_INTERNAL, - SIGMA_1, - SIGMA_2, - SIGMA_3, - SIGMA_4, - ID_1, - ID_2, - ID_3, - ID_4, - TABLE_1, - TABLE_2, - TABLE_3, - TABLE_4, - LAGRANGE_FIRST, - LAGRANGE_LAST, - W_L, - W_R, - W_O, - W_4, - Z_PERM, - LOOKUP_INVERSES, - LOOKUP_READ_COUNTS, - LOOKUP_READ_TAGS, - W_L_SHIFT, - W_R_SHIFT, - W_O_SHIFT, - W_4_SHIFT, - Z_PERM_SHIFT + Q_M, + Q_C, + Q_L, + Q_R, + Q_O, + Q_4, + Q_LOOKUP, + Q_ARITH, + Q_RANGE, + Q_ELLIPTIC, + Q_MEMORY, + Q_NNF, + Q_POSEIDON2_EXTERNAL, + Q_POSEIDON2_INTERNAL, + SIGMA_1, + SIGMA_2, + SIGMA_3, + SIGMA_4, + ID_1, + ID_2, + ID_3, + ID_4, + TABLE_1, + TABLE_2, + TABLE_3, + TABLE_4, + LAGRANGE_FIRST, + LAGRANGE_LAST, + W_L, + W_R, + W_O, + W_4, + Z_PERM, + LOOKUP_INVERSES, + LOOKUP_READ_COUNTS, + LOOKUP_READ_TAGS, + W_L_SHIFT, + W_R_SHIFT, + W_O_SHIFT, + W_4_SHIFT, + Z_PERM_SHIFT } library Honk { - struct G1Point { - uint256 x; - uint256 y; - } - - struct VerificationKey { - // Misc Params - uint256 circuitSize; - uint256 logCircuitSize; - uint256 publicInputsSize; - // Selectors - G1Point qm; - G1Point qc; - G1Point ql; - G1Point qr; - G1Point qo; - G1Point q4; - G1Point qLookup; // Lookup - G1Point qArith; // Arithmetic widget - G1Point qDeltaRange; // Delta Range sort - G1Point qMemory; // Memory - G1Point qNnf; // Non-native Field - G1Point qElliptic; // Auxillary - G1Point qPoseidon2External; - G1Point qPoseidon2Internal; - // Copy cnstraints - G1Point s1; - G1Point s2; - G1Point s3; - G1Point s4; - // Copy identity - G1Point id1; - G1Point id2; - G1Point id3; - G1Point id4; - // Precomputed lookup table - G1Point t1; - G1Point t2; - G1Point t3; - G1Point t4; - // Fixed first and last - G1Point lagrangeFirst; - G1Point lagrangeLast; - } - - struct RelationParameters { - // challenges - Fr eta; - Fr etaTwo; - Fr etaThree; - Fr beta; - Fr gamma; - // derived - Fr publicInputsDelta; - } - - struct Proof { - // Pairing point object - Fr[PAIRING_POINTS_SIZE] pairingPointObject; - // Free wires - G1Point w1; - G1Point w2; - G1Point w3; - G1Point w4; - // Lookup helpers - Permutations - G1Point zPerm; - // Lookup helpers - logup - G1Point lookupReadCounts; - G1Point lookupReadTags; - G1Point lookupInverses; - // Sumcheck - Fr[BATCHED_RELATION_PARTIAL_LENGTH][CONST_PROOF_SIZE_LOG_N] sumcheckUnivariates; - Fr[NUMBER_OF_ENTITIES] sumcheckEvaluations; - // Shplemini - G1Point[CONST_PROOF_SIZE_LOG_N - 1] geminiFoldComms; - Fr[CONST_PROOF_SIZE_LOG_N] geminiAEvaluations; - G1Point shplonkQ; - G1Point kzgQuotient; - } - - struct ZKProof { - // Pairing point object - Fr[PAIRING_POINTS_SIZE] pairingPointObject; - // Commitments to wire polynomials - G1Point w1; - G1Point w2; - G1Point w3; - G1Point w4; - // Commitments to logup witness polynomials - G1Point lookupReadCounts; - G1Point lookupReadTags; - G1Point lookupInverses; - // Commitment to grand permutation polynomial - G1Point zPerm; - G1Point[3] libraCommitments; - // Sumcheck - Fr libraSum; - Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH][CONST_PROOF_SIZE_LOG_N] sumcheckUnivariates; - Fr[NUMBER_OF_ENTITIES] sumcheckEvaluations; - Fr libraEvaluation; - // ZK - G1Point geminiMaskingPoly; - Fr geminiMaskingEval; - // Shplemini - G1Point[CONST_PROOF_SIZE_LOG_N - 1] geminiFoldComms; - Fr[CONST_PROOF_SIZE_LOG_N] geminiAEvaluations; - Fr[4] libraPolyEvals; - G1Point shplonkQ; - G1Point kzgQuotient; - } + struct G1Point { + uint256 x; + uint256 y; + } + + struct VerificationKey { + // Misc Params + uint256 circuitSize; + uint256 logCircuitSize; + uint256 publicInputsSize; + // Selectors + G1Point qm; + G1Point qc; + G1Point ql; + G1Point qr; + G1Point qo; + G1Point q4; + G1Point qLookup; // Lookup + G1Point qArith; // Arithmetic widget + G1Point qDeltaRange; // Delta Range sort + G1Point qMemory; // Memory + G1Point qNnf; // Non-native Field + G1Point qElliptic; // Auxillary + G1Point qPoseidon2External; + G1Point qPoseidon2Internal; + // Copy cnstraints + G1Point s1; + G1Point s2; + G1Point s3; + G1Point s4; + // Copy identity + G1Point id1; + G1Point id2; + G1Point id3; + G1Point id4; + // Precomputed lookup table + G1Point t1; + G1Point t2; + G1Point t3; + G1Point t4; + // Fixed first and last + G1Point lagrangeFirst; + G1Point lagrangeLast; + } + + struct RelationParameters { + // challenges + Fr eta; + Fr etaTwo; + Fr etaThree; + Fr beta; + Fr gamma; + // derived + Fr publicInputsDelta; + } + + struct Proof { + // Pairing point object + Fr[PAIRING_POINTS_SIZE] pairingPointObject; + // Free wires + G1Point w1; + G1Point w2; + G1Point w3; + G1Point w4; + // Lookup helpers - Permutations + G1Point zPerm; + // Lookup helpers - logup + G1Point lookupReadCounts; + G1Point lookupReadTags; + G1Point lookupInverses; + // Sumcheck + Fr[BATCHED_RELATION_PARTIAL_LENGTH][CONST_PROOF_SIZE_LOG_N] sumcheckUnivariates; + Fr[NUMBER_OF_ENTITIES] sumcheckEvaluations; + // Shplemini + G1Point[CONST_PROOF_SIZE_LOG_N - 1] geminiFoldComms; + Fr[CONST_PROOF_SIZE_LOG_N] geminiAEvaluations; + G1Point shplonkQ; + G1Point kzgQuotient; + } + + struct ZKProof { + // Pairing point object + Fr[PAIRING_POINTS_SIZE] pairingPointObject; + // Commitments to wire polynomials + G1Point w1; + G1Point w2; + G1Point w3; + G1Point w4; + // Commitments to logup witness polynomials + G1Point lookupReadCounts; + G1Point lookupReadTags; + G1Point lookupInverses; + // Commitment to grand permutation polynomial + G1Point zPerm; + G1Point[3] libraCommitments; + // Sumcheck + Fr libraSum; + Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH][CONST_PROOF_SIZE_LOG_N] sumcheckUnivariates; + Fr[NUMBER_OF_ENTITIES] sumcheckEvaluations; + Fr libraEvaluation; + // ZK + G1Point geminiMaskingPoly; + Fr geminiMaskingEval; + // Shplemini + G1Point[CONST_PROOF_SIZE_LOG_N - 1] geminiFoldComms; + Fr[CONST_PROOF_SIZE_LOG_N] geminiAEvaluations; + Fr[4] libraPolyEvals; + G1Point shplonkQ; + G1Point kzgQuotient; + } } // ZKTranscript library to generate fiat shamir challenges, the ZK transcript only differest struct ZKTranscript { - // Oink - Honk.RelationParameters relationParameters; - Fr[NUMBER_OF_ALPHAS] alphas; - Fr[CONST_PROOF_SIZE_LOG_N] gateChallenges; - // Sumcheck - Fr libraChallenge; - Fr[CONST_PROOF_SIZE_LOG_N] sumCheckUChallenges; - // Shplemini - Fr rho; - Fr geminiR; - Fr shplonkNu; - Fr shplonkZ; - // Derived - Fr publicInputsDelta; + // Oink + Honk.RelationParameters relationParameters; + Fr[NUMBER_OF_ALPHAS] alphas; + Fr[CONST_PROOF_SIZE_LOG_N] gateChallenges; + // Sumcheck + Fr libraChallenge; + Fr[CONST_PROOF_SIZE_LOG_N] sumCheckUChallenges; + // Shplemini + Fr rho; + Fr geminiR; + Fr shplonkNu; + Fr shplonkZ; + // Derived + Fr publicInputsDelta; } library ZKTranscriptLib { - function generateTranscript( - Honk.ZKProof memory proof, - bytes32[] calldata publicInputs, - uint256 vkHash, - uint256 publicInputsSize, - uint256 logN - ) external pure returns (ZKTranscript memory t) { - Fr previousChallenge; - (t.relationParameters, previousChallenge) = - generateRelationParametersChallenges(proof, publicInputs, vkHash, publicInputsSize, previousChallenge); - - (t.alphas, previousChallenge) = generateAlphaChallenges(previousChallenge, proof); - - (t.gateChallenges, previousChallenge) = generateGateChallenges(previousChallenge, logN); - (t.libraChallenge, previousChallenge) = generateLibraChallenge(previousChallenge, proof); - (t.sumCheckUChallenges, previousChallenge) = generateSumcheckChallenges(proof, previousChallenge, logN); - - (t.rho, previousChallenge) = generateRhoChallenge(proof, previousChallenge); - - (t.geminiR, previousChallenge) = generateGeminiRChallenge(proof, previousChallenge, logN); - - (t.shplonkNu, previousChallenge) = generateShplonkNuChallenge(proof, previousChallenge, logN); - - (t.shplonkZ, previousChallenge) = generateShplonkZChallenge(proof, previousChallenge); - return t; - } - - function splitChallenge(Fr challenge) internal pure returns (Fr first, Fr second) { - uint256 challengeU256 = uint256(Fr.unwrap(challenge)); - uint256 lo = challengeU256 & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - uint256 hi = challengeU256 >> 128; - first = FrLib.fromBytes32(bytes32(lo)); - second = FrLib.fromBytes32(bytes32(hi)); - } - - function generateRelationParametersChallenges( - Honk.ZKProof memory proof, - bytes32[] calldata publicInputs, - uint256 vkHash, - uint256 publicInputsSize, - Fr previousChallenge - ) internal pure returns (Honk.RelationParameters memory rp, Fr nextPreviousChallenge) { - (rp.eta, rp.etaTwo, rp.etaThree, previousChallenge) = - generateEtaChallenge(proof, publicInputs, vkHash, publicInputsSize); - - (rp.beta, rp.gamma, nextPreviousChallenge) = generateBetaAndGammaChallenges(previousChallenge, proof); - } - - function generateEtaChallenge( - Honk.ZKProof memory proof, - bytes32[] calldata publicInputs, - uint256 vkHash, - uint256 publicInputsSize - ) internal pure returns (Fr eta, Fr etaTwo, Fr etaThree, Fr previousChallenge) { - bytes32[] memory round0 = new bytes32[](1 + publicInputsSize + 6); - round0[0] = bytes32(vkHash); - - for (uint256 i = 0; i < publicInputsSize - PAIRING_POINTS_SIZE; i++) { - round0[1 + i] = bytes32(publicInputs[i]); - } - for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) { - round0[1 + publicInputsSize - PAIRING_POINTS_SIZE + i] = FrLib.toBytes32(proof.pairingPointObject[i]); - } - - // Create the first challenge - // Note: w4 is added to the challenge later on - round0[1 + publicInputsSize] = bytes32(proof.w1.x); - round0[1 + publicInputsSize + 1] = bytes32(proof.w1.y); - round0[1 + publicInputsSize + 2] = bytes32(proof.w2.x); - round0[1 + publicInputsSize + 3] = bytes32(proof.w2.y); - round0[1 + publicInputsSize + 4] = bytes32(proof.w3.x); - round0[1 + publicInputsSize + 5] = bytes32(proof.w3.y); - - previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(round0))); - (eta, etaTwo) = splitChallenge(previousChallenge); - previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(Fr.unwrap(previousChallenge)))); - - (etaThree,) = splitChallenge(previousChallenge); - } - - function generateBetaAndGammaChallenges(Fr previousChallenge, Honk.ZKProof memory proof) - internal - pure - returns (Fr beta, Fr gamma, Fr nextPreviousChallenge) + function generateTranscript( + Honk.ZKProof memory proof, + bytes32[] calldata publicInputs, + uint256 vkHash, + uint256 publicInputsSize, + uint256 logN + ) external pure returns (ZKTranscript memory t) { + Fr previousChallenge; + (t.relationParameters, previousChallenge) = generateRelationParametersChallenges( + proof, + publicInputs, + vkHash, + publicInputsSize, + previousChallenge + ); + + (t.alphas, previousChallenge) = generateAlphaChallenges(previousChallenge, proof); + + (t.gateChallenges, previousChallenge) = generateGateChallenges(previousChallenge, logN); + (t.libraChallenge, previousChallenge) = generateLibraChallenge(previousChallenge, proof); + (t.sumCheckUChallenges, previousChallenge) = generateSumcheckChallenges(proof, previousChallenge, logN); + + (t.rho, previousChallenge) = generateRhoChallenge(proof, previousChallenge); + + (t.geminiR, previousChallenge) = generateGeminiRChallenge(proof, previousChallenge, logN); + + (t.shplonkNu, previousChallenge) = generateShplonkNuChallenge(proof, previousChallenge, logN); + + (t.shplonkZ, previousChallenge) = generateShplonkZChallenge(proof, previousChallenge); + return t; + } + + function splitChallenge(Fr challenge) internal pure returns (Fr first, Fr second) { + uint256 challengeU256 = uint256(Fr.unwrap(challenge)); + uint256 lo = challengeU256 & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + uint256 hi = challengeU256 >> 128; + first = FrLib.fromBytes32(bytes32(lo)); + second = FrLib.fromBytes32(bytes32(hi)); + } + + function generateRelationParametersChallenges( + Honk.ZKProof memory proof, + bytes32[] calldata publicInputs, + uint256 vkHash, + uint256 publicInputsSize, + Fr previousChallenge + ) internal pure returns (Honk.RelationParameters memory rp, Fr nextPreviousChallenge) { + (rp.eta, rp.etaTwo, rp.etaThree, previousChallenge) = generateEtaChallenge(proof, publicInputs, vkHash, publicInputsSize); + + (rp.beta, rp.gamma, nextPreviousChallenge) = generateBetaAndGammaChallenges(previousChallenge, proof); + } + + function generateEtaChallenge( + Honk.ZKProof memory proof, + bytes32[] calldata publicInputs, + uint256 vkHash, + uint256 publicInputsSize + ) internal pure returns (Fr eta, Fr etaTwo, Fr etaThree, Fr previousChallenge) { + bytes32[] memory round0 = new bytes32[](1 + publicInputsSize + 6); + round0[0] = bytes32(vkHash); + + for (uint256 i = 0; i < publicInputsSize - PAIRING_POINTS_SIZE; i++) { + round0[1 + i] = bytes32(publicInputs[i]); + } + for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) { + round0[1 + publicInputsSize - PAIRING_POINTS_SIZE + i] = FrLib.toBytes32(proof.pairingPointObject[i]); + } + + // Create the first challenge + // Note: w4 is added to the challenge later on + round0[1 + publicInputsSize] = bytes32(proof.w1.x); + round0[1 + publicInputsSize + 1] = bytes32(proof.w1.y); + round0[1 + publicInputsSize + 2] = bytes32(proof.w2.x); + round0[1 + publicInputsSize + 3] = bytes32(proof.w2.y); + round0[1 + publicInputsSize + 4] = bytes32(proof.w3.x); + round0[1 + publicInputsSize + 5] = bytes32(proof.w3.y); + + previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(round0))); + (eta, etaTwo) = splitChallenge(previousChallenge); + previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(Fr.unwrap(previousChallenge)))); + + (etaThree, ) = splitChallenge(previousChallenge); + } + + function generateBetaAndGammaChallenges( + Fr previousChallenge, + Honk.ZKProof memory proof + ) internal pure returns (Fr beta, Fr gamma, Fr nextPreviousChallenge) { + bytes32[7] memory round1; + round1[0] = FrLib.toBytes32(previousChallenge); + round1[1] = bytes32(proof.lookupReadCounts.x); + round1[2] = bytes32(proof.lookupReadCounts.y); + round1[3] = bytes32(proof.lookupReadTags.x); + round1[4] = bytes32(proof.lookupReadTags.y); + round1[5] = bytes32(proof.w4.x); + round1[6] = bytes32(proof.w4.y); + + nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(round1))); + (beta, gamma) = splitChallenge(nextPreviousChallenge); + } + + // Alpha challenges non-linearise the gate contributions + function generateAlphaChallenges( + Fr previousChallenge, + Honk.ZKProof memory proof + ) internal pure returns (Fr[NUMBER_OF_ALPHAS] memory alphas, Fr nextPreviousChallenge) { + // Generate the original sumcheck alpha 0 by hashing zPerm and zLookup + uint256[5] memory alpha0; + alpha0[0] = Fr.unwrap(previousChallenge); + alpha0[1] = proof.lookupInverses.x; + alpha0[2] = proof.lookupInverses.y; + alpha0[3] = proof.zPerm.x; + alpha0[4] = proof.zPerm.y; + + nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(alpha0))); + Fr alpha; + (alpha, ) = splitChallenge(nextPreviousChallenge); + + // Compute powers of alpha for batching subrelations + alphas[0] = alpha; + for (uint256 i = 1; i < NUMBER_OF_ALPHAS; i++) { + alphas[i] = alphas[i - 1] * alpha; + } + } + + function generateGateChallenges( + Fr previousChallenge, + uint256 logN + ) internal pure returns (Fr[CONST_PROOF_SIZE_LOG_N] memory gateChallenges, Fr nextPreviousChallenge) { + previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(Fr.unwrap(previousChallenge)))); + (gateChallenges[0], ) = splitChallenge(previousChallenge); + for (uint256 i = 1; i < logN; i++) { + gateChallenges[i] = gateChallenges[i - 1] * gateChallenges[i - 1]; + } + nextPreviousChallenge = previousChallenge; + } + + function generateLibraChallenge( + Fr previousChallenge, + Honk.ZKProof memory proof + ) internal pure returns (Fr libraChallenge, Fr nextPreviousChallenge) { + // 2 comm, 1 sum, 1 challenge + uint256[4] memory challengeData; + challengeData[0] = Fr.unwrap(previousChallenge); + challengeData[1] = proof.libraCommitments[0].x; + challengeData[2] = proof.libraCommitments[0].y; + challengeData[3] = Fr.unwrap(proof.libraSum); + nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(challengeData))); + (libraChallenge, ) = splitChallenge(nextPreviousChallenge); + } + + function generateSumcheckChallenges( + Honk.ZKProof memory proof, + Fr prevChallenge, + uint256 logN + ) internal pure returns (Fr[CONST_PROOF_SIZE_LOG_N] memory sumcheckChallenges, Fr nextPreviousChallenge) { + for (uint256 i = 0; i < logN; i++) { + Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH + 1] memory univariateChal; + univariateChal[0] = prevChallenge; + + for (uint256 j = 0; j < ZK_BATCHED_RELATION_PARTIAL_LENGTH; j++) { + univariateChal[j + 1] = proof.sumcheckUnivariates[i][j]; + } + prevChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(univariateChal))); + + (sumcheckChallenges[i], ) = splitChallenge(prevChallenge); + } + nextPreviousChallenge = prevChallenge; + } + + // We add Libra claimed eval + 3 comm + 1 more eval + function generateRhoChallenge(Honk.ZKProof memory proof, Fr prevChallenge) internal pure returns (Fr rho, Fr nextPreviousChallenge) { + uint256[NUMBER_OF_ENTITIES + 9] memory rhoChallengeElements; + rhoChallengeElements[0] = Fr.unwrap(prevChallenge); + uint256 i; + for (i = 1; i <= NUMBER_OF_ENTITIES; i++) { + rhoChallengeElements[i] = Fr.unwrap(proof.sumcheckEvaluations[i - 1]); + } + rhoChallengeElements[i] = Fr.unwrap(proof.libraEvaluation); + + i += 1; + rhoChallengeElements[i] = proof.libraCommitments[1].x; + rhoChallengeElements[i + 1] = proof.libraCommitments[1].y; + i += 2; + rhoChallengeElements[i] = proof.libraCommitments[2].x; + rhoChallengeElements[i + 1] = proof.libraCommitments[2].y; + i += 2; + rhoChallengeElements[i] = proof.geminiMaskingPoly.x; + rhoChallengeElements[i + 1] = proof.geminiMaskingPoly.y; + + i += 2; + rhoChallengeElements[i] = Fr.unwrap(proof.geminiMaskingEval); + + nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(rhoChallengeElements))); + (rho, ) = splitChallenge(nextPreviousChallenge); + } + + function generateGeminiRChallenge( + Honk.ZKProof memory proof, + Fr prevChallenge, + uint256 logN + ) internal pure returns (Fr geminiR, Fr nextPreviousChallenge) { + uint256[] memory gR = new uint256[]((logN - 1) * 2 + 1); + gR[0] = Fr.unwrap(prevChallenge); + + for (uint256 i = 0; i < logN - 1; i++) { + gR[1 + i * 2] = proof.geminiFoldComms[i].x; + gR[2 + i * 2] = proof.geminiFoldComms[i].y; + } + + nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(gR))); + + (geminiR, ) = splitChallenge(nextPreviousChallenge); + } + + function generateShplonkNuChallenge( + Honk.ZKProof memory proof, + Fr prevChallenge, + uint256 logN + ) internal pure returns (Fr shplonkNu, Fr nextPreviousChallenge) { + uint256[] memory shplonkNuChallengeElements = new uint256[](logN + 1 + 4); + shplonkNuChallengeElements[0] = Fr.unwrap(prevChallenge); + + for (uint256 i = 1; i <= logN; i++) { + shplonkNuChallengeElements[i] = Fr.unwrap(proof.geminiAEvaluations[i - 1]); + } + + uint256 libraIdx = 0; + for (uint256 i = logN + 1; i <= logN + 4; i++) { + shplonkNuChallengeElements[i] = Fr.unwrap(proof.libraPolyEvals[libraIdx]); + libraIdx++; + } + + nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(shplonkNuChallengeElements))); + (shplonkNu, ) = splitChallenge(nextPreviousChallenge); + } + + function generateShplonkZChallenge( + Honk.ZKProof memory proof, + Fr prevChallenge + ) internal pure returns (Fr shplonkZ, Fr nextPreviousChallenge) { + uint256[3] memory shplonkZChallengeElements; + shplonkZChallengeElements[0] = Fr.unwrap(prevChallenge); + + shplonkZChallengeElements[1] = proof.shplonkQ.x; + shplonkZChallengeElements[2] = proof.shplonkQ.y; + + nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(shplonkZChallengeElements))); + (shplonkZ, ) = splitChallenge(nextPreviousChallenge); + } + + function loadProof(bytes calldata proof, uint256 logN) internal pure returns (Honk.ZKProof memory p) { + uint256 boundary = 0x0; + + // Pairing point object + for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) { + p.pairingPointObject[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + boundary += FIELD_ELEMENT_SIZE; + } + // Commitments + p.w1 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.w2 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.w3 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + + // Lookup / Permutation Helper Commitments + p.lookupReadCounts = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.lookupReadTags = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.w4 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.lookupInverses = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.zPerm = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.libraCommitments[0] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + + p.libraSum = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + boundary += FIELD_ELEMENT_SIZE; + // Sumcheck univariates + for (uint256 i = 0; i < logN; i++) { + for (uint256 j = 0; j < ZK_BATCHED_RELATION_PARTIAL_LENGTH; j++) { + p.sumcheckUnivariates[i][j] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + boundary += FIELD_ELEMENT_SIZE; + } + } + + // Sumcheck evaluations + for (uint256 i = 0; i < NUMBER_OF_ENTITIES; i++) { + p.sumcheckEvaluations[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + boundary += FIELD_ELEMENT_SIZE; + } + + p.libraEvaluation = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + boundary += FIELD_ELEMENT_SIZE; + + p.libraCommitments[1] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.libraCommitments[2] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.geminiMaskingPoly = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.geminiMaskingEval = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + boundary += FIELD_ELEMENT_SIZE; + + // Gemini + // Read gemini fold univariates + for (uint256 i = 0; i < logN - 1; i++) { + p.geminiFoldComms[i] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + } + + // Read gemini a evaluations + for (uint256 i = 0; i < logN; i++) { + p.geminiAEvaluations[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + boundary += FIELD_ELEMENT_SIZE; + } + + for (uint256 i = 0; i < 4; i++) { + p.libraPolyEvals[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + boundary += FIELD_ELEMENT_SIZE; + } + + // Shplonk + p.shplonkQ = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + // KZG + p.kzgQuotient = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + } +} + +// Field arithmetic libraries + +library RelationsLib { + Fr internal constant GRUMPKIN_CURVE_B_PARAMETER_NEGATED = Fr.wrap(17); // -(-17) + + function accumulateRelationEvaluations( + Fr[NUMBER_OF_ENTITIES] memory purportedEvaluations, + Honk.RelationParameters memory rp, + Fr[NUMBER_OF_ALPHAS] memory alphas, + Fr powPartialEval + ) internal pure returns (Fr accumulator) { + Fr[NUMBER_OF_SUBRELATIONS] memory evaluations; + + // Accumulate all relations in Ultra Honk - each with varying number of subrelations + accumulateArithmeticRelation(purportedEvaluations, evaluations, powPartialEval); + accumulatePermutationRelation(purportedEvaluations, rp, evaluations, powPartialEval); + accumulateLogDerivativeLookupRelation(purportedEvaluations, rp, evaluations, powPartialEval); + accumulateDeltaRangeRelation(purportedEvaluations, evaluations, powPartialEval); + accumulateEllipticRelation(purportedEvaluations, evaluations, powPartialEval); + accumulateMemoryRelation(purportedEvaluations, rp, evaluations, powPartialEval); + accumulateNnfRelation(purportedEvaluations, evaluations, powPartialEval); + accumulatePoseidonExternalRelation(purportedEvaluations, evaluations, powPartialEval); + accumulatePoseidonInternalRelation(purportedEvaluations, evaluations, powPartialEval); + + // batch the subrelations with the alpha challenges to obtain the full honk relation + accumulator = scaleAndBatchSubrelations(evaluations, alphas); + } + + /** + * Aesthetic helper function that is used to index by enum into proof.sumcheckEvaluations, it avoids + * the relation checking code being cluttered with uint256 type casting, which is often a different colour in code + * editors, and thus is noisy. + */ + function wire(Fr[NUMBER_OF_ENTITIES] memory p, WIRE _wire) internal pure returns (Fr) { + return p[uint256(_wire)]; + } + + uint256 internal constant NEG_HALF_MODULO_P = 0x183227397098d014dc2822db40c0ac2e9419f4243cdcb848a1f0fac9f8000000; + /** + * Ultra Arithmetic Relation + * + */ + + function accumulateArithmeticRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + // Relation 0 + Fr q_arith = wire(p, WIRE.Q_ARITH); { - bytes32[7] memory round1; - round1[0] = FrLib.toBytes32(previousChallenge); - round1[1] = bytes32(proof.lookupReadCounts.x); - round1[2] = bytes32(proof.lookupReadCounts.y); - round1[3] = bytes32(proof.lookupReadTags.x); - round1[4] = bytes32(proof.lookupReadTags.y); - round1[5] = bytes32(proof.w4.x); - round1[6] = bytes32(proof.w4.y); - - nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(round1))); - (beta, gamma) = splitChallenge(nextPreviousChallenge); - } - - // Alpha challenges non-linearise the gate contributions - function generateAlphaChallenges(Fr previousChallenge, Honk.ZKProof memory proof) - internal - pure - returns (Fr[NUMBER_OF_ALPHAS] memory alphas, Fr nextPreviousChallenge) + Fr neg_half = Fr.wrap(NEG_HALF_MODULO_P); + + Fr accum = (q_arith - Fr.wrap(3)) * (wire(p, WIRE.Q_M) * wire(p, WIRE.W_R) * wire(p, WIRE.W_L)) * neg_half; + accum = + accum + + (wire(p, WIRE.Q_L) * wire(p, WIRE.W_L)) + + (wire(p, WIRE.Q_R) * wire(p, WIRE.W_R)) + + (wire(p, WIRE.Q_O) * wire(p, WIRE.W_O)) + + (wire(p, WIRE.Q_4) * wire(p, WIRE.W_4)) + + wire(p, WIRE.Q_C); + accum = accum + (q_arith - ONE) * wire(p, WIRE.W_4_SHIFT); + accum = accum * q_arith; + accum = accum * domainSep; + evals[0] = accum; + } + + // Relation 1 { - // Generate the original sumcheck alpha 0 by hashing zPerm and zLookup - uint256[5] memory alpha0; - alpha0[0] = Fr.unwrap(previousChallenge); - alpha0[1] = proof.lookupInverses.x; - alpha0[2] = proof.lookupInverses.y; - alpha0[3] = proof.zPerm.x; - alpha0[4] = proof.zPerm.y; - - nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(alpha0))); - Fr alpha; - (alpha,) = splitChallenge(nextPreviousChallenge); - - // Compute powers of alpha for batching subrelations - alphas[0] = alpha; - for (uint256 i = 1; i < NUMBER_OF_ALPHAS; i++) { - alphas[i] = alphas[i - 1] * alpha; - } - } - - function generateGateChallenges(Fr previousChallenge, uint256 logN) - internal - pure - returns (Fr[CONST_PROOF_SIZE_LOG_N] memory gateChallenges, Fr nextPreviousChallenge) + Fr accum = wire(p, WIRE.W_L) + wire(p, WIRE.W_4) - wire(p, WIRE.W_L_SHIFT) + wire(p, WIRE.Q_M); + accum = accum * (q_arith - Fr.wrap(2)); + accum = accum * (q_arith - ONE); + accum = accum * q_arith; + accum = accum * domainSep; + evals[1] = accum; + } + } + + function accumulatePermutationRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Honk.RelationParameters memory rp, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + Fr grand_product_numerator; + Fr grand_product_denominator; + { - previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(Fr.unwrap(previousChallenge)))); - (gateChallenges[0],) = splitChallenge(previousChallenge); - for (uint256 i = 1; i < logN; i++) { - gateChallenges[i] = gateChallenges[i - 1] * gateChallenges[i - 1]; - } - nextPreviousChallenge = previousChallenge; - } - - function generateLibraChallenge(Fr previousChallenge, Honk.ZKProof memory proof) - internal - pure - returns (Fr libraChallenge, Fr nextPreviousChallenge) + Fr num = wire(p, WIRE.W_L) + wire(p, WIRE.ID_1) * rp.beta + rp.gamma; + num = num * (wire(p, WIRE.W_R) + wire(p, WIRE.ID_2) * rp.beta + rp.gamma); + num = num * (wire(p, WIRE.W_O) + wire(p, WIRE.ID_3) * rp.beta + rp.gamma); + num = num * (wire(p, WIRE.W_4) + wire(p, WIRE.ID_4) * rp.beta + rp.gamma); + + grand_product_numerator = num; + } { - // 2 comm, 1 sum, 1 challenge - uint256[4] memory challengeData; - challengeData[0] = Fr.unwrap(previousChallenge); - challengeData[1] = proof.libraCommitments[0].x; - challengeData[2] = proof.libraCommitments[0].y; - challengeData[3] = Fr.unwrap(proof.libraSum); - nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(challengeData))); - (libraChallenge,) = splitChallenge(nextPreviousChallenge); - } - - function generateSumcheckChallenges(Honk.ZKProof memory proof, Fr prevChallenge, uint256 logN) - internal - pure - returns (Fr[CONST_PROOF_SIZE_LOG_N] memory sumcheckChallenges, Fr nextPreviousChallenge) + Fr den = wire(p, WIRE.W_L) + wire(p, WIRE.SIGMA_1) * rp.beta + rp.gamma; + den = den * (wire(p, WIRE.W_R) + wire(p, WIRE.SIGMA_2) * rp.beta + rp.gamma); + den = den * (wire(p, WIRE.W_O) + wire(p, WIRE.SIGMA_3) * rp.beta + rp.gamma); + den = den * (wire(p, WIRE.W_4) + wire(p, WIRE.SIGMA_4) * rp.beta + rp.gamma); + + grand_product_denominator = den; + } + + // Contribution 2 { - for (uint256 i = 0; i < logN; i++) { - Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH + 1] memory univariateChal; - univariateChal[0] = prevChallenge; + Fr acc = (wire(p, WIRE.Z_PERM) + wire(p, WIRE.LAGRANGE_FIRST)) * grand_product_numerator; - for (uint256 j = 0; j < ZK_BATCHED_RELATION_PARTIAL_LENGTH; j++) { - univariateChal[j + 1] = proof.sumcheckUnivariates[i][j]; - } - prevChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(univariateChal))); + acc = acc - ((wire(p, WIRE.Z_PERM_SHIFT) + (wire(p, WIRE.LAGRANGE_LAST) * rp.publicInputsDelta)) * grand_product_denominator); + acc = acc * domainSep; + evals[2] = acc; + } - (sumcheckChallenges[i],) = splitChallenge(prevChallenge); - } - nextPreviousChallenge = prevChallenge; + // Contribution 3 + { + Fr acc = (wire(p, WIRE.LAGRANGE_LAST) * wire(p, WIRE.Z_PERM_SHIFT)) * domainSep; + evals[3] = acc; } + } + + function accumulateLogDerivativeLookupRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Honk.RelationParameters memory rp, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + Fr write_term; + Fr read_term; - // We add Libra claimed eval + 3 comm + 1 more eval - function generateRhoChallenge(Honk.ZKProof memory proof, Fr prevChallenge) - internal - pure - returns (Fr rho, Fr nextPreviousChallenge) + // Calculate the write term (the table accumulation) { - uint256[NUMBER_OF_ENTITIES + 9] memory rhoChallengeElements; - rhoChallengeElements[0] = Fr.unwrap(prevChallenge); - uint256 i; - for (i = 1; i <= NUMBER_OF_ENTITIES; i++) { - rhoChallengeElements[i] = Fr.unwrap(proof.sumcheckEvaluations[i - 1]); - } - rhoChallengeElements[i] = Fr.unwrap(proof.libraEvaluation); - - i += 1; - rhoChallengeElements[i] = proof.libraCommitments[1].x; - rhoChallengeElements[i + 1] = proof.libraCommitments[1].y; - i += 2; - rhoChallengeElements[i] = proof.libraCommitments[2].x; - rhoChallengeElements[i + 1] = proof.libraCommitments[2].y; - i += 2; - rhoChallengeElements[i] = proof.geminiMaskingPoly.x; - rhoChallengeElements[i + 1] = proof.geminiMaskingPoly.y; - - i += 2; - rhoChallengeElements[i] = Fr.unwrap(proof.geminiMaskingEval); - - nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(rhoChallengeElements))); - (rho,) = splitChallenge(nextPreviousChallenge); - } - - function generateGeminiRChallenge(Honk.ZKProof memory proof, Fr prevChallenge, uint256 logN) - internal - pure - returns (Fr geminiR, Fr nextPreviousChallenge) + write_term = + wire(p, WIRE.TABLE_1) + + rp.gamma + + (wire(p, WIRE.TABLE_2) * rp.eta) + + (wire(p, WIRE.TABLE_3) * rp.etaTwo) + + (wire(p, WIRE.TABLE_4) * rp.etaThree); + } + + // Calculate the write term { - uint256[] memory gR = new uint256[]((logN - 1) * 2 + 1); - gR[0] = Fr.unwrap(prevChallenge); + Fr derived_entry_1 = wire(p, WIRE.W_L) + rp.gamma + (wire(p, WIRE.Q_R) * wire(p, WIRE.W_L_SHIFT)); + Fr derived_entry_2 = wire(p, WIRE.W_R) + wire(p, WIRE.Q_M) * wire(p, WIRE.W_R_SHIFT); + Fr derived_entry_3 = wire(p, WIRE.W_O) + wire(p, WIRE.Q_C) * wire(p, WIRE.W_O_SHIFT); + + read_term = derived_entry_1 + (derived_entry_2 * rp.eta) + (derived_entry_3 * rp.etaTwo) + (wire(p, WIRE.Q_O) * rp.etaThree); + } + + Fr read_inverse = wire(p, WIRE.LOOKUP_INVERSES) * write_term; + Fr write_inverse = wire(p, WIRE.LOOKUP_INVERSES) * read_term; + + Fr inverse_exists_xor = wire(p, WIRE.LOOKUP_READ_TAGS) + + wire(p, WIRE.Q_LOOKUP) - + (wire(p, WIRE.LOOKUP_READ_TAGS) * wire(p, WIRE.Q_LOOKUP)); + + // Inverse calculated correctly relation + Fr accumulatorNone = read_term * write_term * wire(p, WIRE.LOOKUP_INVERSES) - inverse_exists_xor; + accumulatorNone = accumulatorNone * domainSep; + + // Inverse + Fr accumulatorOne = wire(p, WIRE.Q_LOOKUP) * read_inverse - wire(p, WIRE.LOOKUP_READ_COUNTS) * write_inverse; + + Fr read_tag = wire(p, WIRE.LOOKUP_READ_TAGS); + + Fr read_tag_boolean_relation = read_tag * read_tag - read_tag; - for (uint256 i = 0; i < logN - 1; i++) { - gR[1 + i * 2] = proof.geminiFoldComms[i].x; - gR[2 + i * 2] = proof.geminiFoldComms[i].y; - } + evals[4] = accumulatorNone; + evals[5] = accumulatorOne; + evals[6] = read_tag_boolean_relation * domainSep; + } - nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(gR))); + function accumulateDeltaRangeRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + Fr minus_one = ZERO - ONE; + Fr minus_two = ZERO - Fr.wrap(2); + Fr minus_three = ZERO - Fr.wrap(3); - (geminiR,) = splitChallenge(nextPreviousChallenge); + // Compute wire differences + Fr delta_1 = wire(p, WIRE.W_R) - wire(p, WIRE.W_L); + Fr delta_2 = wire(p, WIRE.W_O) - wire(p, WIRE.W_R); + Fr delta_3 = wire(p, WIRE.W_4) - wire(p, WIRE.W_O); + Fr delta_4 = wire(p, WIRE.W_L_SHIFT) - wire(p, WIRE.W_4); + + // Contribution 6 + { + Fr acc = delta_1; + acc = acc * (delta_1 + minus_one); + acc = acc * (delta_1 + minus_two); + acc = acc * (delta_1 + minus_three); + acc = acc * wire(p, WIRE.Q_RANGE); + acc = acc * domainSep; + evals[7] = acc; } - function generateShplonkNuChallenge(Honk.ZKProof memory proof, Fr prevChallenge, uint256 logN) - internal - pure - returns (Fr shplonkNu, Fr nextPreviousChallenge) + // Contribution 7 { - uint256[] memory shplonkNuChallengeElements = new uint256[](logN + 1 + 4); - shplonkNuChallengeElements[0] = Fr.unwrap(prevChallenge); + Fr acc = delta_2; + acc = acc * (delta_2 + minus_one); + acc = acc * (delta_2 + minus_two); + acc = acc * (delta_2 + minus_three); + acc = acc * wire(p, WIRE.Q_RANGE); + acc = acc * domainSep; + evals[8] = acc; + } - for (uint256 i = 1; i <= logN; i++) { - shplonkNuChallengeElements[i] = Fr.unwrap(proof.geminiAEvaluations[i - 1]); - } + // Contribution 8 + { + Fr acc = delta_3; + acc = acc * (delta_3 + minus_one); + acc = acc * (delta_3 + minus_two); + acc = acc * (delta_3 + minus_three); + acc = acc * wire(p, WIRE.Q_RANGE); + acc = acc * domainSep; + evals[9] = acc; + } - uint256 libraIdx = 0; - for (uint256 i = logN + 1; i <= logN + 4; i++) { - shplonkNuChallengeElements[i] = Fr.unwrap(proof.libraPolyEvals[libraIdx]); - libraIdx++; - } + // Contribution 9 + { + Fr acc = delta_4; + acc = acc * (delta_4 + minus_one); + acc = acc * (delta_4 + minus_two); + acc = acc * (delta_4 + minus_three); + acc = acc * wire(p, WIRE.Q_RANGE); + acc = acc * domainSep; + evals[10] = acc; + } + } + + struct EllipticParams { + // Points + Fr x_1; + Fr y_1; + Fr x_2; + Fr y_2; + Fr y_3; + Fr x_3; + // push accumulators into memory + Fr x_double_identity; + } + + function accumulateEllipticRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + EllipticParams memory ep; + ep.x_1 = wire(p, WIRE.W_R); + ep.y_1 = wire(p, WIRE.W_O); + + ep.x_2 = wire(p, WIRE.W_L_SHIFT); + ep.y_2 = wire(p, WIRE.W_4_SHIFT); + ep.y_3 = wire(p, WIRE.W_O_SHIFT); + ep.x_3 = wire(p, WIRE.W_R_SHIFT); + + Fr q_sign = wire(p, WIRE.Q_L); + Fr q_is_double = wire(p, WIRE.Q_M); + + // Contribution 10 point addition, x-coordinate check + // q_elliptic * (x3 + x2 + x1)(x2 - x1)(x2 - x1) - y2^2 - y1^2 + 2(y2y1)*q_sign = 0 + Fr x_diff = (ep.x_2 - ep.x_1); + Fr y1_sqr = (ep.y_1 * ep.y_1); + { + // Move to top + Fr partialEval = domainSep; + + Fr y2_sqr = (ep.y_2 * ep.y_2); + Fr y1y2 = ep.y_1 * ep.y_2 * q_sign; + Fr x_add_identity = (ep.x_3 + ep.x_2 + ep.x_1); + x_add_identity = x_add_identity * x_diff * x_diff; + x_add_identity = x_add_identity - y2_sqr - y1_sqr + y1y2 + y1y2; - nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(shplonkNuChallengeElements))); - (shplonkNu,) = splitChallenge(nextPreviousChallenge); + evals[11] = x_add_identity * partialEval * wire(p, WIRE.Q_ELLIPTIC) * (ONE - q_is_double); } - function generateShplonkZChallenge(Honk.ZKProof memory proof, Fr prevChallenge) - internal - pure - returns (Fr shplonkZ, Fr nextPreviousChallenge) + // Contribution 11 point addition, x-coordinate check + // q_elliptic * (q_sign * y1 + y3)(x2 - x1) + (x3 - x1)(y2 - q_sign * y1) = 0 { - uint256[3] memory shplonkZChallengeElements; - shplonkZChallengeElements[0] = Fr.unwrap(prevChallenge); - - shplonkZChallengeElements[1] = proof.shplonkQ.x; - shplonkZChallengeElements[2] = proof.shplonkQ.y; - - nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(shplonkZChallengeElements))); - (shplonkZ,) = splitChallenge(nextPreviousChallenge); - } - - function loadProof(bytes calldata proof, uint256 logN) internal pure returns (Honk.ZKProof memory p) { - uint256 boundary = 0x0; - - // Pairing point object - for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) { - p.pairingPointObject[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); - boundary += FIELD_ELEMENT_SIZE; - } - // Commitments - p.w1 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - p.w2 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - p.w3 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - - // Lookup / Permutation Helper Commitments - p.lookupReadCounts = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - p.lookupReadTags = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - p.w4 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - p.lookupInverses = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - p.zPerm = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - p.libraCommitments[0] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - - p.libraSum = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); - boundary += FIELD_ELEMENT_SIZE; - // Sumcheck univariates - for (uint256 i = 0; i < logN; i++) { - for (uint256 j = 0; j < ZK_BATCHED_RELATION_PARTIAL_LENGTH; j++) { - p.sumcheckUnivariates[i][j] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); - boundary += FIELD_ELEMENT_SIZE; - } - } - - // Sumcheck evaluations - for (uint256 i = 0; i < NUMBER_OF_ENTITIES; i++) { - p.sumcheckEvaluations[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); - boundary += FIELD_ELEMENT_SIZE; - } - - p.libraEvaluation = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); - boundary += FIELD_ELEMENT_SIZE; + Fr y1_plus_y3 = ep.y_1 + ep.y_3; + Fr y_diff = ep.y_2 * q_sign - ep.y_1; + Fr y_add_identity = y1_plus_y3 * x_diff + (ep.x_3 - ep.x_1) * y_diff; + evals[12] = y_add_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * (ONE - q_is_double); + } - p.libraCommitments[1] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - p.libraCommitments[2] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - p.geminiMaskingPoly = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - p.geminiMaskingEval = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); - boundary += FIELD_ELEMENT_SIZE; + // Contribution 10 point doubling, x-coordinate check + // (x3 + x1 + x1) (4y1*y1) - 9 * x1 * x1 * x1 * x1 = 0 + // N.B. we're using the equivalence x1*x1*x1 === y1*y1 - curve_b to reduce degree by 1 + { + Fr x_pow_4 = (y1_sqr + GRUMPKIN_CURVE_B_PARAMETER_NEGATED) * ep.x_1; + Fr y1_sqr_mul_4 = y1_sqr + y1_sqr; + y1_sqr_mul_4 = y1_sqr_mul_4 + y1_sqr_mul_4; + Fr x1_pow_4_mul_9 = x_pow_4 * Fr.wrap(9); + + // NOTE: pushed into memory (stack >:'( ) + ep.x_double_identity = (ep.x_3 + ep.x_1 + ep.x_1) * y1_sqr_mul_4 - x1_pow_4_mul_9; - // Gemini - // Read gemini fold univariates - for (uint256 i = 0; i < logN - 1; i++) { - p.geminiFoldComms[i] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - } - - // Read gemini a evaluations - for (uint256 i = 0; i < logN; i++) { - p.geminiAEvaluations[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); - boundary += FIELD_ELEMENT_SIZE; - } - - for (uint256 i = 0; i < 4; i++) { - p.libraPolyEvals[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); - boundary += FIELD_ELEMENT_SIZE; - } - - // Shplonk - p.shplonkQ = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - // KZG - p.kzgQuotient = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + Fr acc = ep.x_double_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * q_is_double; + evals[11] = evals[11] + acc; } -} -// Field arithmetic libraries + // Contribution 11 point doubling, y-coordinate check + // (y1 + y1) (2y1) - (3 * x1 * x1)(x1 - x3) = 0 + { + Fr x1_sqr_mul_3 = (ep.x_1 + ep.x_1 + ep.x_1) * ep.x_1; + Fr y_double_identity = x1_sqr_mul_3 * (ep.x_1 - ep.x_3) - (ep.y_1 + ep.y_1) * (ep.y_1 + ep.y_3); + evals[12] = evals[12] + y_double_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * q_is_double; + } + } + + // Parameters used within the Memory Relation + // A struct is used to work around stack too deep. This relation has alot of variables + struct MemParams { + Fr memory_record_check; + Fr partial_record_check; + Fr next_gate_access_type; + Fr record_delta; + Fr index_delta; + Fr adjacent_values_match_if_adjacent_indices_match; + Fr adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation; + Fr access_check; + Fr next_gate_access_type_is_boolean; + Fr ROM_consistency_check_identity; + Fr RAM_consistency_check_identity; + Fr timestamp_delta; + Fr RAM_timestamp_check_identity; + Fr memory_identity; + Fr index_is_monotonically_increasing; + } + + function accumulateMemoryRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Honk.RelationParameters memory rp, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + MemParams memory ap; -library RelationsLib { - Fr internal constant GRUMPKIN_CURVE_B_PARAMETER_NEGATED = Fr.wrap(17); // -(-17) - - function accumulateRelationEvaluations( - Fr[NUMBER_OF_ENTITIES] memory purportedEvaluations, - Honk.RelationParameters memory rp, - Fr[NUMBER_OF_ALPHAS] memory alphas, - Fr powPartialEval - ) internal pure returns (Fr accumulator) { - Fr[NUMBER_OF_SUBRELATIONS] memory evaluations; - - // Accumulate all relations in Ultra Honk - each with varying number of subrelations - accumulateArithmeticRelation(purportedEvaluations, evaluations, powPartialEval); - accumulatePermutationRelation(purportedEvaluations, rp, evaluations, powPartialEval); - accumulateLogDerivativeLookupRelation(purportedEvaluations, rp, evaluations, powPartialEval); - accumulateDeltaRangeRelation(purportedEvaluations, evaluations, powPartialEval); - accumulateEllipticRelation(purportedEvaluations, evaluations, powPartialEval); - accumulateMemoryRelation(purportedEvaluations, rp, evaluations, powPartialEval); - accumulateNnfRelation(purportedEvaluations, evaluations, powPartialEval); - accumulatePoseidonExternalRelation(purportedEvaluations, evaluations, powPartialEval); - accumulatePoseidonInternalRelation(purportedEvaluations, evaluations, powPartialEval); - - // batch the subrelations with the alpha challenges to obtain the full honk relation - accumulator = scaleAndBatchSubrelations(evaluations, alphas); - } + /** + * MEMORY + * + * A RAM memory record contains a tuple of the following fields: + * * i: `index` of memory cell being accessed + * * t: `timestamp` of memory cell being accessed (used for RAM, set to 0 for ROM) + * * v: `value` of memory cell being accessed + * * a: `access` type of record. read: 0 = read, 1 = write + * * r: `record` of memory cell. record = access + index * eta + timestamp * eta_two + value * eta_three + * + * A ROM memory record contains a tuple of the following fields: + * * i: `index` of memory cell being accessed + * * v: `value1` of memory cell being accessed (ROM tables can store up to 2 values per index) + * * v2:`value2` of memory cell being accessed (ROM tables can store up to 2 values per index) + * * r: `record` of memory cell. record = index * eta + value2 * eta_two + value1 * eta_three + * + * When performing a read/write access, the values of i, t, v, v2, a, r are stored in the following wires + + * selectors, depending on whether the gate is a RAM read/write or a ROM read + * + * | gate type | i | v2/t | v | a | r | + * | --------- | -- | ----- | -- | -- | -- | + * | ROM | w1 | w2 | w3 | -- | w4 | + * | RAM | w1 | w2 | w3 | qc | w4 | + * + * (for accesses where `index` is a circuit constant, it is assumed the circuit will apply a copy constraint on + * `w2` to fix its value) + * + * + */ /** - * Aesthetic helper function that is used to index by enum into proof.sumcheckEvaluations, it avoids - * the relation checking code being cluttered with uint256 type casting, which is often a different colour in code - * editors, and thus is noisy. + * Memory Record Check + * Partial degree: 1 + * Total degree: 4 + * + * A ROM/ROM access gate can be evaluated with the identity: + * + * qc + w1 \eta + w2 \eta_two + w3 \eta_three - w4 = 0 + * + * For ROM gates, qc = 0 */ - function wire(Fr[NUMBER_OF_ENTITIES] memory p, WIRE _wire) internal pure returns (Fr) { - return p[uint256(_wire)]; - } + ap.memory_record_check = wire(p, WIRE.W_O) * rp.etaThree; + ap.memory_record_check = ap.memory_record_check + (wire(p, WIRE.W_R) * rp.etaTwo); + ap.memory_record_check = ap.memory_record_check + (wire(p, WIRE.W_L) * rp.eta); + ap.memory_record_check = ap.memory_record_check + wire(p, WIRE.Q_C); + ap.partial_record_check = ap.memory_record_check; // used in RAM consistency check; deg 1 or 4 + ap.memory_record_check = ap.memory_record_check - wire(p, WIRE.W_4); - uint256 internal constant NEG_HALF_MODULO_P = 0x183227397098d014dc2822db40c0ac2e9419f4243cdcb848a1f0fac9f8000000; /** - * Ultra Arithmetic Relation + * Contribution 13 & 14 + * ROM Consistency Check + * Partial degree: 1 + * Total degree: 4 + * + * For every ROM read, a set equivalence check is applied between the record witnesses, and a second set of + * records that are sorted. + * + * We apply the following checks for the sorted records: + * + * 1. w1, w2, w3 correctly map to 'index', 'v1, 'v2' for a given record value at w4 + * 2. index values for adjacent records are monotonically increasing + * 3. if, at gate i, index_i == index_{i + 1}, then value1_i == value1_{i + 1} and value2_i == value2_{i + 1} * */ + ap.index_delta = wire(p, WIRE.W_L_SHIFT) - wire(p, WIRE.W_L); + ap.record_delta = wire(p, WIRE.W_4_SHIFT) - wire(p, WIRE.W_4); - function accumulateArithmeticRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { - // Relation 0 - Fr q_arith = wire(p, WIRE.Q_ARITH); - { - Fr neg_half = Fr.wrap(NEG_HALF_MODULO_P); - - Fr accum = (q_arith - Fr.wrap(3)) * (wire(p, WIRE.Q_M) * wire(p, WIRE.W_R) * wire(p, WIRE.W_L)) * neg_half; - accum = accum + (wire(p, WIRE.Q_L) * wire(p, WIRE.W_L)) + (wire(p, WIRE.Q_R) * wire(p, WIRE.W_R)) - + (wire(p, WIRE.Q_O) * wire(p, WIRE.W_O)) + (wire(p, WIRE.Q_4) * wire(p, WIRE.W_4)) + wire(p, WIRE.Q_C); - accum = accum + (q_arith - ONE) * wire(p, WIRE.W_4_SHIFT); - accum = accum * q_arith; - accum = accum * domainSep; - evals[0] = accum; - } - - // Relation 1 - { - Fr accum = wire(p, WIRE.W_L) + wire(p, WIRE.W_4) - wire(p, WIRE.W_L_SHIFT) + wire(p, WIRE.Q_M); - accum = accum * (q_arith - Fr.wrap(2)); - accum = accum * (q_arith - ONE); - accum = accum * q_arith; - accum = accum * domainSep; - evals[1] = accum; - } - } - - function accumulatePermutationRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Honk.RelationParameters memory rp, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { - Fr grand_product_numerator; - Fr grand_product_denominator; - - { - Fr num = wire(p, WIRE.W_L) + wire(p, WIRE.ID_1) * rp.beta + rp.gamma; - num = num * (wire(p, WIRE.W_R) + wire(p, WIRE.ID_2) * rp.beta + rp.gamma); - num = num * (wire(p, WIRE.W_O) + wire(p, WIRE.ID_3) * rp.beta + rp.gamma); - num = num * (wire(p, WIRE.W_4) + wire(p, WIRE.ID_4) * rp.beta + rp.gamma); - - grand_product_numerator = num; - } - { - Fr den = wire(p, WIRE.W_L) + wire(p, WIRE.SIGMA_1) * rp.beta + rp.gamma; - den = den * (wire(p, WIRE.W_R) + wire(p, WIRE.SIGMA_2) * rp.beta + rp.gamma); - den = den * (wire(p, WIRE.W_O) + wire(p, WIRE.SIGMA_3) * rp.beta + rp.gamma); - den = den * (wire(p, WIRE.W_4) + wire(p, WIRE.SIGMA_4) * rp.beta + rp.gamma); - - grand_product_denominator = den; - } - - // Contribution 2 - { - Fr acc = (wire(p, WIRE.Z_PERM) + wire(p, WIRE.LAGRANGE_FIRST)) * grand_product_numerator; - - acc = acc - - ( - (wire(p, WIRE.Z_PERM_SHIFT) + (wire(p, WIRE.LAGRANGE_LAST) * rp.publicInputsDelta)) - * grand_product_denominator - ); - acc = acc * domainSep; - evals[2] = acc; - } - - // Contribution 3 - { - Fr acc = (wire(p, WIRE.LAGRANGE_LAST) * wire(p, WIRE.Z_PERM_SHIFT)) * domainSep; - evals[3] = acc; - } - } - - function accumulateLogDerivativeLookupRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Honk.RelationParameters memory rp, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { - Fr write_term; - Fr read_term; - - // Calculate the write term (the table accumulation) - { - write_term = wire(p, WIRE.TABLE_1) + rp.gamma + (wire(p, WIRE.TABLE_2) * rp.eta) - + (wire(p, WIRE.TABLE_3) * rp.etaTwo) + (wire(p, WIRE.TABLE_4) * rp.etaThree); - } - - // Calculate the write term - { - Fr derived_entry_1 = wire(p, WIRE.W_L) + rp.gamma + (wire(p, WIRE.Q_R) * wire(p, WIRE.W_L_SHIFT)); - Fr derived_entry_2 = wire(p, WIRE.W_R) + wire(p, WIRE.Q_M) * wire(p, WIRE.W_R_SHIFT); - Fr derived_entry_3 = wire(p, WIRE.W_O) + wire(p, WIRE.Q_C) * wire(p, WIRE.W_O_SHIFT); - - read_term = derived_entry_1 + (derived_entry_2 * rp.eta) + (derived_entry_3 * rp.etaTwo) - + (wire(p, WIRE.Q_O) * rp.etaThree); - } - - Fr read_inverse = wire(p, WIRE.LOOKUP_INVERSES) * write_term; - Fr write_inverse = wire(p, WIRE.LOOKUP_INVERSES) * read_term; - - Fr inverse_exists_xor = wire(p, WIRE.LOOKUP_READ_TAGS) + wire(p, WIRE.Q_LOOKUP) - - (wire(p, WIRE.LOOKUP_READ_TAGS) * wire(p, WIRE.Q_LOOKUP)); - - // Inverse calculated correctly relation - Fr accumulatorNone = read_term * write_term * wire(p, WIRE.LOOKUP_INVERSES) - inverse_exists_xor; - accumulatorNone = accumulatorNone * domainSep; - - // Inverse - Fr accumulatorOne = wire(p, WIRE.Q_LOOKUP) * read_inverse - wire(p, WIRE.LOOKUP_READ_COUNTS) * write_inverse; - - Fr read_tag = wire(p, WIRE.LOOKUP_READ_TAGS); - - Fr read_tag_boolean_relation = read_tag * read_tag - read_tag; - - evals[4] = accumulatorNone; - evals[5] = accumulatorOne; - evals[6] = read_tag_boolean_relation * domainSep; - } - - function accumulateDeltaRangeRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { - Fr minus_one = ZERO - ONE; - Fr minus_two = ZERO - Fr.wrap(2); - Fr minus_three = ZERO - Fr.wrap(3); - - // Compute wire differences - Fr delta_1 = wire(p, WIRE.W_R) - wire(p, WIRE.W_L); - Fr delta_2 = wire(p, WIRE.W_O) - wire(p, WIRE.W_R); - Fr delta_3 = wire(p, WIRE.W_4) - wire(p, WIRE.W_O); - Fr delta_4 = wire(p, WIRE.W_L_SHIFT) - wire(p, WIRE.W_4); - - // Contribution 6 - { - Fr acc = delta_1; - acc = acc * (delta_1 + minus_one); - acc = acc * (delta_1 + minus_two); - acc = acc * (delta_1 + minus_three); - acc = acc * wire(p, WIRE.Q_RANGE); - acc = acc * domainSep; - evals[7] = acc; - } - - // Contribution 7 - { - Fr acc = delta_2; - acc = acc * (delta_2 + minus_one); - acc = acc * (delta_2 + minus_two); - acc = acc * (delta_2 + minus_three); - acc = acc * wire(p, WIRE.Q_RANGE); - acc = acc * domainSep; - evals[8] = acc; - } - - // Contribution 8 - { - Fr acc = delta_3; - acc = acc * (delta_3 + minus_one); - acc = acc * (delta_3 + minus_two); - acc = acc * (delta_3 + minus_three); - acc = acc * wire(p, WIRE.Q_RANGE); - acc = acc * domainSep; - evals[9] = acc; - } - - // Contribution 9 - { - Fr acc = delta_4; - acc = acc * (delta_4 + minus_one); - acc = acc * (delta_4 + minus_two); - acc = acc * (delta_4 + minus_three); - acc = acc * wire(p, WIRE.Q_RANGE); - acc = acc * domainSep; - evals[10] = acc; - } - } - - struct EllipticParams { - // Points - Fr x_1; - Fr y_1; - Fr x_2; - Fr y_2; - Fr y_3; - Fr x_3; - // push accumulators into memory - Fr x_double_identity; - } - - function accumulateEllipticRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { - EllipticParams memory ep; - ep.x_1 = wire(p, WIRE.W_R); - ep.y_1 = wire(p, WIRE.W_O); - - ep.x_2 = wire(p, WIRE.W_L_SHIFT); - ep.y_2 = wire(p, WIRE.W_4_SHIFT); - ep.y_3 = wire(p, WIRE.W_O_SHIFT); - ep.x_3 = wire(p, WIRE.W_R_SHIFT); - - Fr q_sign = wire(p, WIRE.Q_L); - Fr q_is_double = wire(p, WIRE.Q_M); - - // Contribution 10 point addition, x-coordinate check - // q_elliptic * (x3 + x2 + x1)(x2 - x1)(x2 - x1) - y2^2 - y1^2 + 2(y2y1)*q_sign = 0 - Fr x_diff = (ep.x_2 - ep.x_1); - Fr y1_sqr = (ep.y_1 * ep.y_1); - { - // Move to top - Fr partialEval = domainSep; - - Fr y2_sqr = (ep.y_2 * ep.y_2); - Fr y1y2 = ep.y_1 * ep.y_2 * q_sign; - Fr x_add_identity = (ep.x_3 + ep.x_2 + ep.x_1); - x_add_identity = x_add_identity * x_diff * x_diff; - x_add_identity = x_add_identity - y2_sqr - y1_sqr + y1y2 + y1y2; - - evals[11] = x_add_identity * partialEval * wire(p, WIRE.Q_ELLIPTIC) * (ONE - q_is_double); - } - - // Contribution 11 point addition, x-coordinate check - // q_elliptic * (q_sign * y1 + y3)(x2 - x1) + (x3 - x1)(y2 - q_sign * y1) = 0 - { - Fr y1_plus_y3 = ep.y_1 + ep.y_3; - Fr y_diff = ep.y_2 * q_sign - ep.y_1; - Fr y_add_identity = y1_plus_y3 * x_diff + (ep.x_3 - ep.x_1) * y_diff; - evals[12] = y_add_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * (ONE - q_is_double); - } - - // Contribution 10 point doubling, x-coordinate check - // (x3 + x1 + x1) (4y1*y1) - 9 * x1 * x1 * x1 * x1 = 0 - // N.B. we're using the equivalence x1*x1*x1 === y1*y1 - curve_b to reduce degree by 1 - { - Fr x_pow_4 = (y1_sqr + GRUMPKIN_CURVE_B_PARAMETER_NEGATED) * ep.x_1; - Fr y1_sqr_mul_4 = y1_sqr + y1_sqr; - y1_sqr_mul_4 = y1_sqr_mul_4 + y1_sqr_mul_4; - Fr x1_pow_4_mul_9 = x_pow_4 * Fr.wrap(9); - - // NOTE: pushed into memory (stack >:'( ) - ep.x_double_identity = (ep.x_3 + ep.x_1 + ep.x_1) * y1_sqr_mul_4 - x1_pow_4_mul_9; - - Fr acc = ep.x_double_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * q_is_double; - evals[11] = evals[11] + acc; - } - - // Contribution 11 point doubling, y-coordinate check - // (y1 + y1) (2y1) - (3 * x1 * x1)(x1 - x3) = 0 - { - Fr x1_sqr_mul_3 = (ep.x_1 + ep.x_1 + ep.x_1) * ep.x_1; - Fr y_double_identity = x1_sqr_mul_3 * (ep.x_1 - ep.x_3) - (ep.y_1 + ep.y_1) * (ep.y_1 + ep.y_3); - evals[12] = evals[12] + y_double_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * q_is_double; - } - } - - // Parameters used within the Memory Relation - // A struct is used to work around stack too deep. This relation has alot of variables - struct MemParams { - Fr memory_record_check; - Fr partial_record_check; - Fr next_gate_access_type; - Fr record_delta; - Fr index_delta; - Fr adjacent_values_match_if_adjacent_indices_match; - Fr adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation; - Fr access_check; - Fr next_gate_access_type_is_boolean; - Fr ROM_consistency_check_identity; - Fr RAM_consistency_check_identity; - Fr timestamp_delta; - Fr RAM_timestamp_check_identity; - Fr memory_identity; - Fr index_is_monotonically_increasing; - } - - function accumulateMemoryRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Honk.RelationParameters memory rp, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { - MemParams memory ap; - - /** - * MEMORY - * - * A RAM memory record contains a tuple of the following fields: - * * i: `index` of memory cell being accessed - * * t: `timestamp` of memory cell being accessed (used for RAM, set to 0 for ROM) - * * v: `value` of memory cell being accessed - * * a: `access` type of record. read: 0 = read, 1 = write - * * r: `record` of memory cell. record = access + index * eta + timestamp * eta_two + value * eta_three - * - * A ROM memory record contains a tuple of the following fields: - * * i: `index` of memory cell being accessed - * * v: `value1` of memory cell being accessed (ROM tables can store up to 2 values per index) - * * v2:`value2` of memory cell being accessed (ROM tables can store up to 2 values per index) - * * r: `record` of memory cell. record = index * eta + value2 * eta_two + value1 * eta_three - * - * When performing a read/write access, the values of i, t, v, v2, a, r are stored in the following wires + - * selectors, depending on whether the gate is a RAM read/write or a ROM read - * - * | gate type | i | v2/t | v | a | r | - * | --------- | -- | ----- | -- | -- | -- | - * | ROM | w1 | w2 | w3 | -- | w4 | - * | RAM | w1 | w2 | w3 | qc | w4 | - * - * (for accesses where `index` is a circuit constant, it is assumed the circuit will apply a copy constraint on - * `w2` to fix its value) - * - * - */ - - /** - * Memory Record Check - * Partial degree: 1 - * Total degree: 4 - * - * A ROM/ROM access gate can be evaluated with the identity: - * - * qc + w1 \eta + w2 \eta_two + w3 \eta_three - w4 = 0 - * - * For ROM gates, qc = 0 - */ - ap.memory_record_check = wire(p, WIRE.W_O) * rp.etaThree; - ap.memory_record_check = ap.memory_record_check + (wire(p, WIRE.W_R) * rp.etaTwo); - ap.memory_record_check = ap.memory_record_check + (wire(p, WIRE.W_L) * rp.eta); - ap.memory_record_check = ap.memory_record_check + wire(p, WIRE.Q_C); - ap.partial_record_check = ap.memory_record_check; // used in RAM consistency check; deg 1 or 4 - ap.memory_record_check = ap.memory_record_check - wire(p, WIRE.W_4); - - /** - * Contribution 13 & 14 - * ROM Consistency Check - * Partial degree: 1 - * Total degree: 4 - * - * For every ROM read, a set equivalence check is applied between the record witnesses, and a second set of - * records that are sorted. - * - * We apply the following checks for the sorted records: - * - * 1. w1, w2, w3 correctly map to 'index', 'v1, 'v2' for a given record value at w4 - * 2. index values for adjacent records are monotonically increasing - * 3. if, at gate i, index_i == index_{i + 1}, then value1_i == value1_{i + 1} and value2_i == value2_{i + 1} - * - */ - ap.index_delta = wire(p, WIRE.W_L_SHIFT) - wire(p, WIRE.W_L); - ap.record_delta = wire(p, WIRE.W_4_SHIFT) - wire(p, WIRE.W_4); - - ap.index_is_monotonically_increasing = ap.index_delta * (ap.index_delta - Fr.wrap(1)); // deg 2 - - ap.adjacent_values_match_if_adjacent_indices_match = (ap.index_delta * MINUS_ONE + ONE) * ap.record_delta; // deg 2 - - evals[14] = ap.adjacent_values_match_if_adjacent_indices_match * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)) - * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5 - evals[15] = ap.index_is_monotonically_increasing * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)) - * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5 - - ap.ROM_consistency_check_identity = ap.memory_record_check * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)); // deg 3 or 7 - - /** - * Contributions 15,16,17 - * RAM Consistency Check - * - * The 'access' type of the record is extracted with the expression `w_4 - ap.partial_record_check` - * (i.e. for an honest Prover `w1 * eta + w2 * eta^2 + w3 * eta^3 - w4 = access`. - * This is validated by requiring `access` to be boolean - * - * For two adjacent entries in the sorted list if _both_ - * A) index values match - * B) adjacent access value is 0 (i.e. next gate is a READ) - * then - * C) both values must match. - * The gate boolean check is - * (A && B) => C === !(A && B) || C === !A || !B || C - * - * N.B. it is the responsibility of the circuit writer to ensure that every RAM cell is initialized - * with a WRITE operation. - */ - Fr access_type = (wire(p, WIRE.W_4) - ap.partial_record_check); // will be 0 or 1 for honest Prover; deg 1 or 4 - ap.access_check = access_type * (access_type - Fr.wrap(1)); // check value is 0 or 1; deg 2 or 8 - - // reverse order we could re-use `ap.partial_record_check` 1 - ((w3' * eta + w2') * eta + w1') * eta - // deg 1 or 4 - ap.next_gate_access_type = wire(p, WIRE.W_O_SHIFT) * rp.etaThree; - ap.next_gate_access_type = ap.next_gate_access_type + (wire(p, WIRE.W_R_SHIFT) * rp.etaTwo); - ap.next_gate_access_type = ap.next_gate_access_type + (wire(p, WIRE.W_L_SHIFT) * rp.eta); - ap.next_gate_access_type = wire(p, WIRE.W_4_SHIFT) - ap.next_gate_access_type; - - Fr value_delta = wire(p, WIRE.W_O_SHIFT) - wire(p, WIRE.W_O); - ap.adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation = - (ap.index_delta * MINUS_ONE + ONE) * value_delta * (ap.next_gate_access_type * MINUS_ONE + ONE); // deg 3 or 6 - - // We can't apply the RAM consistency check identity on the final entry in the sorted list (the wires in the - // next gate would make the identity fail). We need to validate that its 'access type' bool is correct. Can't - // do with an arithmetic gate because of the `eta` factors. We need to check that the *next* gate's access - // type is correct, to cover this edge case - // deg 2 or 4 - ap.next_gate_access_type_is_boolean = - ap.next_gate_access_type * ap.next_gate_access_type - ap.next_gate_access_type; - - // Putting it all together... - evals[16] = ap.adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation - * (wire(p, WIRE.Q_O)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5 or 8 - evals[17] = ap.index_is_monotonically_increasing * (wire(p, WIRE.Q_O)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 - evals[18] = ap.next_gate_access_type_is_boolean * (wire(p, WIRE.Q_O)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 or 6 - - ap.RAM_consistency_check_identity = ap.access_check * (wire(p, WIRE.Q_O)); // deg 3 or 9 - - /** - * RAM Timestamp Consistency Check - * - * | w1 | w2 | w3 | w4 | - * | index | timestamp | timestamp_check | -- | - * - * Let delta_index = index_{i + 1} - index_{i} - * - * Iff delta_index == 0, timestamp_check = timestamp_{i + 1} - timestamp_i - * Else timestamp_check = 0 - */ - ap.timestamp_delta = wire(p, WIRE.W_R_SHIFT) - wire(p, WIRE.W_R); - ap.RAM_timestamp_check_identity = (ap.index_delta * MINUS_ONE + ONE) * ap.timestamp_delta - wire(p, WIRE.W_O); // deg 3 - - /** - * Complete Contribution 12 - * The complete RAM/ROM memory identity - * Partial degree: - */ - ap.memory_identity = ap.ROM_consistency_check_identity; // deg 3 or 6 - ap.memory_identity = - ap.memory_identity + ap.RAM_timestamp_check_identity * (wire(p, WIRE.Q_4) * wire(p, WIRE.Q_L)); // deg 4 - ap.memory_identity = ap.memory_identity + ap.memory_record_check * (wire(p, WIRE.Q_M) * wire(p, WIRE.Q_L)); // deg 3 or 6 - ap.memory_identity = ap.memory_identity + ap.RAM_consistency_check_identity; // deg 3 or 9 - - // (deg 3 or 9) + (deg 4) + (deg 3) - ap.memory_identity = ap.memory_identity * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 or 10 - evals[13] = ap.memory_identity; - } - - // Constants for the Non-native Field relation - Fr constant LIMB_SIZE = Fr.wrap(uint256(1) << 68); - Fr constant SUBLIMB_SHIFT = Fr.wrap(uint256(1) << 14); - - // Parameters used within the Non-Native Field Relation - // A struct is used to work around stack too deep. This relation has alot of variables - struct NnfParams { - Fr limb_subproduct; - Fr non_native_field_gate_1; - Fr non_native_field_gate_2; - Fr non_native_field_gate_3; - Fr limb_accumulator_1; - Fr limb_accumulator_2; - Fr nnf_identity; - } - - function accumulateNnfRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { - NnfParams memory ap; - - /** - * Contribution 12 - * Non native field arithmetic gate 2 - * deg 4 - * - * _ _ - * / _ _ _ 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 | - * \_ _/ - * - * - */ - ap.limb_subproduct = wire(p, WIRE.W_L) * wire(p, WIRE.W_R_SHIFT) + wire(p, WIRE.W_L_SHIFT) * wire(p, WIRE.W_R); - ap.non_native_field_gate_2 = - (wire(p, WIRE.W_L) * wire(p, WIRE.W_4) + wire(p, WIRE.W_R) * wire(p, WIRE.W_O) - wire(p, WIRE.W_O_SHIFT)); - ap.non_native_field_gate_2 = ap.non_native_field_gate_2 * LIMB_SIZE; - ap.non_native_field_gate_2 = ap.non_native_field_gate_2 - wire(p, WIRE.W_4_SHIFT); - ap.non_native_field_gate_2 = ap.non_native_field_gate_2 + ap.limb_subproduct; - ap.non_native_field_gate_2 = ap.non_native_field_gate_2 * wire(p, WIRE.Q_4); - - ap.limb_subproduct = ap.limb_subproduct * LIMB_SIZE; - ap.limb_subproduct = ap.limb_subproduct + (wire(p, WIRE.W_L_SHIFT) * wire(p, WIRE.W_R_SHIFT)); - ap.non_native_field_gate_1 = ap.limb_subproduct; - ap.non_native_field_gate_1 = ap.non_native_field_gate_1 - (wire(p, WIRE.W_O) + wire(p, WIRE.W_4)); - ap.non_native_field_gate_1 = ap.non_native_field_gate_1 * wire(p, WIRE.Q_O); - - ap.non_native_field_gate_3 = ap.limb_subproduct; - ap.non_native_field_gate_3 = ap.non_native_field_gate_3 + wire(p, WIRE.W_4); - ap.non_native_field_gate_3 = ap.non_native_field_gate_3 - (wire(p, WIRE.W_O_SHIFT) + wire(p, WIRE.W_4_SHIFT)); - ap.non_native_field_gate_3 = ap.non_native_field_gate_3 * wire(p, WIRE.Q_M); - - Fr non_native_field_identity = - ap.non_native_field_gate_1 + ap.non_native_field_gate_2 + ap.non_native_field_gate_3; - non_native_field_identity = non_native_field_identity * wire(p, WIRE.Q_R); - - // ((((w2' * 2^14 + w1') * 2^14 + w3) * 2^14 + w2) * 2^14 + w1 - w4) * qm - // deg 2 - ap.limb_accumulator_1 = wire(p, WIRE.W_R_SHIFT) * SUBLIMB_SHIFT; - ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_L_SHIFT); - ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT; - ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_O); - ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT; - ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_R); - ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT; - ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_L); - ap.limb_accumulator_1 = ap.limb_accumulator_1 - wire(p, WIRE.W_4); - ap.limb_accumulator_1 = ap.limb_accumulator_1 * wire(p, WIRE.Q_4); - - // ((((w3' * 2^14 + w2') * 2^14 + w1') * 2^14 + w4) * 2^14 + w3 - w4') * qm - // deg 2 - ap.limb_accumulator_2 = wire(p, WIRE.W_O_SHIFT) * SUBLIMB_SHIFT; - ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_R_SHIFT); - ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT; - ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_L_SHIFT); - ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT; - ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_4); - ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT; - ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_O); - ap.limb_accumulator_2 = ap.limb_accumulator_2 - wire(p, WIRE.W_4_SHIFT); - ap.limb_accumulator_2 = ap.limb_accumulator_2 * wire(p, WIRE.Q_M); - - Fr limb_accumulator_identity = ap.limb_accumulator_1 + ap.limb_accumulator_2; - limb_accumulator_identity = limb_accumulator_identity * wire(p, WIRE.Q_O); // deg 3 - - ap.nnf_identity = non_native_field_identity + limb_accumulator_identity; - ap.nnf_identity = ap.nnf_identity * (wire(p, WIRE.Q_NNF) * domainSep); - evals[19] = ap.nnf_identity; - } - - struct PoseidonExternalParams { - Fr s1; - Fr s2; - Fr s3; - Fr s4; - Fr u1; - Fr u2; - Fr u3; - Fr u4; - Fr t0; - Fr t1; - Fr t2; - Fr t3; - Fr v1; - Fr v2; - Fr v3; - Fr v4; - Fr q_pos_by_scaling; - } - - function accumulatePoseidonExternalRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { - PoseidonExternalParams memory ep; - - ep.s1 = wire(p, WIRE.W_L) + wire(p, WIRE.Q_L); - ep.s2 = wire(p, WIRE.W_R) + wire(p, WIRE.Q_R); - ep.s3 = wire(p, WIRE.W_O) + wire(p, WIRE.Q_O); - ep.s4 = wire(p, WIRE.W_4) + wire(p, WIRE.Q_4); - - ep.u1 = ep.s1 * ep.s1 * ep.s1 * ep.s1 * ep.s1; - ep.u2 = ep.s2 * ep.s2 * ep.s2 * ep.s2 * ep.s2; - ep.u3 = ep.s3 * ep.s3 * ep.s3 * ep.s3 * ep.s3; - ep.u4 = ep.s4 * ep.s4 * ep.s4 * ep.s4 * ep.s4; - // matrix mul v = M_E * u with 14 additions - ep.t0 = ep.u1 + ep.u2; // u_1 + u_2 - ep.t1 = ep.u3 + ep.u4; // u_3 + u_4 - ep.t2 = ep.u2 + ep.u2 + ep.t1; // 2u_2 - // ep.t2 += ep.t1; // 2u_2 + u_3 + u_4 - ep.t3 = ep.u4 + ep.u4 + ep.t0; // 2u_4 - // ep.t3 += ep.t0; // u_1 + u_2 + 2u_4 - ep.v4 = ep.t1 + ep.t1; - ep.v4 = ep.v4 + ep.v4 + ep.t3; - // ep.v4 += ep.t3; // u_1 + u_2 + 4u_3 + 6u_4 - ep.v2 = ep.t0 + ep.t0; - ep.v2 = ep.v2 + ep.v2 + ep.t2; - // ep.v2 += ep.t2; // 4u_1 + 6u_2 + u_3 + u_4 - ep.v1 = ep.t3 + ep.v2; // 5u_1 + 7u_2 + u_3 + 3u_4 - ep.v3 = ep.t2 + ep.v4; // u_1 + 3u_2 + 5u_3 + 7u_4 - - ep.q_pos_by_scaling = wire(p, WIRE.Q_POSEIDON2_EXTERNAL) * domainSep; - evals[20] = evals[20] + ep.q_pos_by_scaling * (ep.v1 - wire(p, WIRE.W_L_SHIFT)); - - evals[21] = evals[21] + ep.q_pos_by_scaling * (ep.v2 - wire(p, WIRE.W_R_SHIFT)); - - evals[22] = evals[22] + ep.q_pos_by_scaling * (ep.v3 - wire(p, WIRE.W_O_SHIFT)); - - evals[23] = evals[23] + ep.q_pos_by_scaling * (ep.v4 - wire(p, WIRE.W_4_SHIFT)); - } - - struct PoseidonInternalParams { - Fr u1; - Fr u2; - Fr u3; - Fr u4; - Fr u_sum; - Fr v1; - Fr v2; - Fr v3; - Fr v4; - Fr s1; - Fr q_pos_by_scaling; - } - - function accumulatePoseidonInternalRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { - PoseidonInternalParams memory ip; - - Fr[4] memory INTERNAL_MATRIX_DIAGONAL = [ - FrLib.from(0x10dc6e9c006ea38b04b1e03b4bd9490c0d03f98929ca1d7fb56821fd19d3b6e7), - FrLib.from(0x0c28145b6a44df3e0149b3d0a30b3bb599df9756d4dd9b84a86b38cfb45a740b), - FrLib.from(0x00544b8338791518b2c7645a50392798b21f75bb60e3596170067d00141cac15), - FrLib.from(0x222c01175718386f2e2e82eb122789e352e105a3b8fa852613bc534433ee428b) - ]; - - // add round constants - ip.s1 = wire(p, WIRE.W_L) + wire(p, WIRE.Q_L); - - // apply s-box round - ip.u1 = ip.s1 * ip.s1 * ip.s1 * ip.s1 * ip.s1; - ip.u2 = wire(p, WIRE.W_R); - ip.u3 = wire(p, WIRE.W_O); - ip.u4 = wire(p, WIRE.W_4); - - // matrix mul with v = M_I * u 4 muls and 7 additions - ip.u_sum = ip.u1 + ip.u2 + ip.u3 + ip.u4; - - ip.q_pos_by_scaling = wire(p, WIRE.Q_POSEIDON2_INTERNAL) * domainSep; - - ip.v1 = ip.u1 * INTERNAL_MATRIX_DIAGONAL[0] + ip.u_sum; - evals[24] = evals[24] + ip.q_pos_by_scaling * (ip.v1 - wire(p, WIRE.W_L_SHIFT)); - - ip.v2 = ip.u2 * INTERNAL_MATRIX_DIAGONAL[1] + ip.u_sum; - evals[25] = evals[25] + ip.q_pos_by_scaling * (ip.v2 - wire(p, WIRE.W_R_SHIFT)); - - ip.v3 = ip.u3 * INTERNAL_MATRIX_DIAGONAL[2] + ip.u_sum; - evals[26] = evals[26] + ip.q_pos_by_scaling * (ip.v3 - wire(p, WIRE.W_O_SHIFT)); - - ip.v4 = ip.u4 * INTERNAL_MATRIX_DIAGONAL[3] + ip.u_sum; - evals[27] = evals[27] + ip.q_pos_by_scaling * (ip.v4 - wire(p, WIRE.W_4_SHIFT)); - } - - function scaleAndBatchSubrelations( - Fr[NUMBER_OF_SUBRELATIONS] memory evaluations, - Fr[NUMBER_OF_ALPHAS] memory subrelationChallenges - ) internal pure returns (Fr accumulator) { - accumulator = evaluations[0]; - - for (uint256 i = 1; i < NUMBER_OF_SUBRELATIONS; ++i) { - accumulator = accumulator + evaluations[i] * subrelationChallenges[i - 1]; - } - } + ap.index_is_monotonically_increasing = ap.index_delta * (ap.index_delta - Fr.wrap(1)); // deg 2 + + ap.adjacent_values_match_if_adjacent_indices_match = (ap.index_delta * MINUS_ONE + ONE) * ap.record_delta; // deg 2 + + evals[14] = + ap.adjacent_values_match_if_adjacent_indices_match * + (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)) * + (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5 + evals[15] = ap.index_is_monotonically_increasing * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5 + + ap.ROM_consistency_check_identity = ap.memory_record_check * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)); // deg 3 or 7 + + /** + * Contributions 15,16,17 + * RAM Consistency Check + * + * The 'access' type of the record is extracted with the expression `w_4 - ap.partial_record_check` + * (i.e. for an honest Prover `w1 * eta + w2 * eta^2 + w3 * eta^3 - w4 = access`. + * This is validated by requiring `access` to be boolean + * + * For two adjacent entries in the sorted list if _both_ + * A) index values match + * B) adjacent access value is 0 (i.e. next gate is a READ) + * then + * C) both values must match. + * The gate boolean check is + * (A && B) => C === !(A && B) || C === !A || !B || C + * + * N.B. it is the responsibility of the circuit writer to ensure that every RAM cell is initialized + * with a WRITE operation. + */ + Fr access_type = (wire(p, WIRE.W_4) - ap.partial_record_check); // will be 0 or 1 for honest Prover; deg 1 or 4 + ap.access_check = access_type * (access_type - Fr.wrap(1)); // check value is 0 or 1; deg 2 or 8 + + // reverse order we could re-use `ap.partial_record_check` 1 - ((w3' * eta + w2') * eta + w1') * eta + // deg 1 or 4 + ap.next_gate_access_type = wire(p, WIRE.W_O_SHIFT) * rp.etaThree; + ap.next_gate_access_type = ap.next_gate_access_type + (wire(p, WIRE.W_R_SHIFT) * rp.etaTwo); + ap.next_gate_access_type = ap.next_gate_access_type + (wire(p, WIRE.W_L_SHIFT) * rp.eta); + ap.next_gate_access_type = wire(p, WIRE.W_4_SHIFT) - ap.next_gate_access_type; + + Fr value_delta = wire(p, WIRE.W_O_SHIFT) - wire(p, WIRE.W_O); + ap.adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation = + (ap.index_delta * MINUS_ONE + ONE) * + value_delta * + (ap.next_gate_access_type * MINUS_ONE + ONE); // deg 3 or 6 + + // We can't apply the RAM consistency check identity on the final entry in the sorted list (the wires in the + // next gate would make the identity fail). We need to validate that its 'access type' bool is correct. Can't + // do with an arithmetic gate because of the `eta` factors. We need to check that the *next* gate's access + // type is correct, to cover this edge case + // deg 2 or 4 + ap.next_gate_access_type_is_boolean = ap.next_gate_access_type * ap.next_gate_access_type - ap.next_gate_access_type; + + // Putting it all together... + evals[16] = + ap.adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation * + (wire(p, WIRE.Q_O)) * + (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5 or 8 + evals[17] = ap.index_is_monotonically_increasing * (wire(p, WIRE.Q_O)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 + evals[18] = ap.next_gate_access_type_is_boolean * (wire(p, WIRE.Q_O)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 or 6 + + ap.RAM_consistency_check_identity = ap.access_check * (wire(p, WIRE.Q_O)); // deg 3 or 9 + + /** + * RAM Timestamp Consistency Check + * + * | w1 | w2 | w3 | w4 | + * | index | timestamp | timestamp_check | -- | + * + * Let delta_index = index_{i + 1} - index_{i} + * + * Iff delta_index == 0, timestamp_check = timestamp_{i + 1} - timestamp_i + * Else timestamp_check = 0 + */ + ap.timestamp_delta = wire(p, WIRE.W_R_SHIFT) - wire(p, WIRE.W_R); + ap.RAM_timestamp_check_identity = (ap.index_delta * MINUS_ONE + ONE) * ap.timestamp_delta - wire(p, WIRE.W_O); // deg 3 + + /** + * Complete Contribution 12 + * The complete RAM/ROM memory identity + * Partial degree: + */ + ap.memory_identity = ap.ROM_consistency_check_identity; // deg 3 or 6 + ap.memory_identity = ap.memory_identity + ap.RAM_timestamp_check_identity * (wire(p, WIRE.Q_4) * wire(p, WIRE.Q_L)); // deg 4 + ap.memory_identity = ap.memory_identity + ap.memory_record_check * (wire(p, WIRE.Q_M) * wire(p, WIRE.Q_L)); // deg 3 or 6 + ap.memory_identity = ap.memory_identity + ap.RAM_consistency_check_identity; // deg 3 or 9 + + // (deg 3 or 9) + (deg 4) + (deg 3) + ap.memory_identity = ap.memory_identity * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 or 10 + evals[13] = ap.memory_identity; + } + + // Constants for the Non-native Field relation + Fr constant LIMB_SIZE = Fr.wrap(uint256(1) << 68); + Fr constant SUBLIMB_SHIFT = Fr.wrap(uint256(1) << 14); + + // Parameters used within the Non-Native Field Relation + // A struct is used to work around stack too deep. This relation has alot of variables + struct NnfParams { + Fr limb_subproduct; + Fr non_native_field_gate_1; + Fr non_native_field_gate_2; + Fr non_native_field_gate_3; + Fr limb_accumulator_1; + Fr limb_accumulator_2; + Fr nnf_identity; + } + + function accumulateNnfRelation(Fr[NUMBER_OF_ENTITIES] memory p, Fr[NUMBER_OF_SUBRELATIONS] memory evals, Fr domainSep) internal pure { + NnfParams memory ap; + + /** + * Contribution 12 + * Non native field arithmetic gate 2 + * deg 4 + * + * _ _ + * / _ _ _ 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 | + * \_ _/ + * + * + */ + ap.limb_subproduct = wire(p, WIRE.W_L) * wire(p, WIRE.W_R_SHIFT) + wire(p, WIRE.W_L_SHIFT) * wire(p, WIRE.W_R); + ap.non_native_field_gate_2 = (wire(p, WIRE.W_L) * wire(p, WIRE.W_4) + wire(p, WIRE.W_R) * wire(p, WIRE.W_O) - wire(p, WIRE.W_O_SHIFT)); + ap.non_native_field_gate_2 = ap.non_native_field_gate_2 * LIMB_SIZE; + ap.non_native_field_gate_2 = ap.non_native_field_gate_2 - wire(p, WIRE.W_4_SHIFT); + ap.non_native_field_gate_2 = ap.non_native_field_gate_2 + ap.limb_subproduct; + ap.non_native_field_gate_2 = ap.non_native_field_gate_2 * wire(p, WIRE.Q_4); + + ap.limb_subproduct = ap.limb_subproduct * LIMB_SIZE; + ap.limb_subproduct = ap.limb_subproduct + (wire(p, WIRE.W_L_SHIFT) * wire(p, WIRE.W_R_SHIFT)); + ap.non_native_field_gate_1 = ap.limb_subproduct; + ap.non_native_field_gate_1 = ap.non_native_field_gate_1 - (wire(p, WIRE.W_O) + wire(p, WIRE.W_4)); + ap.non_native_field_gate_1 = ap.non_native_field_gate_1 * wire(p, WIRE.Q_O); + + ap.non_native_field_gate_3 = ap.limb_subproduct; + ap.non_native_field_gate_3 = ap.non_native_field_gate_3 + wire(p, WIRE.W_4); + ap.non_native_field_gate_3 = ap.non_native_field_gate_3 - (wire(p, WIRE.W_O_SHIFT) + wire(p, WIRE.W_4_SHIFT)); + ap.non_native_field_gate_3 = ap.non_native_field_gate_3 * wire(p, WIRE.Q_M); + + Fr non_native_field_identity = ap.non_native_field_gate_1 + ap.non_native_field_gate_2 + ap.non_native_field_gate_3; + non_native_field_identity = non_native_field_identity * wire(p, WIRE.Q_R); + + // ((((w2' * 2^14 + w1') * 2^14 + w3) * 2^14 + w2) * 2^14 + w1 - w4) * qm + // deg 2 + ap.limb_accumulator_1 = wire(p, WIRE.W_R_SHIFT) * SUBLIMB_SHIFT; + ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_L_SHIFT); + ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT; + ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_O); + ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT; + ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_R); + ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT; + ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_L); + ap.limb_accumulator_1 = ap.limb_accumulator_1 - wire(p, WIRE.W_4); + ap.limb_accumulator_1 = ap.limb_accumulator_1 * wire(p, WIRE.Q_4); + + // ((((w3' * 2^14 + w2') * 2^14 + w1') * 2^14 + w4) * 2^14 + w3 - w4') * qm + // deg 2 + ap.limb_accumulator_2 = wire(p, WIRE.W_O_SHIFT) * SUBLIMB_SHIFT; + ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_R_SHIFT); + ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT; + ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_L_SHIFT); + ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT; + ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_4); + ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT; + ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_O); + ap.limb_accumulator_2 = ap.limb_accumulator_2 - wire(p, WIRE.W_4_SHIFT); + ap.limb_accumulator_2 = ap.limb_accumulator_2 * wire(p, WIRE.Q_M); + + Fr limb_accumulator_identity = ap.limb_accumulator_1 + ap.limb_accumulator_2; + limb_accumulator_identity = limb_accumulator_identity * wire(p, WIRE.Q_O); // deg 3 + + ap.nnf_identity = non_native_field_identity + limb_accumulator_identity; + ap.nnf_identity = ap.nnf_identity * (wire(p, WIRE.Q_NNF) * domainSep); + evals[19] = ap.nnf_identity; + } + + struct PoseidonExternalParams { + Fr s1; + Fr s2; + Fr s3; + Fr s4; + Fr u1; + Fr u2; + Fr u3; + Fr u4; + Fr t0; + Fr t1; + Fr t2; + Fr t3; + Fr v1; + Fr v2; + Fr v3; + Fr v4; + Fr q_pos_by_scaling; + } + + function accumulatePoseidonExternalRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + PoseidonExternalParams memory ep; + + ep.s1 = wire(p, WIRE.W_L) + wire(p, WIRE.Q_L); + ep.s2 = wire(p, WIRE.W_R) + wire(p, WIRE.Q_R); + ep.s3 = wire(p, WIRE.W_O) + wire(p, WIRE.Q_O); + ep.s4 = wire(p, WIRE.W_4) + wire(p, WIRE.Q_4); + + ep.u1 = ep.s1 * ep.s1 * ep.s1 * ep.s1 * ep.s1; + ep.u2 = ep.s2 * ep.s2 * ep.s2 * ep.s2 * ep.s2; + ep.u3 = ep.s3 * ep.s3 * ep.s3 * ep.s3 * ep.s3; + ep.u4 = ep.s4 * ep.s4 * ep.s4 * ep.s4 * ep.s4; + // matrix mul v = M_E * u with 14 additions + ep.t0 = ep.u1 + ep.u2; // u_1 + u_2 + ep.t1 = ep.u3 + ep.u4; // u_3 + u_4 + ep.t2 = ep.u2 + ep.u2 + ep.t1; // 2u_2 + // ep.t2 += ep.t1; // 2u_2 + u_3 + u_4 + ep.t3 = ep.u4 + ep.u4 + ep.t0; // 2u_4 + // ep.t3 += ep.t0; // u_1 + u_2 + 2u_4 + ep.v4 = ep.t1 + ep.t1; + ep.v4 = ep.v4 + ep.v4 + ep.t3; + // ep.v4 += ep.t3; // u_1 + u_2 + 4u_3 + 6u_4 + ep.v2 = ep.t0 + ep.t0; + ep.v2 = ep.v2 + ep.v2 + ep.t2; + // ep.v2 += ep.t2; // 4u_1 + 6u_2 + u_3 + u_4 + ep.v1 = ep.t3 + ep.v2; // 5u_1 + 7u_2 + u_3 + 3u_4 + ep.v3 = ep.t2 + ep.v4; // u_1 + 3u_2 + 5u_3 + 7u_4 + + ep.q_pos_by_scaling = wire(p, WIRE.Q_POSEIDON2_EXTERNAL) * domainSep; + evals[20] = evals[20] + ep.q_pos_by_scaling * (ep.v1 - wire(p, WIRE.W_L_SHIFT)); + + evals[21] = evals[21] + ep.q_pos_by_scaling * (ep.v2 - wire(p, WIRE.W_R_SHIFT)); + + evals[22] = evals[22] + ep.q_pos_by_scaling * (ep.v3 - wire(p, WIRE.W_O_SHIFT)); + + evals[23] = evals[23] + ep.q_pos_by_scaling * (ep.v4 - wire(p, WIRE.W_4_SHIFT)); + } + + struct PoseidonInternalParams { + Fr u1; + Fr u2; + Fr u3; + Fr u4; + Fr u_sum; + Fr v1; + Fr v2; + Fr v3; + Fr v4; + Fr s1; + Fr q_pos_by_scaling; + } + + function accumulatePoseidonInternalRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + PoseidonInternalParams memory ip; + + Fr[4] memory INTERNAL_MATRIX_DIAGONAL = [ + FrLib.from(0x10dc6e9c006ea38b04b1e03b4bd9490c0d03f98929ca1d7fb56821fd19d3b6e7), + FrLib.from(0x0c28145b6a44df3e0149b3d0a30b3bb599df9756d4dd9b84a86b38cfb45a740b), + FrLib.from(0x00544b8338791518b2c7645a50392798b21f75bb60e3596170067d00141cac15), + FrLib.from(0x222c01175718386f2e2e82eb122789e352e105a3b8fa852613bc534433ee428b) + ]; + + // add round constants + ip.s1 = wire(p, WIRE.W_L) + wire(p, WIRE.Q_L); + + // apply s-box round + ip.u1 = ip.s1 * ip.s1 * ip.s1 * ip.s1 * ip.s1; + ip.u2 = wire(p, WIRE.W_R); + ip.u3 = wire(p, WIRE.W_O); + ip.u4 = wire(p, WIRE.W_4); + + // matrix mul with v = M_I * u 4 muls and 7 additions + ip.u_sum = ip.u1 + ip.u2 + ip.u3 + ip.u4; + + ip.q_pos_by_scaling = wire(p, WIRE.Q_POSEIDON2_INTERNAL) * domainSep; + + ip.v1 = ip.u1 * INTERNAL_MATRIX_DIAGONAL[0] + ip.u_sum; + evals[24] = evals[24] + ip.q_pos_by_scaling * (ip.v1 - wire(p, WIRE.W_L_SHIFT)); + + ip.v2 = ip.u2 * INTERNAL_MATRIX_DIAGONAL[1] + ip.u_sum; + evals[25] = evals[25] + ip.q_pos_by_scaling * (ip.v2 - wire(p, WIRE.W_R_SHIFT)); + + ip.v3 = ip.u3 * INTERNAL_MATRIX_DIAGONAL[2] + ip.u_sum; + evals[26] = evals[26] + ip.q_pos_by_scaling * (ip.v3 - wire(p, WIRE.W_O_SHIFT)); + + ip.v4 = ip.u4 * INTERNAL_MATRIX_DIAGONAL[3] + ip.u_sum; + evals[27] = evals[27] + ip.q_pos_by_scaling * (ip.v4 - wire(p, WIRE.W_4_SHIFT)); + } + + function scaleAndBatchSubrelations( + Fr[NUMBER_OF_SUBRELATIONS] memory evaluations, + Fr[NUMBER_OF_ALPHAS] memory subrelationChallenges + ) internal pure returns (Fr accumulator) { + accumulator = evaluations[0]; + + for (uint256 i = 1; i < NUMBER_OF_SUBRELATIONS; ++i) { + accumulator = accumulator + evaluations[i] * subrelationChallenges[i - 1]; + } + } } // Field arithmetic libraries - prevent littering the code with modmul / addmul library CommitmentSchemeLib { - using FrLib for Fr; - - // Avoid stack too deep - struct ShpleminiIntermediates { - Fr unshiftedScalar; - Fr shiftedScalar; - Fr unshiftedScalarNeg; - Fr shiftedScalarNeg; - // Scalar to be multiplied by [1]₁ - Fr constantTermAccumulator; - // Accumulator for powers of rho - Fr batchingChallenge; - // Linear combination of multilinear (sumcheck) evaluations and powers of rho - Fr batchedEvaluation; - Fr[4] denominators; - Fr[4] batchingScalars; - // 1/(z - r^{2^i}) for i = 0, ..., logSize, dynamically updated - Fr posInvertedDenominator; - // 1/(z + r^{2^i}) for i = 0, ..., logSize, dynamically updated - Fr negInvertedDenominator; - // ν^{2i} * 1/(z - r^{2^i}) - Fr scalingFactorPos; - // ν^{2i+1} * 1/(z + r^{2^i}) - Fr scalingFactorNeg; - // Fold_i(r^{2^i}) reconstructed by Verifier - Fr[] foldPosEvaluations; - } - - function computeSquares(Fr r, uint256 logN) internal pure returns (Fr[] memory) { - Fr[] memory squares = new Fr[](logN); - squares[0] = r; - for (uint256 i = 1; i < logN; ++i) { - squares[i] = squares[i - 1].sqr(); - } - return squares; - } - // Compute the evaluations Aₗ(r^{2ˡ}) for l = 0, ..., m-1 - - function computeFoldPosEvaluations( - Fr[CONST_PROOF_SIZE_LOG_N] memory sumcheckUChallenges, - Fr batchedEvalAccumulator, - Fr[CONST_PROOF_SIZE_LOG_N] memory geminiEvaluations, - Fr[] memory geminiEvalChallengePowers, - uint256 logSize - ) internal view returns (Fr[] memory) { - Fr[] memory foldPosEvaluations = new Fr[](logSize); - for (uint256 i = logSize; i > 0; --i) { - Fr challengePower = geminiEvalChallengePowers[i - 1]; - Fr u = sumcheckUChallenges[i - 1]; - - Fr batchedEvalRoundAcc = ( - (challengePower * batchedEvalAccumulator * Fr.wrap(2)) - - geminiEvaluations[i - 1] * (challengePower * (ONE - u) - u) - ); - // Divide by the denominator - batchedEvalRoundAcc = batchedEvalRoundAcc * (challengePower * (ONE - u) + u).invert(); - - batchedEvalAccumulator = batchedEvalRoundAcc; - foldPosEvaluations[i - 1] = batchedEvalRoundAcc; - } - return foldPosEvaluations; - } + using FrLib for Fr; + + // Avoid stack too deep + struct ShpleminiIntermediates { + Fr unshiftedScalar; + Fr shiftedScalar; + Fr unshiftedScalarNeg; + Fr shiftedScalarNeg; + // Scalar to be multiplied by [1]₁ + Fr constantTermAccumulator; + // Accumulator for powers of rho + Fr batchingChallenge; + // Linear combination of multilinear (sumcheck) evaluations and powers of rho + Fr batchedEvaluation; + Fr[4] denominators; + Fr[4] batchingScalars; + // 1/(z - r^{2^i}) for i = 0, ..., logSize, dynamically updated + Fr posInvertedDenominator; + // 1/(z + r^{2^i}) for i = 0, ..., logSize, dynamically updated + Fr negInvertedDenominator; + // ν^{2i} * 1/(z - r^{2^i}) + Fr scalingFactorPos; + // ν^{2i+1} * 1/(z + r^{2^i}) + Fr scalingFactorNeg; + // Fold_i(r^{2^i}) reconstructed by Verifier + Fr[] foldPosEvaluations; + } + + function computeSquares(Fr r, uint256 logN) internal pure returns (Fr[] memory) { + Fr[] memory squares = new Fr[](logN); + squares[0] = r; + for (uint256 i = 1; i < logN; ++i) { + squares[i] = squares[i - 1].sqr(); + } + return squares; + } + // Compute the evaluations Aₗ(r^{2ˡ}) for l = 0, ..., m-1 + + function computeFoldPosEvaluations( + Fr[CONST_PROOF_SIZE_LOG_N] memory sumcheckUChallenges, + Fr batchedEvalAccumulator, + Fr[CONST_PROOF_SIZE_LOG_N] memory geminiEvaluations, + Fr[] memory geminiEvalChallengePowers, + uint256 logSize + ) internal view returns (Fr[] memory) { + Fr[] memory foldPosEvaluations = new Fr[](logSize); + for (uint256 i = logSize; i > 0; --i) { + Fr challengePower = geminiEvalChallengePowers[i - 1]; + Fr u = sumcheckUChallenges[i - 1]; + + Fr batchedEvalRoundAcc = ((challengePower * batchedEvalAccumulator * Fr.wrap(2)) - + geminiEvaluations[i - 1] * + (challengePower * (ONE - u) - u)); + // Divide by the denominator + batchedEvalRoundAcc = batchedEvalRoundAcc * (challengePower * (ONE - u) + u).invert(); + + batchedEvalAccumulator = batchedEvalRoundAcc; + foldPosEvaluations[i - 1] = batchedEvalRoundAcc; + } + return foldPosEvaluations; + } } uint256 constant Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; // EC group order. F_q function bytes32ToString(bytes32 value) pure returns (string memory result) { - bytes memory alphabet = "0123456789abcdef"; - - bytes memory str = new bytes(66); - str[0] = "0"; - str[1] = "x"; - for (uint256 i = 0; i < 32; i++) { - str[2 + i * 2] = alphabet[uint8(value[i] >> 4)]; - str[3 + i * 2] = alphabet[uint8(value[i] & 0x0f)]; - } - result = string(str); + bytes memory alphabet = "0123456789abcdef"; + + bytes memory str = new bytes(66); + str[0] = "0"; + str[1] = "x"; + for (uint256 i = 0; i < 32; i++) { + str[2 + i * 2] = alphabet[uint8(value[i] >> 4)]; + str[3 + i * 2] = alphabet[uint8(value[i] & 0x0f)]; + } + result = string(str); } // Fr utility function bytesToFr(bytes calldata proofSection) pure returns (Fr scalar) { - scalar = FrLib.fromBytes32(bytes32(proofSection)); + scalar = FrLib.fromBytes32(bytes32(proofSection)); } // EC Point utilities function bytesToG1Point(bytes calldata proofSection) pure returns (Honk.G1Point memory point) { - point = Honk.G1Point({ - x: uint256(bytes32(proofSection[0x00:0x20])) % Q, - y: uint256(bytes32(proofSection[0x20:0x40])) % Q - }); + point = Honk.G1Point({ x: uint256(bytes32(proofSection[0x00:0x20])) % Q, y: uint256(bytes32(proofSection[0x20:0x40])) % Q }); } function negateInplace(Honk.G1Point memory point) pure returns (Honk.G1Point memory) { - point.y = (Q - point.y) % Q; - return point; + point.y = (Q - point.y) % Q; + return point; } /** @@ -1648,33 +1641,32 @@ function negateInplace(Honk.G1Point memory point) pure returns (Honk.G1Point mem * @return lhs * @return rhs */ -function convertPairingPointsToG1(Fr[PAIRING_POINTS_SIZE] memory pairingPoints) - pure - returns (Honk.G1Point memory lhs, Honk.G1Point memory rhs) -{ - uint256 lhsX = Fr.unwrap(pairingPoints[0]); - lhsX |= Fr.unwrap(pairingPoints[1]) << 68; - lhsX |= Fr.unwrap(pairingPoints[2]) << 136; - lhsX |= Fr.unwrap(pairingPoints[3]) << 204; - lhs.x = lhsX; - - uint256 lhsY = Fr.unwrap(pairingPoints[4]); - lhsY |= Fr.unwrap(pairingPoints[5]) << 68; - lhsY |= Fr.unwrap(pairingPoints[6]) << 136; - lhsY |= Fr.unwrap(pairingPoints[7]) << 204; - lhs.y = lhsY; - - uint256 rhsX = Fr.unwrap(pairingPoints[8]); - rhsX |= Fr.unwrap(pairingPoints[9]) << 68; - rhsX |= Fr.unwrap(pairingPoints[10]) << 136; - rhsX |= Fr.unwrap(pairingPoints[11]) << 204; - rhs.x = rhsX; - - uint256 rhsY = Fr.unwrap(pairingPoints[12]); - rhsY |= Fr.unwrap(pairingPoints[13]) << 68; - rhsY |= Fr.unwrap(pairingPoints[14]) << 136; - rhsY |= Fr.unwrap(pairingPoints[15]) << 204; - rhs.y = rhsY; +function convertPairingPointsToG1( + Fr[PAIRING_POINTS_SIZE] memory pairingPoints +) pure returns (Honk.G1Point memory lhs, Honk.G1Point memory rhs) { + uint256 lhsX = Fr.unwrap(pairingPoints[0]); + lhsX |= Fr.unwrap(pairingPoints[1]) << 68; + lhsX |= Fr.unwrap(pairingPoints[2]) << 136; + lhsX |= Fr.unwrap(pairingPoints[3]) << 204; + lhs.x = lhsX; + + uint256 lhsY = Fr.unwrap(pairingPoints[4]); + lhsY |= Fr.unwrap(pairingPoints[5]) << 68; + lhsY |= Fr.unwrap(pairingPoints[6]) << 136; + lhsY |= Fr.unwrap(pairingPoints[7]) << 204; + lhs.y = lhsY; + + uint256 rhsX = Fr.unwrap(pairingPoints[8]); + rhsX |= Fr.unwrap(pairingPoints[9]) << 68; + rhsX |= Fr.unwrap(pairingPoints[10]) << 136; + rhsX |= Fr.unwrap(pairingPoints[11]) << 204; + rhs.x = rhsX; + + uint256 rhsY = Fr.unwrap(pairingPoints[12]); + rhsY |= Fr.unwrap(pairingPoints[13]) << 68; + rhsY |= Fr.unwrap(pairingPoints[14]) << 136; + rhsY |= Fr.unwrap(pairingPoints[15]) << 204; + rhs.y = rhsY; } /** @@ -1686,32 +1678,32 @@ function convertPairingPointsToG1(Fr[PAIRING_POINTS_SIZE] memory pairingPoints) * @return recursionSeparator The recursion separator - generated from hashing the above. */ function generateRecursionSeparator( - Fr[PAIRING_POINTS_SIZE] memory proofPairingPoints, - Honk.G1Point memory accLhs, - Honk.G1Point memory accRhs + Fr[PAIRING_POINTS_SIZE] memory proofPairingPoints, + Honk.G1Point memory accLhs, + Honk.G1Point memory accRhs ) pure returns (Fr recursionSeparator) { - // hash the proof aggregated X - // hash the proof aggregated Y - // hash the accum X - // hash the accum Y + // hash the proof aggregated X + // hash the proof aggregated Y + // hash the accum X + // hash the accum Y - (Honk.G1Point memory proofLhs, Honk.G1Point memory proofRhs) = convertPairingPointsToG1(proofPairingPoints); + (Honk.G1Point memory proofLhs, Honk.G1Point memory proofRhs) = convertPairingPointsToG1(proofPairingPoints); - uint256[8] memory recursionSeparatorElements; + uint256[8] memory recursionSeparatorElements; - // Proof points - recursionSeparatorElements[0] = proofLhs.x; - recursionSeparatorElements[1] = proofLhs.y; - recursionSeparatorElements[2] = proofRhs.x; - recursionSeparatorElements[3] = proofRhs.y; + // Proof points + recursionSeparatorElements[0] = proofLhs.x; + recursionSeparatorElements[1] = proofLhs.y; + recursionSeparatorElements[2] = proofRhs.x; + recursionSeparatorElements[3] = proofRhs.y; - // Accumulator points - recursionSeparatorElements[4] = accLhs.x; - recursionSeparatorElements[5] = accLhs.y; - recursionSeparatorElements[6] = accRhs.x; - recursionSeparatorElements[7] = accRhs.y; + // Accumulator points + recursionSeparatorElements[4] = accLhs.x; + recursionSeparatorElements[5] = accLhs.y; + recursionSeparatorElements[6] = accRhs.x; + recursionSeparatorElements[7] = accRhs.y; - recursionSeparator = FrLib.fromBytes32(keccak256(abi.encodePacked(recursionSeparatorElements))); + recursionSeparator = FrLib.fromBytes32(keccak256(abi.encodePacked(recursionSeparatorElements))); } /** @@ -1723,16 +1715,17 @@ function generateRecursionSeparator( * @param recursionSeperator The separator to use for the multiplication. * @return `(recursionSeperator * basePoint) + other`. */ -function mulWithSeperator(Honk.G1Point memory basePoint, Honk.G1Point memory other, Fr recursionSeperator) - view - returns (Honk.G1Point memory) -{ - Honk.G1Point memory result; +function mulWithSeperator( + Honk.G1Point memory basePoint, + Honk.G1Point memory other, + Fr recursionSeperator +) view returns (Honk.G1Point memory) { + Honk.G1Point memory result; - result = ecMul(recursionSeperator, basePoint); - result = ecAdd(result, other); + result = ecMul(recursionSeperator, basePoint); + result = ecAdd(result, other); - return result; + return result; } /** @@ -1744,41 +1737,41 @@ function mulWithSeperator(Honk.G1Point memory basePoint, Honk.G1Point memory oth * @return result The result of the multiplication. */ function ecMul(Fr value, Honk.G1Point memory point) view returns (Honk.G1Point memory) { - Honk.G1Point memory result; - - assembly { - let free := mload(0x40) - // Write the point into memory (two 32 byte words) - // Memory layout: - // Address | value - // free | point.x - // free + 0x20| point.y - mstore(free, mload(point)) - mstore(add(free, 0x20), mload(add(point, 0x20))) - // Write the scalar into memory (one 32 byte word) - // Memory layout: - // Address | value - // free + 0x40| value - mstore(add(free, 0x40), value) - - // Call the ecMul precompile, it takes in the following - // [point.x, point.y, scalar], and returns the result back into the free memory location. - let success := staticcall(gas(), 0x07, free, 0x60, free, 0x40) - if iszero(success) { - revert(0, 0) - } - // Copy the result of the multiplication back into the result memory location. - // Memory layout: - // Address | value - // result | result.x - // result + 0x20| result.y - mstore(result, mload(free)) - mstore(add(result, 0x20), mload(add(free, 0x20))) - - mstore(0x40, add(free, 0x60)) - } - - return result; + Honk.G1Point memory result; + + assembly { + let free := mload(0x40) + // Write the point into memory (two 32 byte words) + // Memory layout: + // Address | value + // free | point.x + // free + 0x20| point.y + mstore(free, mload(point)) + mstore(add(free, 0x20), mload(add(point, 0x20))) + // Write the scalar into memory (one 32 byte word) + // Memory layout: + // Address | value + // free + 0x40| value + mstore(add(free, 0x40), value) + + // Call the ecMul precompile, it takes in the following + // [point.x, point.y, scalar], and returns the result back into the free memory location. + let success := staticcall(gas(), 0x07, free, 0x60, free, 0x40) + if iszero(success) { + revert(0, 0) + } + // Copy the result of the multiplication back into the result memory location. + // Memory layout: + // Address | value + // result | result.x + // result + 0x20| result.y + mstore(result, mload(free)) + mstore(add(result, 0x20), mload(add(free, 0x20))) + + mstore(0x40, add(free, 0x60)) + } + + return result; } /** @@ -1790,649 +1783,637 @@ function ecMul(Fr value, Honk.G1Point memory point) view returns (Honk.G1Point m * @return result The result of the addition. */ function ecAdd(Honk.G1Point memory lhs, Honk.G1Point memory rhs) view returns (Honk.G1Point memory) { - Honk.G1Point memory result; - - assembly { - let free := mload(0x40) - // Write lhs into memory (two 32 byte words) - // Memory layout: - // Address | value - // free | lhs.x - // free + 0x20| lhs.y - mstore(free, mload(lhs)) - mstore(add(free, 0x20), mload(add(lhs, 0x20))) - - // Write rhs into memory (two 32 byte words) - // Memory layout: - // Address | value - // free + 0x40| rhs.x - // free + 0x60| rhs.y - mstore(add(free, 0x40), mload(rhs)) - mstore(add(free, 0x60), mload(add(rhs, 0x20))) - - // Call the ecAdd precompile, it takes in the following - // [lhs.x, lhs.y, rhs.x, rhs.y], and returns their addition back into the free memory location. - let success := staticcall(gas(), 0x06, free, 0x80, free, 0x40) - if iszero(success) { revert(0, 0) } - - // Copy the result of the addition back into the result memory location. - // Memory layout: - // Address | value - // result | result.x - // result + 0x20| result.y - mstore(result, mload(free)) - mstore(add(result, 0x20), mload(add(free, 0x20))) - - mstore(0x40, add(free, 0x80)) - } - - return result; + Honk.G1Point memory result; + + assembly { + let free := mload(0x40) + // Write lhs into memory (two 32 byte words) + // Memory layout: + // Address | value + // free | lhs.x + // free + 0x20| lhs.y + mstore(free, mload(lhs)) + mstore(add(free, 0x20), mload(add(lhs, 0x20))) + + // Write rhs into memory (two 32 byte words) + // Memory layout: + // Address | value + // free + 0x40| rhs.x + // free + 0x60| rhs.y + mstore(add(free, 0x40), mload(rhs)) + mstore(add(free, 0x60), mload(add(rhs, 0x20))) + + // Call the ecAdd precompile, it takes in the following + // [lhs.x, lhs.y, rhs.x, rhs.y], and returns their addition back into the free memory location. + let success := staticcall(gas(), 0x06, free, 0x80, free, 0x40) + if iszero(success) { + revert(0, 0) + } + + // Copy the result of the addition back into the result memory location. + // Memory layout: + // Address | value + // result | result.x + // result + 0x20| result.y + mstore(result, mload(free)) + mstore(add(result, 0x20), mload(add(free, 0x20))) + + mstore(0x40, add(free, 0x80)) + } + + return result; } function validateOnCurve(Honk.G1Point memory point) pure { - uint256 x = point.x; - uint256 y = point.y; + uint256 x = point.x; + uint256 y = point.y; - bool success = false; - assembly { - let xx := mulmod(x, x, Q) - success := eq(mulmod(y, y, Q), addmod(mulmod(x, xx, Q), 3, Q)) - } + bool success = false; + assembly { + let xx := mulmod(x, x, Q) + success := eq(mulmod(y, y, Q), addmod(mulmod(x, xx, Q), 3, Q)) + } - require(success, "point is not on the curve"); + require(success, "point is not on the curve"); } function pairing(Honk.G1Point memory rhs, Honk.G1Point memory lhs) view returns (bool decodedResult) { - bytes memory input = abi.encodePacked( - rhs.x, - rhs.y, - // Fixed G2 point - uint256(0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2), - uint256(0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed), - uint256(0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b), - uint256(0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa), - lhs.x, - lhs.y, - // G2 point from VK - uint256(0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1), - uint256(0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0), - uint256(0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4), - uint256(0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55) - ); - - (bool success, bytes memory result) = address(0x08).staticcall(input); - decodedResult = success && abi.decode(result, (bool)); + bytes memory input = abi.encodePacked( + rhs.x, + rhs.y, + // Fixed G2 point + uint256(0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2), + uint256(0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed), + uint256(0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b), + uint256(0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa), + lhs.x, + lhs.y, + // G2 point from VK + uint256(0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1), + uint256(0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0), + uint256(0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4), + uint256(0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55) + ); + + (bool success, bytes memory result) = address(0x08).staticcall(input); + decodedResult = success && abi.decode(result, (bool)); } // Field arithmetic libraries - prevent littering the code with modmul / addmul +abstract contract BaseZKHonkVerifier is IVerifier { + using FrLib for Fr; + + uint256 immutable $N; + uint256 immutable $LOG_N; + uint256 immutable $VK_HASH; + uint256 immutable $NUM_PUBLIC_INPUTS; + + constructor(uint256 _N, uint256 _logN, uint256 _vkHash, uint256 _numPublicInputs) { + $N = _N; + $LOG_N = _logN; + $VK_HASH = _vkHash; + $NUM_PUBLIC_INPUTS = _numPublicInputs; + } + + // Errors + error ProofLengthWrong(); + error ProofLengthWrongWithLogN(uint256 logN, uint256 actualLength, uint256 expectedLength); + error PublicInputsLengthWrong(); + error SumcheckFailed(); + error ShpleminiFailed(); + error GeminiChallengeInSubgroup(); + error ConsistencyCheckFailed(); + + // Constants for proof length calculation (matching UltraKeccakZKFlavor) + uint256 constant NUM_WITNESS_ENTITIES = 8; + uint256 constant NUM_ELEMENTS_COMM = 2; // uint256 elements for curve points + uint256 constant NUM_ELEMENTS_FR = 1; // uint256 elements for field elements + uint256 constant NUM_LIBRA_EVALUATIONS = 4; // libra evaluations + + // Calculate proof size based on log_n (matching UltraKeccakZKFlavor formula) + function calculateProofSize(uint256 logN) internal pure returns (uint256) { + // Witness and Libra commitments + uint256 proofLength = NUM_WITNESS_ENTITIES * NUM_ELEMENTS_COMM; // witness commitments + proofLength += NUM_ELEMENTS_COMM * 4; // Libra concat, grand sum, quotient comms + Gemini masking + // Sumcheck + proofLength += logN * ZK_BATCHED_RELATION_PARTIAL_LENGTH * NUM_ELEMENTS_FR; // sumcheck univariates + proofLength += NUMBER_OF_ENTITIES * NUM_ELEMENTS_FR; // sumcheck evaluations + // Libra and Gemini + proofLength += NUM_ELEMENTS_FR * 3; // Libra sum, claimed eval, Gemini masking eval + proofLength += logN * NUM_ELEMENTS_FR; // Gemini a evaluations + proofLength += NUM_LIBRA_EVALUATIONS * NUM_ELEMENTS_FR; // libra evaluations -abstract contract BaseZKHonkVerifier is IVerifier { - using FrLib for Fr; + // PCS commitments + proofLength += (logN - 1) * NUM_ELEMENTS_COMM; // Gemini Fold commitments + proofLength += NUM_ELEMENTS_COMM * 2; // Shplonk Q and KZG W commitments + + // Pairing points + proofLength += PAIRING_POINTS_SIZE; // pairing inputs carried on public inputs - uint256 immutable $N; - uint256 immutable $LOG_N; - uint256 immutable $VK_HASH; - uint256 immutable $NUM_PUBLIC_INPUTS; + return proofLength; + } - constructor(uint256 _N, uint256 _logN, uint256 _vkHash, uint256 _numPublicInputs) { - $N = _N; - $LOG_N = _logN; - $VK_HASH = _vkHash; - $NUM_PUBLIC_INPUTS = _numPublicInputs; + uint256 constant SHIFTED_COMMITMENTS_START = 30; + + function loadVerificationKey() internal pure virtual returns (Honk.VerificationKey memory); + + function verify(bytes calldata proof, bytes32[] calldata publicInputs) public view override returns (bool verified) { + // Calculate expected proof size based on $LOG_N + uint256 expectedProofSize = calculateProofSize($LOG_N); + + // Check the received proof is the expected size where each field element is 32 bytes + if (proof.length != expectedProofSize * 32) { + revert ProofLengthWrongWithLogN($LOG_N, proof.length, expectedProofSize * 32); } - // Errors - error ProofLengthWrong(); - error ProofLengthWrongWithLogN(uint256 logN, uint256 actualLength, uint256 expectedLength); - error PublicInputsLengthWrong(); - error SumcheckFailed(); - error ShpleminiFailed(); - error GeminiChallengeInSubgroup(); - error ConsistencyCheckFailed(); + Honk.VerificationKey memory vk = loadVerificationKey(); + Honk.ZKProof memory p = ZKTranscriptLib.loadProof(proof, $LOG_N); - // Constants for proof length calculation (matching UltraKeccakZKFlavor) - uint256 constant NUM_WITNESS_ENTITIES = 8; - uint256 constant NUM_ELEMENTS_COMM = 2; // uint256 elements for curve points - uint256 constant NUM_ELEMENTS_FR = 1; // uint256 elements for field elements - uint256 constant NUM_LIBRA_EVALUATIONS = 4; // libra evaluations + if (publicInputs.length != vk.publicInputsSize - PAIRING_POINTS_SIZE) { + revert PublicInputsLengthWrong(); + } - // Calculate proof size based on log_n (matching UltraKeccakZKFlavor formula) - function calculateProofSize(uint256 logN) internal pure returns (uint256) { - // Witness and Libra commitments - uint256 proofLength = NUM_WITNESS_ENTITIES * NUM_ELEMENTS_COMM; // witness commitments - proofLength += NUM_ELEMENTS_COMM * 4; // Libra concat, grand sum, quotient comms + Gemini masking + // Generate the fiat shamir challenges for the whole protocol + ZKTranscript memory t = ZKTranscriptLib.generateTranscript(p, publicInputs, $VK_HASH, $NUM_PUBLIC_INPUTS, $LOG_N); - // Sumcheck - proofLength += logN * ZK_BATCHED_RELATION_PARTIAL_LENGTH * NUM_ELEMENTS_FR; // sumcheck univariates - proofLength += NUMBER_OF_ENTITIES * NUM_ELEMENTS_FR; // sumcheck evaluations + // Derive public input delta + t.relationParameters.publicInputsDelta = computePublicInputDelta( + publicInputs, + p.pairingPointObject, + t.relationParameters.beta, + t.relationParameters.gamma /*pubInputsOffset=*/, + 1 + ); - // Libra and Gemini - proofLength += NUM_ELEMENTS_FR * 3; // Libra sum, claimed eval, Gemini masking eval - proofLength += logN * NUM_ELEMENTS_FR; // Gemini a evaluations - proofLength += NUM_LIBRA_EVALUATIONS * NUM_ELEMENTS_FR; // libra evaluations + // Sumcheck + if (!verifySumcheck(p, t)) revert SumcheckFailed(); - // PCS commitments - proofLength += (logN - 1) * NUM_ELEMENTS_COMM; // Gemini Fold commitments - proofLength += NUM_ELEMENTS_COMM * 2; // Shplonk Q and KZG W commitments + if (!verifyShplemini(p, vk, t)) revert ShpleminiFailed(); - // Pairing points - proofLength += PAIRING_POINTS_SIZE; // pairing inputs carried on public inputs + verified = true; + } - return proofLength; - } + uint256 constant PERMUTATION_ARGUMENT_VALUE_SEPARATOR = 1 << 28; - uint256 constant SHIFTED_COMMITMENTS_START = 30; + function computePublicInputDelta( + bytes32[] memory publicInputs, + Fr[PAIRING_POINTS_SIZE] memory pairingPointObject, + Fr beta, + Fr gamma, + uint256 offset + ) internal view returns (Fr publicInputDelta) { + Fr numerator = Fr.wrap(1); + Fr denominator = Fr.wrap(1); - function loadVerificationKey() internal pure virtual returns (Honk.VerificationKey memory); + Fr numeratorAcc = gamma + (beta * FrLib.from(PERMUTATION_ARGUMENT_VALUE_SEPARATOR + offset)); + Fr denominatorAcc = gamma - (beta * FrLib.from(offset + 1)); - function verify(bytes calldata proof, bytes32[] calldata publicInputs) - public - view - override - returns (bool verified) { - // Calculate expected proof size based on $LOG_N - uint256 expectedProofSize = calculateProofSize($LOG_N); + for (uint256 i = 0; i < $NUM_PUBLIC_INPUTS - PAIRING_POINTS_SIZE; i++) { + Fr pubInput = FrLib.fromBytes32(publicInputs[i]); + + numerator = numerator * (numeratorAcc + pubInput); + denominator = denominator * (denominatorAcc + pubInput); + + numeratorAcc = numeratorAcc + beta; + denominatorAcc = denominatorAcc - beta; + } - // Check the received proof is the expected size where each field element is 32 bytes - if (proof.length != expectedProofSize * 32) { - revert ProofLengthWrongWithLogN($LOG_N, proof.length, expectedProofSize * 32); - } + for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) { + Fr pubInput = pairingPointObject[i]; - Honk.VerificationKey memory vk = loadVerificationKey(); - Honk.ZKProof memory p = ZKTranscriptLib.loadProof(proof, $LOG_N); + numerator = numerator * (numeratorAcc + pubInput); + denominator = denominator * (denominatorAcc + pubInput); - if (publicInputs.length != vk.publicInputsSize - PAIRING_POINTS_SIZE) { - revert PublicInputsLengthWrong(); - } + numeratorAcc = numeratorAcc + beta; + denominatorAcc = denominatorAcc - beta; + } + } - // Generate the fiat shamir challenges for the whole protocol - ZKTranscript memory t = - ZKTranscriptLib.generateTranscript(p, publicInputs, $VK_HASH, $NUM_PUBLIC_INPUTS, $LOG_N); + // Fr delta = numerator / denominator; // TOOO: batch invert later? + publicInputDelta = FrLib.div(numerator, denominator); + } - // Derive public input delta - t.relationParameters.publicInputsDelta = computePublicInputDelta( - publicInputs, - p.pairingPointObject, - t.relationParameters.beta, - t.relationParameters.gamma, /*pubInputsOffset=*/ - 1 - ); + function verifySumcheck(Honk.ZKProof memory proof, ZKTranscript memory tp) internal view returns (bool verified) { + Fr roundTargetSum = tp.libraChallenge * proof.libraSum; // default 0 + Fr powPartialEvaluation = Fr.wrap(1); - // Sumcheck - if (!verifySumcheck(p, t)) revert SumcheckFailed(); + // We perform sumcheck reductions over log n rounds ( the multivariate degree ) + for (uint256 round; round < $LOG_N; ++round) { + Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory roundUnivariate = proof.sumcheckUnivariates[round]; + Fr totalSum = roundUnivariate[0] + roundUnivariate[1]; + if (totalSum != roundTargetSum) revert SumcheckFailed(); - if (!verifyShplemini(p, vk, t)) revert ShpleminiFailed(); + Fr roundChallenge = tp.sumCheckUChallenges[round]; - verified = true; + // Update the round target for the next rounf + roundTargetSum = computeNextTargetSum(roundUnivariate, roundChallenge); + powPartialEvaluation = powPartialEvaluation * (Fr.wrap(1) + roundChallenge * (tp.gateChallenges[round] - Fr.wrap(1))); } - uint256 constant PERMUTATION_ARGUMENT_VALUE_SEPARATOR = 1 << 28; + // Last round + Fr grandHonkRelationSum = RelationsLib.accumulateRelationEvaluations( + proof.sumcheckEvaluations, + tp.relationParameters, + tp.alphas, + powPartialEvaluation + ); + + Fr evaluation = Fr.wrap(1); + for (uint256 i = 2; i < $LOG_N; i++) { + evaluation = evaluation * tp.sumCheckUChallenges[i]; + } + + grandHonkRelationSum = grandHonkRelationSum * (Fr.wrap(1) - evaluation) + proof.libraEvaluation * tp.libraChallenge; + verified = (grandHonkRelationSum == roundTargetSum); + } + + // Return the new target sum for the next sumcheck round + function computeNextTargetSum( + Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory roundUnivariates, + Fr roundChallenge + ) internal view returns (Fr targetSum) { + Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory BARYCENTRIC_LAGRANGE_DENOMINATORS = [ + Fr.wrap(0x0000000000000000000000000000000000000000000000000000000000009d80), + Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffec51), + Fr.wrap(0x00000000000000000000000000000000000000000000000000000000000005a0), + Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593effffd31), + Fr.wrap(0x0000000000000000000000000000000000000000000000000000000000000240), + Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593effffd31), + Fr.wrap(0x00000000000000000000000000000000000000000000000000000000000005a0), + Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffec51), + Fr.wrap(0x0000000000000000000000000000000000000000000000000000000000009d80) + ]; + + // To compute the next target sum, we evaluate the given univariate at a point u (challenge). + + // Performing Barycentric evaluations + // Compute B(x) + Fr numeratorValue = Fr.wrap(1); + for (uint256 i = 0; i < ZK_BATCHED_RELATION_PARTIAL_LENGTH; ++i) { + numeratorValue = numeratorValue * (roundChallenge - Fr.wrap(i)); + } + + Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory denominatorInverses; + for (uint256 i = 0; i < ZK_BATCHED_RELATION_PARTIAL_LENGTH; ++i) { + denominatorInverses[i] = FrLib.invert(BARYCENTRIC_LAGRANGE_DENOMINATORS[i] * (roundChallenge - Fr.wrap(i))); + } + + for (uint256 i = 0; i < ZK_BATCHED_RELATION_PARTIAL_LENGTH; ++i) { + targetSum = targetSum + roundUnivariates[i] * denominatorInverses[i]; + } + + // Scale the sum by the value of B(x) + targetSum = targetSum * numeratorValue; + } + + uint256 constant LIBRA_COMMITMENTS = 3; + uint256 constant LIBRA_EVALUATIONS = 4; + uint256 constant LIBRA_UNIVARIATES_LENGTH = 9; + + struct PairingInputs { + Honk.G1Point P_0; + Honk.G1Point P_1; + } + + function verifyShplemini( + Honk.ZKProof memory proof, + Honk.VerificationKey memory vk, + ZKTranscript memory tp + ) internal view returns (bool verified) { + CommitmentSchemeLib.ShpleminiIntermediates memory mem; // stack + + // - Compute vector (r, r², ... , r²⁽ⁿ⁻¹⁾), where n = log_circuit_size + Fr[] memory powers_of_evaluation_challenge = CommitmentSchemeLib.computeSquares(tp.geminiR, $LOG_N); + // Arrays hold values that will be linearly combined for the gemini and shplonk batch openings + Fr[] memory scalars = new Fr[](NUMBER_UNSHIFTED + $LOG_N + LIBRA_COMMITMENTS + 3); + Honk.G1Point[] memory commitments = new Honk.G1Point[](NUMBER_UNSHIFTED + $LOG_N + LIBRA_COMMITMENTS + 3); + + mem.posInvertedDenominator = (tp.shplonkZ - powers_of_evaluation_challenge[0]).invert(); + mem.negInvertedDenominator = (tp.shplonkZ + powers_of_evaluation_challenge[0]).invert(); + + mem.unshiftedScalar = mem.posInvertedDenominator + (tp.shplonkNu * mem.negInvertedDenominator); + mem.shiftedScalar = tp.geminiR.invert() * (mem.posInvertedDenominator - (tp.shplonkNu * mem.negInvertedDenominator)); + + scalars[0] = Fr.wrap(1); + commitments[0] = proof.shplonkQ; + + /* Batch multivariate opening claims, shifted and unshifted + * The vector of scalars is populated as follows: + * \f[ + * \left( + * - \left(\frac{1}{z-r} + \nu \times \frac{1}{z+r}\right), + * \ldots, + * - \rho^{i+k-1} \times \left(\frac{1}{z-r} + \nu \times \frac{1}{z+r}\right), + * - \rho^{i+k} \times \frac{1}{r} \times \left(\frac{1}{z-r} - \nu \times \frac{1}{z+r}\right), + * \ldots, + * - \rho^{k+m-1} \times \frac{1}{r} \times \left(\frac{1}{z-r} - \nu \times \frac{1}{z+r}\right) + * \right) + * \f] + * + * The following vector is concatenated to the vector of commitments: + * \f[ + * f_0, \ldots, f_{m-1}, f_{\text{shift}, 0}, \ldots, f_{\text{shift}, k-1} + * \f] + * + * Simultaneously, the evaluation of the multilinear polynomial + * \f[ + * \sum \rho^i \cdot f_i + \sum \rho^{i+k} \cdot f_{\text{shift}, i} + * \f] + * at the challenge point \f$ (u_0,\ldots, u_{n-1}) \f$ is computed. + * + * This approach minimizes the number of iterations over the commitments to multilinear polynomials + * and eliminates the need to store the powers of \f$ \rho \f$. + */ + mem.batchedEvaluation = proof.geminiMaskingEval; + mem.batchingChallenge = tp.rho; + mem.unshiftedScalarNeg = mem.unshiftedScalar.neg(); + mem.shiftedScalarNeg = mem.shiftedScalar.neg(); + + scalars[1] = mem.unshiftedScalarNeg; + for (uint256 i = 0; i < NUMBER_UNSHIFTED; ++i) { + scalars[i + 2] = mem.unshiftedScalarNeg * mem.batchingChallenge; + mem.batchedEvaluation = mem.batchedEvaluation + (proof.sumcheckEvaluations[i] * mem.batchingChallenge); + mem.batchingChallenge = mem.batchingChallenge * tp.rho; + } + // g commitments are accumulated at r + // For each of the to be shifted commitments perform the shift in place by + // adding to the unshifted value. + // We do so, as the values are to be used in batchMul later, and as + // `a * c + b * c = (a + b) * c` this will allow us to reduce memory and compute. + // Applied to w1, w2, w3, w4 and zPerm + for (uint256 i = 0; i < NUMBER_TO_BE_SHIFTED; ++i) { + uint256 scalarOff = i + SHIFTED_COMMITMENTS_START; + uint256 evaluationOff = i + NUMBER_UNSHIFTED; + + scalars[scalarOff] = scalars[scalarOff] + (mem.shiftedScalarNeg * mem.batchingChallenge); + mem.batchedEvaluation = mem.batchedEvaluation + (proof.sumcheckEvaluations[evaluationOff] * mem.batchingChallenge); + mem.batchingChallenge = mem.batchingChallenge * tp.rho; + } + + commitments[1] = proof.geminiMaskingPoly; + + commitments[2] = vk.qm; + commitments[3] = vk.qc; + commitments[4] = vk.ql; + commitments[5] = vk.qr; + commitments[6] = vk.qo; + commitments[7] = vk.q4; + commitments[8] = vk.qLookup; + commitments[9] = vk.qArith; + commitments[10] = vk.qDeltaRange; + commitments[11] = vk.qElliptic; + commitments[12] = vk.qMemory; + commitments[13] = vk.qNnf; + commitments[14] = vk.qPoseidon2External; + commitments[15] = vk.qPoseidon2Internal; + commitments[16] = vk.s1; + commitments[17] = vk.s2; + commitments[18] = vk.s3; + commitments[19] = vk.s4; + commitments[20] = vk.id1; + commitments[21] = vk.id2; + commitments[22] = vk.id3; + commitments[23] = vk.id4; + commitments[24] = vk.t1; + commitments[25] = vk.t2; + commitments[26] = vk.t3; + commitments[27] = vk.t4; + commitments[28] = vk.lagrangeFirst; + commitments[29] = vk.lagrangeLast; + + // Accumulate proof points + commitments[30] = proof.w1; + commitments[31] = proof.w2; + commitments[32] = proof.w3; + commitments[33] = proof.w4; + commitments[34] = proof.zPerm; + commitments[35] = proof.lookupInverses; + commitments[36] = proof.lookupReadCounts; + commitments[37] = proof.lookupReadTags; + + /* Batch gemini claims from the prover + * place the commitments to gemini aᵢ to the vector of commitments, compute the contributions from + * aᵢ(−r²ⁱ) for i=1, … , n−1 to the constant term accumulator, add corresponding scalars + * + * 1. Moves the vector + * \f[ + * \left( \text{com}(A_1), \text{com}(A_2), \ldots, \text{com}(A_{n-1}) \right) + * \f] + * to the 'commitments' vector. + * + * 2. Computes the scalars: + * \f[ + * \frac{\nu^{2}}{z + r^2}, \frac{\nu^3}{z + r^4}, \ldots, \frac{\nu^{n-1}}{z + r^{2^{n-1}}} + * \f] + * and places them into the 'scalars' vector. + * + * 3. Accumulates the summands of the constant term: + * \f[ + * \sum_{i=2}^{n-1} \frac{\nu^{i} \cdot A_i(-r^{2^i})}{z + r^{2^i}} + * \f] + * and adds them to the 'constant_term_accumulator'. + */ + + // Add contributions from A₀(r) and A₀(-r) to constant_term_accumulator: + // Compute the evaluations Aₗ(r^{2ˡ}) for l = 0, ..., $LOG_N - 1 + Fr[] memory foldPosEvaluations = CommitmentSchemeLib.computeFoldPosEvaluations( + tp.sumCheckUChallenges, + mem.batchedEvaluation, + proof.geminiAEvaluations, + powers_of_evaluation_challenge, + $LOG_N + ); - function computePublicInputDelta( - bytes32[] memory publicInputs, - Fr[PAIRING_POINTS_SIZE] memory pairingPointObject, - Fr beta, - Fr gamma, - uint256 offset - ) internal view returns (Fr publicInputDelta) { - Fr numerator = Fr.wrap(1); - Fr denominator = Fr.wrap(1); + mem.constantTermAccumulator = foldPosEvaluations[0] * mem.posInvertedDenominator; + mem.constantTermAccumulator = mem.constantTermAccumulator + (proof.geminiAEvaluations[0] * tp.shplonkNu * mem.negInvertedDenominator); - Fr numeratorAcc = gamma + (beta * FrLib.from(PERMUTATION_ARGUMENT_VALUE_SEPARATOR + offset)); - Fr denominatorAcc = gamma - (beta * FrLib.from(offset + 1)); + mem.batchingChallenge = tp.shplonkNu.sqr(); + uint256 boundary = NUMBER_UNSHIFTED + 2; - { - for (uint256 i = 0; i < $NUM_PUBLIC_INPUTS - PAIRING_POINTS_SIZE; i++) { - Fr pubInput = FrLib.fromBytes32(publicInputs[i]); + // Compute Shplonk constant term contributions from Aₗ(± r^{2ˡ}) for l = 1, ..., m-1; + // Compute scalar multipliers for each fold commitment + for (uint256 i = 0; i < $LOG_N - 1; ++i) { + bool dummy_round = i >= ($LOG_N - 1); - numerator = numerator * (numeratorAcc + pubInput); - denominator = denominator * (denominatorAcc + pubInput); + if (!dummy_round) { + // Update inverted denominators + mem.posInvertedDenominator = (tp.shplonkZ - powers_of_evaluation_challenge[i + 1]).invert(); + mem.negInvertedDenominator = (tp.shplonkZ + powers_of_evaluation_challenge[i + 1]).invert(); + + // Compute the scalar multipliers for Aₗ(± r^{2ˡ}) and [Aₗ] + mem.scalingFactorPos = mem.batchingChallenge * mem.posInvertedDenominator; + mem.scalingFactorNeg = mem.batchingChallenge * tp.shplonkNu * mem.negInvertedDenominator; + scalars[boundary + i] = mem.scalingFactorNeg.neg() + mem.scalingFactorPos.neg(); - numeratorAcc = numeratorAcc + beta; - denominatorAcc = denominatorAcc - beta; - } + // Accumulate the const term contribution given by + // v^{2l} * Aₗ(r^{2ˡ}) /(z-r^{2^l}) + v^{2l+1} * Aₗ(-r^{2ˡ}) /(z+ r^{2^l}) + Fr accumContribution = mem.scalingFactorNeg * proof.geminiAEvaluations[i + 1]; + accumContribution = accumContribution + mem.scalingFactorPos * foldPosEvaluations[i + 1]; + mem.constantTermAccumulator = mem.constantTermAccumulator + accumContribution; + } + // Update the running power of v + mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu * tp.shplonkNu; - for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) { - Fr pubInput = pairingPointObject[i]; + commitments[boundary + i] = proof.geminiFoldComms[i]; + } - numerator = numerator * (numeratorAcc + pubInput); - denominator = denominator * (denominatorAcc + pubInput); + boundary += $LOG_N - 1; - numeratorAcc = numeratorAcc + beta; - denominatorAcc = denominatorAcc - beta; - } - } + // Finalize the batch opening claim + mem.denominators[0] = Fr.wrap(1).div(tp.shplonkZ - tp.geminiR); + mem.denominators[1] = Fr.wrap(1).div(tp.shplonkZ - SUBGROUP_GENERATOR * tp.geminiR); + mem.denominators[2] = mem.denominators[0]; + mem.denominators[3] = mem.denominators[0]; - // Fr delta = numerator / denominator; // TOOO: batch invert later? - publicInputDelta = FrLib.div(numerator, denominator); + mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu * tp.shplonkNu; + for (uint256 i = 0; i < LIBRA_EVALUATIONS; i++) { + Fr scalingFactor = mem.denominators[i] * mem.batchingChallenge; + mem.batchingScalars[i] = scalingFactor.neg(); + mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu; + mem.constantTermAccumulator = mem.constantTermAccumulator + scalingFactor * proof.libraPolyEvals[i]; } + scalars[boundary] = mem.batchingScalars[0]; + scalars[boundary + 1] = mem.batchingScalars[1] + mem.batchingScalars[2]; + scalars[boundary + 2] = mem.batchingScalars[3]; - function verifySumcheck(Honk.ZKProof memory proof, ZKTranscript memory tp) internal view returns (bool verified) { - Fr roundTargetSum = tp.libraChallenge * proof.libraSum; // default 0 - Fr powPartialEvaluation = Fr.wrap(1); + for (uint256 i = 0; i < LIBRA_COMMITMENTS; i++) { + commitments[boundary++] = proof.libraCommitments[i]; + } + + commitments[boundary] = Honk.G1Point({ x: 1, y: 2 }); + scalars[boundary++] = mem.constantTermAccumulator; - // We perform sumcheck reductions over log n rounds ( the multivariate degree ) - for (uint256 round; round < $LOG_N; ++round) { - Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory roundUnivariate = proof.sumcheckUnivariates[round]; - Fr totalSum = roundUnivariate[0] + roundUnivariate[1]; - if (totalSum != roundTargetSum) revert SumcheckFailed(); + if (!checkEvalsConsistency(proof.libraPolyEvals, tp.geminiR, tp.sumCheckUChallenges, proof.libraEvaluation)) { + revert ConsistencyCheckFailed(); + } - Fr roundChallenge = tp.sumCheckUChallenges[round]; + Honk.G1Point memory quotient_commitment = proof.kzgQuotient; - // Update the round target for the next rounf - roundTargetSum = computeNextTargetSum(roundUnivariate, roundChallenge); - powPartialEvaluation = - powPartialEvaluation * (Fr.wrap(1) + roundChallenge * (tp.gateChallenges[round] - Fr.wrap(1))); - } + commitments[boundary] = quotient_commitment; + scalars[boundary] = tp.shplonkZ; // evaluation challenge - // Last round - Fr grandHonkRelationSum = RelationsLib.accumulateRelationEvaluations( - proof.sumcheckEvaluations, tp.relationParameters, tp.alphas, powPartialEvaluation - ); + PairingInputs memory pair; + pair.P_0 = batchMul(commitments, scalars); + pair.P_1 = negateInplace(quotient_commitment); - Fr evaluation = Fr.wrap(1); - for (uint256 i = 2; i < $LOG_N; i++) { - evaluation = evaluation * tp.sumCheckUChallenges[i]; - } + // Aggregate pairing points + Fr recursionSeparator = generateRecursionSeparator(proof.pairingPointObject, pair.P_0, pair.P_1); + (Honk.G1Point memory P_0_other, Honk.G1Point memory P_1_other) = convertPairingPointsToG1(proof.pairingPointObject); - grandHonkRelationSum = - grandHonkRelationSum * (Fr.wrap(1) - evaluation) + proof.libraEvaluation * tp.libraChallenge; - verified = (grandHonkRelationSum == roundTargetSum); + // Validate the points from the proof are on the curve + validateOnCurve(P_0_other); + validateOnCurve(P_1_other); + + // accumulate with aggregate points in proof + pair.P_0 = mulWithSeperator(pair.P_0, P_0_other, recursionSeparator); + pair.P_1 = mulWithSeperator(pair.P_1, P_1_other, recursionSeparator); + + return pairing(pair.P_0, pair.P_1); + } + + struct SmallSubgroupIpaIntermediates { + Fr[SUBGROUP_SIZE] challengePolyLagrange; + Fr challengePolyEval; + Fr lagrangeFirst; + Fr lagrangeLast; + Fr rootPower; + Fr[SUBGROUP_SIZE] denominators; // this has to disappear + Fr diff; + } + + function checkEvalsConsistency( + Fr[LIBRA_EVALUATIONS] memory libraPolyEvals, + Fr geminiR, + Fr[CONST_PROOF_SIZE_LOG_N] memory uChallenges, + Fr libraEval + ) internal view returns (bool check) { + Fr one = Fr.wrap(1); + Fr vanishingPolyEval = geminiR.pow(SUBGROUP_SIZE) - one; + if (vanishingPolyEval == Fr.wrap(0)) { + revert GeminiChallengeInSubgroup(); } - // Return the new target sum for the next sumcheck round - function computeNextTargetSum(Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory roundUnivariates, Fr roundChallenge) - internal - view - returns (Fr targetSum) - { - Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory BARYCENTRIC_LAGRANGE_DENOMINATORS = [ - Fr.wrap(0x0000000000000000000000000000000000000000000000000000000000009d80), - Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffec51), - Fr.wrap(0x00000000000000000000000000000000000000000000000000000000000005a0), - Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593effffd31), - Fr.wrap(0x0000000000000000000000000000000000000000000000000000000000000240), - Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593effffd31), - Fr.wrap(0x00000000000000000000000000000000000000000000000000000000000005a0), - Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffec51), - Fr.wrap(0x0000000000000000000000000000000000000000000000000000000000009d80) - ]; - - // To compute the next target sum, we evaluate the given univariate at a point u (challenge). - - // Performing Barycentric evaluations - // Compute B(x) - Fr numeratorValue = Fr.wrap(1); - for (uint256 i = 0; i < ZK_BATCHED_RELATION_PARTIAL_LENGTH; ++i) { - numeratorValue = numeratorValue * (roundChallenge - Fr.wrap(i)); - } - - Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory denominatorInverses; - for (uint256 i = 0; i < ZK_BATCHED_RELATION_PARTIAL_LENGTH; ++i) { - denominatorInverses[i] = FrLib.invert(BARYCENTRIC_LAGRANGE_DENOMINATORS[i] * (roundChallenge - Fr.wrap(i))); - } - - for (uint256 i = 0; i < ZK_BATCHED_RELATION_PARTIAL_LENGTH; ++i) { - targetSum = targetSum + roundUnivariates[i] * denominatorInverses[i]; - } - - // Scale the sum by the value of B(x) - targetSum = targetSum * numeratorValue; - } - - uint256 constant LIBRA_COMMITMENTS = 3; - uint256 constant LIBRA_EVALUATIONS = 4; - uint256 constant LIBRA_UNIVARIATES_LENGTH = 9; - - struct PairingInputs { - Honk.G1Point P_0; - Honk.G1Point P_1; - } - - function verifyShplemini(Honk.ZKProof memory proof, Honk.VerificationKey memory vk, ZKTranscript memory tp) - internal - view - returns (bool verified) - { - CommitmentSchemeLib.ShpleminiIntermediates memory mem; // stack - - // - Compute vector (r, r², ... , r²⁽ⁿ⁻¹⁾), where n = log_circuit_size - Fr[] memory powers_of_evaluation_challenge = CommitmentSchemeLib.computeSquares(tp.geminiR, $LOG_N); - // Arrays hold values that will be linearly combined for the gemini and shplonk batch openings - Fr[] memory scalars = new Fr[](NUMBER_UNSHIFTED + $LOG_N + LIBRA_COMMITMENTS + 3); - Honk.G1Point[] memory commitments = new Honk.G1Point[](NUMBER_UNSHIFTED + $LOG_N + LIBRA_COMMITMENTS + 3); - - mem.posInvertedDenominator = (tp.shplonkZ - powers_of_evaluation_challenge[0]).invert(); - mem.negInvertedDenominator = (tp.shplonkZ + powers_of_evaluation_challenge[0]).invert(); - - mem.unshiftedScalar = mem.posInvertedDenominator + (tp.shplonkNu * mem.negInvertedDenominator); - mem.shiftedScalar = - tp.geminiR.invert() * (mem.posInvertedDenominator - (tp.shplonkNu * mem.negInvertedDenominator)); - - scalars[0] = Fr.wrap(1); - commitments[0] = proof.shplonkQ; - - /* Batch multivariate opening claims, shifted and unshifted - * The vector of scalars is populated as follows: - * \f[ - * \left( - * - \left(\frac{1}{z-r} + \nu \times \frac{1}{z+r}\right), - * \ldots, - * - \rho^{i+k-1} \times \left(\frac{1}{z-r} + \nu \times \frac{1}{z+r}\right), - * - \rho^{i+k} \times \frac{1}{r} \times \left(\frac{1}{z-r} - \nu \times \frac{1}{z+r}\right), - * \ldots, - * - \rho^{k+m-1} \times \frac{1}{r} \times \left(\frac{1}{z-r} - \nu \times \frac{1}{z+r}\right) - * \right) - * \f] - * - * The following vector is concatenated to the vector of commitments: - * \f[ - * f_0, \ldots, f_{m-1}, f_{\text{shift}, 0}, \ldots, f_{\text{shift}, k-1} - * \f] - * - * Simultaneously, the evaluation of the multilinear polynomial - * \f[ - * \sum \rho^i \cdot f_i + \sum \rho^{i+k} \cdot f_{\text{shift}, i} - * \f] - * at the challenge point \f$ (u_0,\ldots, u_{n-1}) \f$ is computed. - * - * This approach minimizes the number of iterations over the commitments to multilinear polynomials - * and eliminates the need to store the powers of \f$ \rho \f$. - */ - mem.batchedEvaluation = proof.geminiMaskingEval; - mem.batchingChallenge = tp.rho; - mem.unshiftedScalarNeg = mem.unshiftedScalar.neg(); - mem.shiftedScalarNeg = mem.shiftedScalar.neg(); - - scalars[1] = mem.unshiftedScalarNeg; - for (uint256 i = 0; i < NUMBER_UNSHIFTED; ++i) { - scalars[i + 2] = mem.unshiftedScalarNeg * mem.batchingChallenge; - mem.batchedEvaluation = mem.batchedEvaluation + (proof.sumcheckEvaluations[i] * mem.batchingChallenge); - mem.batchingChallenge = mem.batchingChallenge * tp.rho; - } - // g commitments are accumulated at r - // For each of the to be shifted commitments perform the shift in place by - // adding to the unshifted value. - // We do so, as the values are to be used in batchMul later, and as - // `a * c + b * c = (a + b) * c` this will allow us to reduce memory and compute. - // Applied to w1, w2, w3, w4 and zPerm - for (uint256 i = 0; i < NUMBER_TO_BE_SHIFTED; ++i) { - uint256 scalarOff = i + SHIFTED_COMMITMENTS_START; - uint256 evaluationOff = i + NUMBER_UNSHIFTED; - - scalars[scalarOff] = scalars[scalarOff] + (mem.shiftedScalarNeg * mem.batchingChallenge); - mem.batchedEvaluation = - mem.batchedEvaluation + (proof.sumcheckEvaluations[evaluationOff] * mem.batchingChallenge); - mem.batchingChallenge = mem.batchingChallenge * tp.rho; - } - - commitments[1] = proof.geminiMaskingPoly; - - commitments[2] = vk.qm; - commitments[3] = vk.qc; - commitments[4] = vk.ql; - commitments[5] = vk.qr; - commitments[6] = vk.qo; - commitments[7] = vk.q4; - commitments[8] = vk.qLookup; - commitments[9] = vk.qArith; - commitments[10] = vk.qDeltaRange; - commitments[11] = vk.qElliptic; - commitments[12] = vk.qMemory; - commitments[13] = vk.qNnf; - commitments[14] = vk.qPoseidon2External; - commitments[15] = vk.qPoseidon2Internal; - commitments[16] = vk.s1; - commitments[17] = vk.s2; - commitments[18] = vk.s3; - commitments[19] = vk.s4; - commitments[20] = vk.id1; - commitments[21] = vk.id2; - commitments[22] = vk.id3; - commitments[23] = vk.id4; - commitments[24] = vk.t1; - commitments[25] = vk.t2; - commitments[26] = vk.t3; - commitments[27] = vk.t4; - commitments[28] = vk.lagrangeFirst; - commitments[29] = vk.lagrangeLast; - - // Accumulate proof points - commitments[30] = proof.w1; - commitments[31] = proof.w2; - commitments[32] = proof.w3; - commitments[33] = proof.w4; - commitments[34] = proof.zPerm; - commitments[35] = proof.lookupInverses; - commitments[36] = proof.lookupReadCounts; - commitments[37] = proof.lookupReadTags; - - /* Batch gemini claims from the prover - * place the commitments to gemini aᵢ to the vector of commitments, compute the contributions from - * aᵢ(−r²ⁱ) for i=1, … , n−1 to the constant term accumulator, add corresponding scalars - * - * 1. Moves the vector - * \f[ - * \left( \text{com}(A_1), \text{com}(A_2), \ldots, \text{com}(A_{n-1}) \right) - * \f] - * to the 'commitments' vector. - * - * 2. Computes the scalars: - * \f[ - * \frac{\nu^{2}}{z + r^2}, \frac{\nu^3}{z + r^4}, \ldots, \frac{\nu^{n-1}}{z + r^{2^{n-1}}} - * \f] - * and places them into the 'scalars' vector. - * - * 3. Accumulates the summands of the constant term: - * \f[ - * \sum_{i=2}^{n-1} \frac{\nu^{i} \cdot A_i(-r^{2^i})}{z + r^{2^i}} - * \f] - * and adds them to the 'constant_term_accumulator'. - */ - - // Add contributions from A₀(r) and A₀(-r) to constant_term_accumulator: - // Compute the evaluations Aₗ(r^{2ˡ}) for l = 0, ..., $LOG_N - 1 - Fr[] memory foldPosEvaluations = CommitmentSchemeLib.computeFoldPosEvaluations( - tp.sumCheckUChallenges, - mem.batchedEvaluation, - proof.geminiAEvaluations, - powers_of_evaluation_challenge, - $LOG_N - ); - - mem.constantTermAccumulator = foldPosEvaluations[0] * mem.posInvertedDenominator; - mem.constantTermAccumulator = - mem.constantTermAccumulator + (proof.geminiAEvaluations[0] * tp.shplonkNu * mem.negInvertedDenominator); - - mem.batchingChallenge = tp.shplonkNu.sqr(); - uint256 boundary = NUMBER_UNSHIFTED + 2; - - // Compute Shplonk constant term contributions from Aₗ(± r^{2ˡ}) for l = 1, ..., m-1; - // Compute scalar multipliers for each fold commitment - for (uint256 i = 0; i < $LOG_N - 1; ++i) { - bool dummy_round = i >= ($LOG_N - 1); - - if (!dummy_round) { - // Update inverted denominators - mem.posInvertedDenominator = (tp.shplonkZ - powers_of_evaluation_challenge[i + 1]).invert(); - mem.negInvertedDenominator = (tp.shplonkZ + powers_of_evaluation_challenge[i + 1]).invert(); - - // Compute the scalar multipliers for Aₗ(± r^{2ˡ}) and [Aₗ] - mem.scalingFactorPos = mem.batchingChallenge * mem.posInvertedDenominator; - mem.scalingFactorNeg = mem.batchingChallenge * tp.shplonkNu * mem.negInvertedDenominator; - scalars[boundary + i] = mem.scalingFactorNeg.neg() + mem.scalingFactorPos.neg(); - - // Accumulate the const term contribution given by - // v^{2l} * Aₗ(r^{2ˡ}) /(z-r^{2^l}) + v^{2l+1} * Aₗ(-r^{2ˡ}) /(z+ r^{2^l}) - Fr accumContribution = mem.scalingFactorNeg * proof.geminiAEvaluations[i + 1]; - accumContribution = accumContribution + mem.scalingFactorPos * foldPosEvaluations[i + 1]; - mem.constantTermAccumulator = mem.constantTermAccumulator + accumContribution; - } - // Update the running power of v - mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu * tp.shplonkNu; - - commitments[boundary + i] = proof.geminiFoldComms[i]; - } - - boundary += $LOG_N - 1; - - // Finalize the batch opening claim - mem.denominators[0] = Fr.wrap(1).div(tp.shplonkZ - tp.geminiR); - mem.denominators[1] = Fr.wrap(1).div(tp.shplonkZ - SUBGROUP_GENERATOR * tp.geminiR); - mem.denominators[2] = mem.denominators[0]; - mem.denominators[3] = mem.denominators[0]; - - mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu * tp.shplonkNu; - for (uint256 i = 0; i < LIBRA_EVALUATIONS; i++) { - Fr scalingFactor = mem.denominators[i] * mem.batchingChallenge; - mem.batchingScalars[i] = scalingFactor.neg(); - mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu; - mem.constantTermAccumulator = mem.constantTermAccumulator + scalingFactor * proof.libraPolyEvals[i]; - } - scalars[boundary] = mem.batchingScalars[0]; - scalars[boundary + 1] = mem.batchingScalars[1] + mem.batchingScalars[2]; - scalars[boundary + 2] = mem.batchingScalars[3]; - - for (uint256 i = 0; i < LIBRA_COMMITMENTS; i++) { - commitments[boundary++] = proof.libraCommitments[i]; - } - - commitments[boundary] = Honk.G1Point({x: 1, y: 2}); - scalars[boundary++] = mem.constantTermAccumulator; - - if (!checkEvalsConsistency(proof.libraPolyEvals, tp.geminiR, tp.sumCheckUChallenges, proof.libraEvaluation)) { - revert ConsistencyCheckFailed(); - } - - Honk.G1Point memory quotient_commitment = proof.kzgQuotient; - - commitments[boundary] = quotient_commitment; - scalars[boundary] = tp.shplonkZ; // evaluation challenge - - PairingInputs memory pair; - pair.P_0 = batchMul(commitments, scalars); - pair.P_1 = negateInplace(quotient_commitment); - - // Aggregate pairing points - Fr recursionSeparator = generateRecursionSeparator(proof.pairingPointObject, pair.P_0, pair.P_1); - (Honk.G1Point memory P_0_other, Honk.G1Point memory P_1_other) = - convertPairingPointsToG1(proof.pairingPointObject); - - // Validate the points from the proof are on the curve - validateOnCurve(P_0_other); - validateOnCurve(P_1_other); - - // accumulate with aggregate points in proof - pair.P_0 = mulWithSeperator(pair.P_0, P_0_other, recursionSeparator); - pair.P_1 = mulWithSeperator(pair.P_1, P_1_other, recursionSeparator); - - return pairing(pair.P_0, pair.P_1); - } - - struct SmallSubgroupIpaIntermediates { - Fr[SUBGROUP_SIZE] challengePolyLagrange; - Fr challengePolyEval; - Fr lagrangeFirst; - Fr lagrangeLast; - Fr rootPower; - Fr[SUBGROUP_SIZE] denominators; // this has to disappear - Fr diff; - } - - function checkEvalsConsistency( - Fr[LIBRA_EVALUATIONS] memory libraPolyEvals, - Fr geminiR, - Fr[CONST_PROOF_SIZE_LOG_N] memory uChallenges, - Fr libraEval - ) internal view returns (bool check) { - Fr one = Fr.wrap(1); - Fr vanishingPolyEval = geminiR.pow(SUBGROUP_SIZE) - one; - if (vanishingPolyEval == Fr.wrap(0)) { - revert GeminiChallengeInSubgroup(); - } - - SmallSubgroupIpaIntermediates memory mem; - mem.challengePolyLagrange[0] = one; - for (uint256 round = 0; round < $LOG_N; round++) { - uint256 currIdx = 1 + LIBRA_UNIVARIATES_LENGTH * round; - mem.challengePolyLagrange[currIdx] = one; - for (uint256 idx = currIdx + 1; idx < currIdx + LIBRA_UNIVARIATES_LENGTH; idx++) { - mem.challengePolyLagrange[idx] = mem.challengePolyLagrange[idx - 1] * uChallenges[round]; - } - } - - mem.rootPower = one; - mem.challengePolyEval = Fr.wrap(0); - for (uint256 idx = 0; idx < SUBGROUP_SIZE; idx++) { - mem.denominators[idx] = mem.rootPower * geminiR - one; - mem.denominators[idx] = mem.denominators[idx].invert(); - mem.challengePolyEval = mem.challengePolyEval + mem.challengePolyLagrange[idx] * mem.denominators[idx]; - mem.rootPower = mem.rootPower * SUBGROUP_GENERATOR_INVERSE; - } - - Fr numerator = vanishingPolyEval * Fr.wrap(SUBGROUP_SIZE).invert(); - mem.challengePolyEval = mem.challengePolyEval * numerator; - mem.lagrangeFirst = mem.denominators[0] * numerator; - mem.lagrangeLast = mem.denominators[SUBGROUP_SIZE - 1] * numerator; - - mem.diff = mem.lagrangeFirst * libraPolyEvals[2]; - - mem.diff = mem.diff - + (geminiR - SUBGROUP_GENERATOR_INVERSE) - * (libraPolyEvals[1] - libraPolyEvals[2] - libraPolyEvals[0] * mem.challengePolyEval); - mem.diff = mem.diff + mem.lagrangeLast * (libraPolyEvals[2] - libraEval) - vanishingPolyEval * libraPolyEvals[3]; - - check = mem.diff == Fr.wrap(0); - } - - // This implementation is the same as above with different constants - function batchMul(Honk.G1Point[] memory base, Fr[] memory scalars) - internal - view - returns (Honk.G1Point memory result) - { - uint256 limit = NUMBER_UNSHIFTED + $LOG_N + LIBRA_COMMITMENTS + 3; + SmallSubgroupIpaIntermediates memory mem; + mem.challengePolyLagrange[0] = one; + for (uint256 round = 0; round < $LOG_N; round++) { + uint256 currIdx = 1 + LIBRA_UNIVARIATES_LENGTH * round; + mem.challengePolyLagrange[currIdx] = one; + for (uint256 idx = currIdx + 1; idx < currIdx + LIBRA_UNIVARIATES_LENGTH; idx++) { + mem.challengePolyLagrange[idx] = mem.challengePolyLagrange[idx - 1] * uChallenges[round]; + } + } + + mem.rootPower = one; + mem.challengePolyEval = Fr.wrap(0); + for (uint256 idx = 0; idx < SUBGROUP_SIZE; idx++) { + mem.denominators[idx] = mem.rootPower * geminiR - one; + mem.denominators[idx] = mem.denominators[idx].invert(); + mem.challengePolyEval = mem.challengePolyEval + mem.challengePolyLagrange[idx] * mem.denominators[idx]; + mem.rootPower = mem.rootPower * SUBGROUP_GENERATOR_INVERSE; + } + + Fr numerator = vanishingPolyEval * Fr.wrap(SUBGROUP_SIZE).invert(); + mem.challengePolyEval = mem.challengePolyEval * numerator; + mem.lagrangeFirst = mem.denominators[0] * numerator; + mem.lagrangeLast = mem.denominators[SUBGROUP_SIZE - 1] * numerator; + + mem.diff = mem.lagrangeFirst * libraPolyEvals[2]; - // Validate all points are on the curve - for (uint256 i = 0; i < limit; ++i) { - validateOnCurve(base[i]); - } + mem.diff = + mem.diff + + (geminiR - SUBGROUP_GENERATOR_INVERSE) * + (libraPolyEvals[1] - libraPolyEvals[2] - libraPolyEvals[0] * mem.challengePolyEval); + mem.diff = mem.diff + mem.lagrangeLast * (libraPolyEvals[2] - libraEval) - vanishingPolyEval * libraPolyEvals[3]; - bool success = true; - assembly { - let free := mload(0x40) + check = mem.diff == Fr.wrap(0); + } - let count := 0x01 - for {} lt(count, add(limit, 1)) { count := add(count, 1) } { - // Get loop offsets - let base_base := add(base, mul(count, 0x20)) - let scalar_base := add(scalars, mul(count, 0x20)) + // This implementation is the same as above with different constants + function batchMul(Honk.G1Point[] memory base, Fr[] memory scalars) internal view returns (Honk.G1Point memory result) { + uint256 limit = NUMBER_UNSHIFTED + $LOG_N + LIBRA_COMMITMENTS + 3; - mstore(add(free, 0x40), mload(mload(base_base))) - mstore(add(free, 0x60), mload(add(0x20, mload(base_base)))) - // Add scalar - mstore(add(free, 0x80), mload(scalar_base)) + // Validate all points are on the curve + for (uint256 i = 0; i < limit; ++i) { + validateOnCurve(base[i]); + } + + bool success = true; + assembly { + let free := mload(0x40) - success := and(success, staticcall(gas(), 7, add(free, 0x40), 0x60, add(free, 0x40), 0x40)) - // accumulator = accumulator + accumulator_2 - success := and(success, staticcall(gas(), 6, free, 0x80, free, 0x40)) - } + let count := 0x01 + for {} lt(count, add(limit, 1)) { + count := add(count, 1) + } { + // Get loop offsets + let base_base := add(base, mul(count, 0x20)) + let scalar_base := add(scalars, mul(count, 0x20)) - // Return the result - mstore(result, mload(free)) - mstore(add(result, 0x20), mload(add(free, 0x20))) - } + mstore(add(free, 0x40), mload(mload(base_base))) + mstore(add(free, 0x60), mload(add(0x20, mload(base_base)))) + // Add scalar + mstore(add(free, 0x80), mload(scalar_base)) - require(success, ShpleminiFailed()); + success := and(success, staticcall(gas(), 7, add(free, 0x40), 0x60, add(free, 0x40), 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, free, 0x80, free, 0x40)) + } + + // Return the result + mstore(result, mload(free)) + mstore(add(result, 0x20), mload(add(free, 0x20))) } + + require(success, ShpleminiFailed()); + } } contract HonkVerifier is BaseZKHonkVerifier(N, LOG_N, VK_HASH, NUMBER_OF_PUBLIC_INPUTS) { - function loadVerificationKey() internal pure override returns (Honk.VerificationKey memory) { - return HonkVerificationKey.loadVerificationKey(); - } + function loadVerificationKey() internal pure override returns (Honk.VerificationKey memory) { + return HonkVerificationKey.loadVerificationKey(); + } } diff --git a/examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockCRISPProgram.sol b/examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockCRISPProgram.sol index 9202d28adc..87d00d0e9f 100644 --- a/examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockCRISPProgram.sol +++ b/examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockCRISPProgram.sol @@ -5,229 +5,212 @@ // or FITNESS FOR A PARTICULAR PURPOSE. pragma solidity >=0.8.27; -import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {IE3Program} from "@enclave-e3/contracts/contracts/interfaces/IE3Program.sol"; -import {IEnclave} from "@enclave-e3/contracts/contracts/interfaces/IEnclave.sol"; -import {E3} from "@enclave-e3/contracts/contracts/interfaces/IE3.sol"; -import {LazyIMTData, InternalLazyIMT} from "@zk-kit/lazy-imt.sol/InternalLazyIMT.sol"; +import { IRiscZeroVerifier } from "risc0/IRiscZeroVerifier.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { IE3Program } from "@enclave-e3/contracts/contracts/interfaces/IE3Program.sol"; +import { IEnclave } from "@enclave-e3/contracts/contracts/interfaces/IEnclave.sol"; +import { E3 } from "@enclave-e3/contracts/contracts/interfaces/IE3.sol"; +import { LazyIMTData, InternalLazyIMT } from "@zk-kit/lazy-imt.sol/InternalLazyIMT.sol"; -import {HonkVerifier} from "../CRISPVerifier.sol"; +import { HonkVerifier } from "../CRISPVerifier.sol"; contract MockCRISPProgram is IE3Program, Ownable { - using InternalLazyIMT for LazyIMTData; - /// @notice a structure that holds the round data - struct RoundData { - /// @notice The governance token address. - address token; - /// @notice The minimum balance required to pass the validation. - uint256 balanceThreshold; - /// @notice The Merkle root of the census. - uint256 censusMerkleRoot; + using InternalLazyIMT for LazyIMTData; + /// @notice a structure that holds the round data + struct RoundData { + /// @notice The governance token address. + address token; + /// @notice The minimum balance required to pass the validation. + uint256 balanceThreshold; + /// @notice The Merkle root of the census. + uint256 censusMerkleRoot; + } + + // Constants + bytes32 public constant ENCRYPTION_SCHEME_ID = keccak256("fhe.rs:BFV"); + + // The depth of the input merkle tree + uint8 public constant TREE_DEPTH = 20; + + // State variables + IEnclave public enclave; + IRiscZeroVerifier public verifier; + HonkVerifier private immutable HONK_VERIFIER; + bytes32 public imageId; + + /// @notice the round data + RoundData public roundData; + /// @notice whether the round data has been set + bool public isDataSet; + + /// @notice Half of the largest minimum degree used to fit votes + /// inside the plaintext polynomial + uint256 public constant HALF_LARGEST_MINIMUM_DEGREE = 28; + + // Mappings + mapping(address => bool) public authorizedContracts; + mapping(uint256 e3Id => bytes32 paramsHash) public paramsHashes; + /// @notice Mapping to store votes slot indices. Each eligible voter has their own slot + /// to store their vote inside the merkle tree. + mapping(uint256 e3Id => mapping(address slot => uint40 index)) public voteSlots; + mapping(uint256 e3Id => LazyIMTData) public votes; + + // Errors + error CallerNotAuthorized(); + error E3AlreadyInitialized(); + error E3DoesNotExist(); + error EnclaveAddressZero(); + error VerifierAddressZero(); + + /// @notice The error emitted when the honk verifier address is invalid. + error InvalidHonkVerifier(); + /// @notice The error emitted when the input data is empty. + error EmptyInputData(); + /// @notice The error emitted when the input data is invalid. + error InvalidInputData(bytes reason); + /// @notice The error emitted when the Noir proof is invalid. + error InvalidNoirProof(); + /// @notice The error emitted when the round data is not set. + error RoundDataNotSet(); + /// @notice The error emitted when trying to set the round data more than once. + error RoundDataAlreadySet(); + + /// @notice The event emitted when an input is published. + event InputPublished(uint256 indexed e3Id, bytes vote, uint256 index); + + /// @notice Initialize the contract, binding it to a specified RISC Zero verifier. + /// @param _enclave The enclave address + /// @param _verifier The RISC Zero verifier address + /// @param _honkVerifier The honk verifier address + /// @param _imageId The image ID for the guest program + constructor(IEnclave _enclave, IRiscZeroVerifier _verifier, HonkVerifier _honkVerifier, bytes32 _imageId) Ownable(msg.sender) { + require(address(_enclave) != address(0), EnclaveAddressZero()); + require(address(_verifier) != address(0), VerifierAddressZero()); + require(address(_honkVerifier) != address(0), InvalidHonkVerifier()); + + enclave = _enclave; + verifier = _verifier; + HONK_VERIFIER = _honkVerifier; + authorizedContracts[address(_enclave)] = true; + imageId = _imageId; + } + + /// @notice Sets the Round data. Can only be set once. + /// @param _root The Merkle root to set. + /// @param _token The governance token address. + /// @param _balanceThreshold The minimum balance required. + function setRoundData(uint256 _root, address _token, uint256 _balanceThreshold) external onlyOwner { + if (isDataSet) revert RoundDataAlreadySet(); + + isDataSet = true; + + roundData = RoundData({ token: _token, balanceThreshold: _balanceThreshold, censusMerkleRoot: _root }); + } + + /// @notice Set the Image ID for the guest program + /// @param _imageId The new image ID. + function setImageId(bytes32 _imageId) external onlyOwner { + imageId = _imageId; + } + + /// @notice Set the RISC Zero verifier address + /// @param _verifier The new RISC Zero verifier address + function setVerifier(IRiscZeroVerifier _verifier) external onlyOwner { + if (address(_verifier) == address(0)) revert VerifierAddressZero(); + verifier = _verifier; + } + + /// @notice Get the params hash for an E3 program + /// @param e3Id The E3 program ID + /// @return The params hash + function getParamsHash(uint256 e3Id) public view returns (bytes32) { + return paramsHashes[e3Id]; + } + + /// @notice Validate the E3 program parameters + /// @param e3Id The E3 program ID + /// @param e3ProgramParams The E3 program parameters + function validate(uint256 e3Id, uint256, bytes calldata e3ProgramParams, bytes calldata) external returns (bytes32) { + require(authorizedContracts[msg.sender] || msg.sender == owner(), CallerNotAuthorized()); + require(paramsHashes[e3Id] == bytes32(0), E3AlreadyInitialized()); + paramsHashes[e3Id] = keccak256(e3ProgramParams); + + return ENCRYPTION_SCHEME_ID; + } + + /// @inheritdoc IE3Program + function validateInput(uint256 e3Id, address, bytes memory data) external returns (bytes memory input) { + if (data.length == 0) revert EmptyInputData(); + + (, , bytes memory vote, ) = abi.decode(data, (bytes, bytes32[], bytes, address)); + + input = vote; + } + + /// @notice Decode the tally from the plaintext output + /// @param e3Id The E3 program ID + /// @return yes The number of yes votes + /// @return no The number of no votes + function decodeTally(uint256 e3Id) public view returns (uint256 yes, uint256 no) { + // fetch from enclave + E3 memory e3 = enclave.getE3(e3Id); + + // abi decode it into an array of uint256 + uint256[] memory tally = abi.decode(e3.plaintextOutput, (uint256[])); + + /// @notice We want to completely ignore anything outside of the coefficients + /// we agreed to store out votes on. + uint256 halfD = tally.length / 2; + uint256 START_INDEX_Y = halfD - HALF_LARGEST_MINIMUM_DEGREE; + uint256 START_INDEX_N = tally.length - HALF_LARGEST_MINIMUM_DEGREE; + + // first weight (we are converting back from bits to integer) + uint256 weight = 2 ** (HALF_LARGEST_MINIMUM_DEGREE - 1); + + // Convert yes votes + for (uint256 i = START_INDEX_Y; i < halfD; i++) { + yes += tally[i] * weight; + weight /= 2; // Right shift equivalent } - // Constants - bytes32 public constant ENCRYPTION_SCHEME_ID = keccak256("fhe.rs:BFV"); - - // The depth of the input merkle tree - uint8 public constant TREE_DEPTH = 20; - - // State variables - IEnclave public enclave; - IRiscZeroVerifier public verifier; - HonkVerifier private immutable HONK_VERIFIER; - bytes32 public imageId; - - /// @notice the round data - RoundData public roundData; - /// @notice whether the round data has been set - bool public isDataSet; - - /// @notice Half of the largest minimum degree used to fit votes - /// inside the plaintext polynomial - uint256 public constant HALF_LARGEST_MINIMUM_DEGREE = 28; - - // Mappings - mapping(address => bool) public authorizedContracts; - mapping(uint256 e3Id => bytes32 paramsHash) public paramsHashes; - /// @notice Mapping to store votes slot indices. Each eligible voter has their own slot - /// to store their vote inside the merkle tree. - mapping(uint256 e3Id => mapping(address slot => uint40 index)) public voteSlots; - mapping(uint256 e3Id => LazyIMTData) public votes; - - // Errors - error CallerNotAuthorized(); - error E3AlreadyInitialized(); - error E3DoesNotExist(); - error EnclaveAddressZero(); - error VerifierAddressZero(); - - /// @notice The error emitted when the honk verifier address is invalid. - error InvalidHonkVerifier(); - /// @notice The error emitted when the input data is empty. - error EmptyInputData(); - /// @notice The error emitted when the input data is invalid. - error InvalidInputData(bytes reason); - /// @notice The error emitted when the Noir proof is invalid. - error InvalidNoirProof(); - /// @notice The error emitted when the round data is not set. - error RoundDataNotSet(); - /// @notice The error emitted when trying to set the round data more than once. - error RoundDataAlreadySet(); - - /// @notice The event emitted when an input is published. - event InputPublished(uint256 indexed e3Id, bytes vote, uint256 index); - - /// @notice Initialize the contract, binding it to a specified RISC Zero verifier. - /// @param _enclave The enclave address - /// @param _verifier The RISC Zero verifier address - /// @param _honkVerifier The honk verifier address - /// @param _imageId The image ID for the guest program - constructor(IEnclave _enclave, IRiscZeroVerifier _verifier, HonkVerifier _honkVerifier, bytes32 _imageId) - Ownable(msg.sender) - { - require(address(_enclave) != address(0), EnclaveAddressZero()); - require(address(_verifier) != address(0), VerifierAddressZero()); - require(address(_honkVerifier) != address(0), InvalidHonkVerifier()); - - enclave = _enclave; - verifier = _verifier; - HONK_VERIFIER = _honkVerifier; - authorizedContracts[address(_enclave)] = true; - imageId = _imageId; - } - - /// @notice Sets the Round data. Can only be set once. - /// @param _root The Merkle root to set. - /// @param _token The governance token address. - /// @param _balanceThreshold The minimum balance required. - function setRoundData(uint256 _root, address _token, uint256 _balanceThreshold) - external - onlyOwner - { - if (isDataSet) revert RoundDataAlreadySet(); - - isDataSet = true; - - roundData = RoundData({ - token: _token, - balanceThreshold: _balanceThreshold, - censusMerkleRoot: _root - }); - } - - /// @notice Set the Image ID for the guest program - /// @param _imageId The new image ID. - function setImageId(bytes32 _imageId) external onlyOwner { - imageId = _imageId; - } - - /// @notice Set the RISC Zero verifier address - /// @param _verifier The new RISC Zero verifier address - function setVerifier(IRiscZeroVerifier _verifier) external onlyOwner { - if (address(_verifier) == address(0)) revert VerifierAddressZero(); - verifier = _verifier; - } - - /// @notice Get the params hash for an E3 program - /// @param e3Id The E3 program ID - /// @return The params hash - function getParamsHash(uint256 e3Id) public view returns (bytes32) { - return paramsHashes[e3Id]; - } - - /// @notice Validate the E3 program parameters - /// @param e3Id The E3 program ID - /// @param e3ProgramParams The E3 program parameters - function validate(uint256 e3Id, uint256, bytes calldata e3ProgramParams, bytes calldata) - external - returns (bytes32) - { - require(authorizedContracts[msg.sender] || msg.sender == owner(), CallerNotAuthorized()); - require(paramsHashes[e3Id] == bytes32(0), E3AlreadyInitialized()); - paramsHashes[e3Id] = keccak256(e3ProgramParams); - - return ENCRYPTION_SCHEME_ID; - } - - /// @inheritdoc IE3Program - function validateInput(uint256 e3Id, address, bytes memory data) external returns (bytes memory input) { - if (data.length == 0) revert EmptyInputData(); - - (,, bytes memory vote,) = abi.decode(data, (bytes, bytes32[], bytes, address)); - - input = vote; - } - - /// @notice Decode the tally from the plaintext output - /// @param e3Id The E3 program ID - /// @return yes The number of yes votes - /// @return no The number of no votes - function decodeTally(uint256 e3Id) public view returns (uint256 yes, uint256 no) { - // fetch from enclave - E3 memory e3 = enclave.getE3(e3Id); - - // abi decode it into an array of uint256 - uint256[] memory tally = abi.decode(e3.plaintextOutput, (uint256[])); - - /// @notice We want to completely ignore anything outside of the coefficients - /// we agreed to store out votes on. - uint256 halfD = tally.length / 2; - uint256 START_INDEX_Y = halfD - HALF_LARGEST_MINIMUM_DEGREE; - uint256 START_INDEX_N = tally.length - HALF_LARGEST_MINIMUM_DEGREE; - - // first weight (we are converting back from bits to integer) - uint256 weight = 2 ** (HALF_LARGEST_MINIMUM_DEGREE - 1); - - // Convert yes votes - for (uint256 i = START_INDEX_Y; i < halfD; i++) { - yes += tally[i] * weight; - weight /= 2; // Right shift equivalent - } - - // Reset weight for no votes - weight = 2 ** (HALF_LARGEST_MINIMUM_DEGREE - 1); - - // Convert no votes - for (uint256 i = START_INDEX_N; i < tally.length; i++) { - no += tally[i] * weight; - weight /= 2; - } - - return (yes, no); - } + // Reset weight for no votes + weight = 2 ** (HALF_LARGEST_MINIMUM_DEGREE - 1); - /// @notice Verify the proof - /// @param e3Id The E3 program ID - /// @param ciphertextOutputHash The hash of the ciphertext output - /// @param proof The proof to verify - function verify(uint256 e3Id, bytes32 ciphertextOutputHash, bytes memory proof) - external - view - override - returns (bool) - { - require(paramsHashes[e3Id] != bytes32(0), E3DoesNotExist()); - bytes32 inputRoot = bytes32(votes[e3Id]._root(TREE_DEPTH)); - bytes memory journal = new bytes(396); // (32 + 1) * 4 * 3 - - encodeLengthPrefixAndHash(journal, 0, ciphertextOutputHash); - encodeLengthPrefixAndHash(journal, 132, paramsHashes[e3Id]); - encodeLengthPrefixAndHash(journal, 264, inputRoot); - - verifier.verify(proof, imageId, sha256(journal)); - return true; + // Convert no votes + for (uint256 i = START_INDEX_N; i < tally.length; i++) { + no += tally[i] * weight; + weight /= 2; } - /// @notice Encode length prefix and hash - /// @param journal The journal to encode into - /// @param startIndex The start index in the journal - /// @param hashVal The hash value to encode - function encodeLengthPrefixAndHash(bytes memory journal, uint256 startIndex, bytes32 hashVal) internal pure { - journal[startIndex] = 0x20; - startIndex += 4; - for (uint256 i = 0; i < 32; i++) { - journal[startIndex + i * 4] = hashVal[i]; - } + return (yes, no); + } + + /// @notice Verify the proof + /// @param e3Id The E3 program ID + /// @param ciphertextOutputHash The hash of the ciphertext output + /// @param proof The proof to verify + function verify(uint256 e3Id, bytes32 ciphertextOutputHash, bytes memory proof) external view override returns (bool) { + require(paramsHashes[e3Id] != bytes32(0), E3DoesNotExist()); + bytes32 inputRoot = bytes32(votes[e3Id]._root(TREE_DEPTH)); + bytes memory journal = new bytes(396); // (32 + 1) * 4 * 3 + + encodeLengthPrefixAndHash(journal, 0, ciphertextOutputHash); + encodeLengthPrefixAndHash(journal, 132, paramsHashes[e3Id]); + encodeLengthPrefixAndHash(journal, 264, inputRoot); + + verifier.verify(proof, imageId, sha256(journal)); + return true; + } + + /// @notice Encode length prefix and hash + /// @param journal The journal to encode into + /// @param startIndex The start index in the journal + /// @param hashVal The hash value to encode + function encodeLengthPrefixAndHash(bytes memory journal, uint256 startIndex, bytes32 hashVal) internal pure { + journal[startIndex] = 0x20; + startIndex += 4; + for (uint256 i = 0; i < 32; i++) { + journal[startIndex + i * 4] = hashVal[i]; } + } } diff --git a/examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockEnclave.sol b/examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockEnclave.sol index 2df0fb8d40..58cd6e65a6 100644 --- a/examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockEnclave.sol +++ b/examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockEnclave.sol @@ -5,33 +5,34 @@ // or FITNESS FOR A PARTICULAR PURPOSE. pragma solidity >=0.8.27; -import {E3} from "@enclave-e3/contracts/contracts/interfaces/IE3.sol"; -import {IE3Program} from "@enclave-e3/contracts/contracts/interfaces/IE3Program.sol"; -import {IDecryptionVerifier} from "@enclave-e3/contracts/contracts/interfaces/IDecryptionVerifier.sol"; +import { E3 } from "@enclave-e3/contracts/contracts/interfaces/IE3.sol"; +import { IE3Program } from "@enclave-e3/contracts/contracts/interfaces/IE3Program.sol"; +import { IDecryptionVerifier } from "@enclave-e3/contracts/contracts/interfaces/IDecryptionVerifier.sol"; contract MockEnclave { - bytes public plaintextOutput; + bytes public plaintextOutput; - function setPlaintextOutput(uint256[] memory plaintext) external { - plaintextOutput = abi.encode(plaintext); - } + function setPlaintextOutput(uint256[] memory plaintext) external { + plaintextOutput = abi.encode(plaintext); + } - function getE3(uint256 e3Id) external view returns (E3 memory) { - return E3({ - seed: 0, - threshold: [uint32(1), uint32(2)], - requestBlock: 0, - startWindow: [uint256(0), uint256(0)], - duration: 0, - expiration: 0, - encryptionSchemeId: bytes32(0), - e3Program: IE3Program(address(0)), - e3ProgramParams: bytes(""), - customParams: bytes(""), - decryptionVerifier: IDecryptionVerifier(address(0)), - committeePublicKey: bytes32(0), - ciphertextOutput: bytes32(0), - plaintextOutput: plaintextOutput - }); - } + function getE3(uint256 e3Id) external view returns (E3 memory) { + return + E3({ + seed: 0, + threshold: [uint32(1), uint32(2)], + requestBlock: 0, + startWindow: [uint256(0), uint256(0)], + duration: 0, + expiration: 0, + encryptionSchemeId: bytes32(0), + e3Program: IE3Program(address(0)), + e3ProgramParams: bytes(""), + customParams: bytes(""), + decryptionVerifier: IDecryptionVerifier(address(0)), + committeePublicKey: bytes32(0), + ciphertextOutput: bytes32(0), + plaintextOutput: plaintextOutput + }); + } } diff --git a/examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockRISC0Verifier.sol b/examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockRISC0Verifier.sol index 7d0ca3cb8e..bacbf3c88e 100644 --- a/examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockRISC0Verifier.sol +++ b/examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockRISC0Verifier.sol @@ -5,10 +5,10 @@ // or FITNESS FOR A PARTICULAR PURPOSE. pragma solidity ^0.8.27; -import {IRiscZeroVerifier, Receipt} from "risc0/IRiscZeroVerifier.sol"; +import { IRiscZeroVerifier, Receipt } from "risc0/IRiscZeroVerifier.sol"; contract MockRISC0Verifier is IRiscZeroVerifier { - function verify(bytes calldata seal, bytes32 imageId, bytes32 journalDigest) public view override {} + function verify(bytes calldata seal, bytes32 imageId, bytes32 journalDigest) public view override {} - function verifyIntegrity(Receipt calldata receipt) external view override {} + function verifyIntegrity(Receipt calldata receipt) external view override {} } diff --git a/examples/CRISP/packages/crisp-contracts/contracts/Mocks/RiscZeroGroth16Verifier.sol b/examples/CRISP/packages/crisp-contracts/contracts/Mocks/RiscZeroGroth16Verifier.sol index 4fd5d0955f..8403aac742 100644 --- a/examples/CRISP/packages/crisp-contracts/contracts/Mocks/RiscZeroGroth16Verifier.sol +++ b/examples/CRISP/packages/crisp-contracts/contracts/Mocks/RiscZeroGroth16Verifier.sol @@ -5,9 +5,9 @@ // or FITNESS FOR A PARTICULAR PURPOSE. pragma solidity >=0.8.27; -import {RiscZeroGroth16Verifier as RiscZero} from "risc0/groth16/RiscZeroGroth16Verifier.sol"; -import {ControlID} from "risc0/groth16/ControlID.sol"; +import { RiscZeroGroth16Verifier as RiscZero } from "risc0/groth16/RiscZeroGroth16Verifier.sol"; +import { ControlID } from "risc0/groth16/ControlID.sol"; contract RiscZeroGroth16Verifier is RiscZero { - constructor() RiscZero(ControlID.CONTROL_ROOT, ControlID.BN254_CONTROL_ID) {} + constructor() RiscZero(ControlID.CONTROL_ROOT, ControlID.BN254_CONTROL_ID) {} } diff --git a/package.json b/package.json index 32335a4052..9f42bf12f9 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "clean": "tsx scripts/clean.ts", "compile": "pnpm build:ts && pnpm rust:build", "lint": "eslint . && pnpm evm:lint && pnpm rust:lint && pnpm noir:lint", - "format": "prettier --write \"**/*.{js,jsx,mjs,cjs,json,md,mdx,ts,tsx,yml,yaml,css}\"", - "format:check": "prettier --check \"**/*.{js,jsx,mjs,cjs,json,md,mdx,ts,tsx,yml,yaml,css}\"", + "format": "prettier --write \"**/*.{js,jsx,mjs,cjs,json,md,mdx,ts,tsx,yml,yaml,css,sol}\"", + "format:check": "prettier --check \"**/*.{js,jsx,mjs,cjs,json,md,mdx,ts,tsx,yml,yaml,css,sol}\"", "check:license": "./scripts/check-license-headers.sh", "check:size": "./scripts/check-size.sh", "check:pnpm": "./scripts/check-pnpm.sh", diff --git a/templates/default/contracts/Mocks/MockRISC0Verifier.sol b/templates/default/contracts/Mocks/MockRISC0Verifier.sol index 3656241ae3..4505735869 100644 --- a/templates/default/contracts/Mocks/MockRISC0Verifier.sol +++ b/templates/default/contracts/Mocks/MockRISC0Verifier.sol @@ -5,14 +5,10 @@ // or FITNESS FOR A PARTICULAR PURPOSE. pragma solidity ^0.8.27; -import {IRiscZeroVerifier, Receipt} from "@risc0/ethereum/contracts/IRiscZeroVerifier.sol"; +import { IRiscZeroVerifier, Receipt } from "@risc0/ethereum/contracts/IRiscZeroVerifier.sol"; contract MockRISC0Verifier is IRiscZeroVerifier { - function verify( - bytes calldata seal, - bytes32 imageId, - bytes32 journalDigest - ) public view override {} + function verify(bytes calldata seal, bytes32 imageId, bytes32 journalDigest) public view override {} - function verifyIntegrity(Receipt calldata receipt) external view override {} + function verifyIntegrity(Receipt calldata receipt) external view override {} } diff --git a/templates/default/contracts/MyProgram.sol b/templates/default/contracts/MyProgram.sol index 3cb1d2d2a7..9bd048b9d8 100755 --- a/templates/default/contracts/MyProgram.sol +++ b/templates/default/contracts/MyProgram.sol @@ -5,133 +5,109 @@ // or FITNESS FOR A PARTICULAR PURPOSE. pragma solidity >=0.8.27; -import {IRiscZeroVerifier} from "@risc0/ethereum/contracts/IRiscZeroVerifier.sol"; -import {IE3Program} from "@enclave-e3/contracts/contracts/interfaces/IE3Program.sol"; -import {IEnclave} from "@enclave-e3/contracts/contracts/interfaces/IEnclave.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {LazyIMTData, InternalLazyIMT, PoseidonT3} from "@zk-kit/lazy-imt.sol/InternalLazyIMT.sol"; +import { IRiscZeroVerifier } from "@risc0/ethereum/contracts/IRiscZeroVerifier.sol"; +import { IE3Program } from "@enclave-e3/contracts/contracts/interfaces/IE3Program.sol"; +import { IEnclave } from "@enclave-e3/contracts/contracts/interfaces/IEnclave.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { LazyIMTData, InternalLazyIMT, PoseidonT3 } from "@zk-kit/lazy-imt.sol/InternalLazyIMT.sol"; contract MyProgram is IE3Program, Ownable { - using InternalLazyIMT for LazyIMTData; - // Constants - bytes32 public constant ENCRYPTION_SCHEME_ID = keccak256("fhe.rs:BFV"); - - uint8 public constant TREE_DEPTH = 20; - - // State variables - IEnclave public enclave; - IRiscZeroVerifier public verifier; - bytes32 public imageId; - - // Mappings - mapping(address => bool) public authorizedContracts; - mapping(uint256 e3Id => bytes32 paramsHash) public paramsHashes; - mapping(uint256 e3Id => LazyIMTData) public inputs; - - // Errors - error CallerNotAuthorized(); - error E3AlreadyInitialized(); - error E3DoesNotExist(); - error VerifierAddressZero(); - error AlreadyRegistered(); - error EmptyInputData(); - - event InputPublished(uint256 indexed e3Id, bytes data, uint256 index); - - /// @notice Initialize the contract, binding it to a specified RISC Zero verifier. - /// @param _enclave The Enclave contract address - /// @param _verifier The RISC Zero verifier address - /// @param _imageId The image ID for the guest program - constructor( - IEnclave _enclave, - IRiscZeroVerifier _verifier, - bytes32 _imageId - ) Ownable(msg.sender) { - require(address(_verifier) != address(0), VerifierAddressZero()); - - enclave = _enclave; - verifier = _verifier; - imageId = _imageId; - authorizedContracts[address(_enclave)] = true; - } - - /// @notice Validate the E3 program parameters - /// @param e3Id The E3 program ID - /// @param e3ProgramParams The E3 program parameters - function validate( - uint256 e3Id, - uint256, - bytes calldata e3ProgramParams, - bytes calldata - ) external returns (bytes32) { - require( - authorizedContracts[msg.sender] || msg.sender == owner(), - CallerNotAuthorized() - ); - require(paramsHashes[e3Id] == bytes32(0), E3AlreadyInitialized()); - paramsHashes[e3Id] = keccak256(e3ProgramParams); - - inputs[e3Id]._init(TREE_DEPTH); - - return ENCRYPTION_SCHEME_ID; - } - - /// @notice Validates input - /// @param sender The account that is submitting the input. - /// @param data The input to be verified. - /// @return input The input data. - function validateInput( - uint256 e3Id, - address sender, - bytes memory data - ) external returns (bytes memory input) { - if (data.length == 0) revert EmptyInputData(); - - // You can add your own validation logic here. - // EXAMPLE: https://github.com/gnosisguild/enclave/blob/main/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol - - input = data; - - uint256 index = inputs[e3Id].numberOfLeaves; - inputs[e3Id]._insert(PoseidonT3.hash([uint256(keccak256(data)), index])); - - emit InputPublished(e3Id, data, index); - } - - /// @notice Verify the proof - /// @param e3Id The E3 program ID - /// @param ciphertextOutputHash The hash of the ciphertext output - /// @param proof The proof to verify - function verify( - uint256 e3Id, - bytes32 ciphertextOutputHash, - bytes memory proof - ) external view override returns (bool) { - require(paramsHashes[e3Id] != bytes32(0), E3DoesNotExist()); - bytes32 inputRoot = bytes32(inputs[e3Id]._root()); - bytes memory journal = new bytes(396); // (32 + 1) * 4 * 3 - - encodeLengthPrefixAndHash(journal, 0, ciphertextOutputHash); - encodeLengthPrefixAndHash(journal, 132, paramsHashes[e3Id]); - encodeLengthPrefixAndHash(journal, 264, inputRoot); - - verifier.verify(proof, imageId, sha256(journal)); - return true; - } - - /// @notice Encode length prefix and hash - /// @param journal The journal to encode into - /// @param startIndex The start index in the journal - /// @param hashVal The hash value to encode - function encodeLengthPrefixAndHash( - bytes memory journal, - uint256 startIndex, - bytes32 hashVal - ) internal pure { - journal[startIndex] = 0x20; - startIndex += 4; - for (uint256 i = 0; i < 32; i++) { - journal[startIndex + i * 4] = hashVal[i]; - } + using InternalLazyIMT for LazyIMTData; + // Constants + bytes32 public constant ENCRYPTION_SCHEME_ID = keccak256("fhe.rs:BFV"); + + uint8 public constant TREE_DEPTH = 20; + + // State variables + IEnclave public enclave; + IRiscZeroVerifier public verifier; + bytes32 public imageId; + + // Mappings + mapping(address => bool) public authorizedContracts; + mapping(uint256 e3Id => bytes32 paramsHash) public paramsHashes; + mapping(uint256 e3Id => LazyIMTData) public inputs; + + // Errors + error CallerNotAuthorized(); + error E3AlreadyInitialized(); + error E3DoesNotExist(); + error VerifierAddressZero(); + error AlreadyRegistered(); + error EmptyInputData(); + + event InputPublished(uint256 indexed e3Id, bytes data, uint256 index); + + /// @notice Initialize the contract, binding it to a specified RISC Zero verifier. + /// @param _enclave The Enclave contract address + /// @param _verifier The RISC Zero verifier address + /// @param _imageId The image ID for the guest program + constructor(IEnclave _enclave, IRiscZeroVerifier _verifier, bytes32 _imageId) Ownable(msg.sender) { + require(address(_verifier) != address(0), VerifierAddressZero()); + + enclave = _enclave; + verifier = _verifier; + imageId = _imageId; + authorizedContracts[address(_enclave)] = true; + } + + /// @notice Validate the E3 program parameters + /// @param e3Id The E3 program ID + /// @param e3ProgramParams The E3 program parameters + function validate(uint256 e3Id, uint256, bytes calldata e3ProgramParams, bytes calldata) external returns (bytes32) { + require(authorizedContracts[msg.sender] || msg.sender == owner(), CallerNotAuthorized()); + require(paramsHashes[e3Id] == bytes32(0), E3AlreadyInitialized()); + paramsHashes[e3Id] = keccak256(e3ProgramParams); + + inputs[e3Id]._init(TREE_DEPTH); + + return ENCRYPTION_SCHEME_ID; + } + + /// @notice Validates input + /// @param sender The account that is submitting the input. + /// @param data The input to be verified. + /// @return input The input data. + function validateInput(uint256 e3Id, address sender, bytes memory data) external returns (bytes memory input) { + if (data.length == 0) revert EmptyInputData(); + + // You can add your own validation logic here. + // EXAMPLE: https://github.com/gnosisguild/enclave/blob/main/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol + + input = data; + + uint256 index = inputs[e3Id].numberOfLeaves; + inputs[e3Id]._insert(PoseidonT3.hash([uint256(keccak256(data)), index])); + + emit InputPublished(e3Id, data, index); + } + + /// @notice Verify the proof + /// @param e3Id The E3 program ID + /// @param ciphertextOutputHash The hash of the ciphertext output + /// @param proof The proof to verify + function verify(uint256 e3Id, bytes32 ciphertextOutputHash, bytes memory proof) external view override returns (bool) { + require(paramsHashes[e3Id] != bytes32(0), E3DoesNotExist()); + bytes32 inputRoot = bytes32(inputs[e3Id]._root()); + bytes memory journal = new bytes(396); // (32 + 1) * 4 * 3 + + encodeLengthPrefixAndHash(journal, 0, ciphertextOutputHash); + encodeLengthPrefixAndHash(journal, 132, paramsHashes[e3Id]); + encodeLengthPrefixAndHash(journal, 264, inputRoot); + + verifier.verify(proof, imageId, sha256(journal)); + return true; + } + + /// @notice Encode length prefix and hash + /// @param journal The journal to encode into + /// @param startIndex The start index in the journal + /// @param hashVal The hash value to encode + function encodeLengthPrefixAndHash(bytes memory journal, uint256 startIndex, bytes32 hashVal) internal pure { + journal[startIndex] = 0x20; + startIndex += 4; + for (uint256 i = 0; i < 32; i++) { + journal[startIndex + i * 4] = hashVal[i]; } + } }