Commit a266b32d authored by John Doe's avatar John Doe
Browse files

monorepo.

parent 85ec022c

Too many changes to show.

To preserve performance only 360 of 360+ files are displayed.
This source diff could not be displayed because it is too large. You can view the blob instead.
/* Autogenerated file, do not edit! */
/* eslint-disable */
import { Noir, InputMap, CompiledCircuit, ForeignCallHandler } from "@noir-lang/noir_js"
export type { ForeignCallHandler } from "@noir-lang/noir_js"
export type Field = string;
export type VerifierTx = {
key_hash: Field;
verification_key: Field[];
proof: Field[];
};
export type Verifier = {
key_hash: Field;
verification_key: Field[];
proof: Field[];
aggregation_object: Field[];
};
export type PublicInputs = {
current_root: Field;
utxo_root: Field;
deposit_amount: Field;
withdrawals: Field;
commitment_in: Field[];
commitment_out: Field[];
nullifier_hashes: Field[];
contract_only_inputs: Field;
};
export type RecursionInputs = {
accumulator: Field;
tx_verifier: VerifierTx;
recursion_verifier: Verifier;
previous_accumulator: Field;
tx: PublicInputs;
};
export const rollup_transaction_circuit: CompiledCircuit = {"abi":{"parameters":[{"name":"tx_verifier","type":{"kind":"struct","path":"structs::VerifierTx","fields":[{"name":"key_hash","type":{"kind":"field"}},{"name":"verification_key","type":{"kind":"array","length":114,"type":{"kind":"field"}}},{"name":"proof","type":{"kind":"array","length":93,"type":{"kind":"field"}}}]},"visibility":"private"},{"name":"recursion_verifier","type":{"kind":"struct","path":"structs::Verifier","fields":[{"name":"key_hash","type":{"kind":"field"}},{"name":"verification_key","type":{"kind":"array","length":114,"type":{"kind":"field"}}},{"name":"proof","type":{"kind":"array","length":109,"type":{"kind":"field"}}},{"name":"aggregation_object","type":{"kind":"array","length":16,"type":{"kind":"field"}}}]},"visibility":"private"},{"name":"previous_accumulator","type":{"kind":"field"},"visibility":"private"},{"name":"tx","type":{"kind":"struct","path":"structs::PublicInputs","fields":[{"name":"current_root","type":{"kind":"field"}},{"name":"utxo_root","type":{"kind":"field"}},{"name":"deposit_amount","type":{"kind":"field"}},{"name":"withdrawals","type":{"kind":"field"}},{"name":"commitment_in","type":{"kind":"array","length":16,"type":{"kind":"field"}}},{"name":"commitment_out","type":{"kind":"array","length":16,"type":{"kind":"field"}}},{"name":"nullifier_hashes","type":{"kind":"array","length":16,"type":{"kind":"field"}}},{"name":"contract_only_inputs","type":{"kind":"field"}}]},"visibility":"private"}],"param_witnesses":{"previous_accumulator":[{"start":448,"end":449}],"recursion_verifier":[{"start":208,"end":448}],"tx":[{"start":449,"end":502}],"tx_verifier":[{"start":0,"end":208}]},"return_type":{"abi_type":{"kind":"struct","path":"structs::RecursionInputs","fields":[{"name":"accumulator","type":{"kind":"field"}},{"name":"tx_verifier","type":{"kind":"struct","path":"structs::VerifierTx","fields":[{"name":"key_hash","type":{"kind":"field"}},{"name":"verification_key","type":{"kind":"array","length":114,"type":{"kind":"field"}}},{"name":"proof","type":{"kind":"array","length":93,"type":{"kind":"field"}}}]}},{"name":"recursion_verifier","type":{"kind":"struct","path":"structs::Verifier","fields":[{"name":"key_hash","type":{"kind":"field"}},{"name":"verification_key","type":{"kind":"array","length":114,"type":{"kind":"field"}}},{"name":"proof","type":{"kind":"array","length":109,"type":{"kind":"field"}}},{"name":"aggregation_object","type":{"kind":"array","length":16,"type":{"kind":"field"}}}]}},{"name":"previous_accumulator","type":{"kind":"field"}},{"name":"tx","type":{"kind":"struct","path":"structs::PublicInputs","fields":[{"name":"current_root","type":{"kind":"field"}},{"name":"utxo_root","type":{"kind":"field"}},{"name":"deposit_amount","type":{"kind":"field"}},{"name":"withdrawals","type":{"kind":"field"}},{"name":"commitment_in","type":{"kind":"array","length":16,"type":{"kind":"field"}}},{"name":"commitment_out","type":{"kind":"array","length":16,"type":{"kind":"field"}}},{"name":"nullifier_hashes","type":{"kind":"array","length":16,"type":{"kind":"field"}}},{"name":"contract_only_inputs","type":{"kind":"field"}}]}}]},"visibility":"public"},"return_witnesses":[6360,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501]},"bytecode":"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"};
export async function rollup_transaction(tx_verifier: VerifierTx, recursion_verifier: Verifier, previous_accumulator: Field, tx: PublicInputs, foreignCallHandler?: ForeignCallHandler): Promise<RecursionInputs> {
const program = new Noir(rollup_transaction_circuit);
const args: InputMap = { tx_verifier, recursion_verifier, previous_accumulator, tx };
const { returnValue } = await program.execute(args, foreignCallHandler);
return returnValue as RecursionInputs;
}
/* Autogenerated file, do not edit! */
/* eslint-disable */
import { Noir, InputMap, CompiledCircuit, ForeignCallHandler } from "@noir-lang/noir_js"
export type { ForeignCallHandler } from "@noir-lang/noir_js"
export type Field = string;
export type PublicInputs = {
current_root: Field;
utxo_root: Field;
deposit_amount: Field;
withdrawals: Field;
commitment_in: Field[];
commitment_out: Field[];
nullifier_hashes: Field[];
contract_only_inputs: Field;
};
export const tx_as_hash_circuit: CompiledCircuit = {"abi":{"parameters":[{"name":"tx","type":{"kind":"struct","path":"structs::PublicInputs","fields":[{"name":"current_root","type":{"kind":"field"}},{"name":"utxo_root","type":{"kind":"field"}},{"name":"deposit_amount","type":{"kind":"field"}},{"name":"withdrawals","type":{"kind":"field"}},{"name":"commitment_in","type":{"kind":"array","length":16,"type":{"kind":"field"}}},{"name":"commitment_out","type":{"kind":"array","length":16,"type":{"kind":"field"}}},{"name":"nullifier_hashes","type":{"kind":"array","length":16,"type":{"kind":"field"}}},{"name":"contract_only_inputs","type":{"kind":"field"}}]},"visibility":"private"}],"param_witnesses":{"tx":[{"start":0,"end":53}]},"return_type":{"abi_type":{"kind":"field"},"visibility":"private"},"return_witnesses":[53]},"bytecode":"H4sIAAAAAAAA/13R1QpCURRFUdtrd3d36///mag4FPGADLlve65w6P1uz1/Y/5eBb5939/1lhFHGGGeCSQZMMc0Ms8wxzwKLLLHMCqussc4Gm2yxzQ677LHPAYccccwJp5xxzgWXXHHNDbfccc8DjzzxzAuv/Oz4a8huEXvF7JSwT2CXtD2ydsjrX9S9rHdV57q+TV3benZ17Os31G2s11SnuT5LXdZ6bHXYu//o7rN7r393/d/6fQ/p5LXj2QIAAA=="};
export async function tx_as_hash(tx: PublicInputs, foreignCallHandler?: ForeignCallHandler): Promise<Field> {
const program = new Noir(tx_as_hash_circuit);
const args: InputMap = { tx };
const { returnValue } = await program.execute(args, foreignCallHandler);
return returnValue as Field;
}
/* Autogenerated file, do not edit! */
/* eslint-disable */
import { Noir, InputMap, CompiledCircuit, ForeignCallHandler } from "@noir-lang/noir_js"
export type { ForeignCallHandler } from "@noir-lang/noir_js"
export type Field = string;
export type UTXO_New = {
secret: Field;
amount: Field;
asset_type: Field;
};
export const utxo_to_commitment_circuit: CompiledCircuit = {"abi":{"parameters":[{"name":"utxo","type":{"kind":"struct","path":"structs::UTXO_New","fields":[{"name":"secret","type":{"kind":"field"}},{"name":"amount","type":{"kind":"field"}},{"name":"asset_type","type":{"kind":"field"}}]},"visibility":"private"}],"param_witnesses":{"utxo":[{"start":0,"end":3}]},"return_type":{"abi_type":{"kind":"field"},"visibility":"private"},"return_witnesses":[4]},"bytecode":"H4sIAAAAAAAA/11MWwoAMAjqsY/d/8CyYAaWIJaKbh+nGLy9eKkNUFPyFA/0wR3Irkm392P92h14YkM2CqEAAAA="};
export async function utxo_to_commitment(utxo: UTXO_New, foreignCallHandler?: ForeignCallHandler): Promise<Field> {
const program = new Noir(utxo_to_commitment_circuit);
const args: InputMap = { utxo };
const { returnValue } = await program.execute(args, foreignCallHandler);
return returnValue as Field;
}
use dep::std;
use crate::structs;
fn field_to_u8(_value: Field) -> [u8; 32] {
let _array = _value.to_be_bytes(32);
let mut array: [u8; 32] = [0; 32];
for i in 0 .. 32 {
array[i] = _array[i];
}
array
}
fn hash<N>(data: [Field; N]) -> Field {
std::hash::pedersen_hash(data)
}
#[export]
fn utxo_to_commitment(utxo: structs::UTXO_New) -> Field {
utxo.to_commitment()
}
#[export]
fn pedersen_left_right(left: Field, right: Field) -> Field {
std::hash::pedersen_hash([left, right])
}
#[export]
fn keccak_tx(tx: structs::PublicInputs) -> Field {
let mut hash_array: [Field; 53] = [0; 53];
hash_array[0] = tx.current_root;
hash_array[1] = tx.utxo_root;
hash_array[2] = tx.deposit_amount;
hash_array[3] = tx.contract_only_inputs;
hash_array[4] = tx.withdrawals;
for i in 0..16 {
hash_array[5 + i] = tx.commitment_in[i];
hash_array[21 + i] = tx.commitment_out[i];
hash_array[37 + i] = tx.nullifier_hashes[i];
}
let u8_array = tx_to_u8(hash_array);
hash_to_field(std::hash::keccak256(u8_array, u8_array.len() as u32))
}
fn bytes_tx_without_deposit(tx: structs::PublicInputs) -> [u8; 1696] {
let mut hash_array: [Field; 53] = [0; 53];
hash_array[0] = tx.current_root;
hash_array[1] = tx.utxo_root;
hash_array[2] = tx.deposit_amount;
hash_array[3] = tx.contract_only_inputs;
hash_array[4] = tx.withdrawals;
for i in 0..16 {
hash_array[5 + i] = tx.commitment_in[i];
hash_array[21 + i] = tx.commitment_out[i];
hash_array[37 + i] = tx.nullifier_hashes[i];
}
tx_to_u8(hash_array)
}
#[export]
fn keccak_contract_only_inputs(contract_only_inputs: structs::ContractOnlyInputs) -> Field {
let mut hash_array: [Field; 100] = [0; 100];
hash_array[0] = contract_only_inputs.timestamp;
hash_array[1] = contract_only_inputs.deadline;
hash_array[2] = contract_only_inputs.signature_hash;
hash_array[3] = contract_only_inputs.price_limit;
for i in 0..16 {
hash_array[4 + i] = contract_only_inputs.recipients[i];
hash_array[20 + i] = contract_only_inputs.swap_amounts[i];
hash_array[36 + i] = contract_only_inputs.uids[i];
hash_array[52 + (i * 3)] = contract_only_inputs.encrypted_utxo[i].secret;
hash_array[53 + (i * 3)] = contract_only_inputs.encrypted_utxo[i].amount;
hash_array[54 + (i * 3)] = contract_only_inputs.encrypted_utxo[i].data;
}
let u8_array = contract_only_to_u8(hash_array);
hash_to_field(std::hash::keccak256(u8_array, u8_array.len() as u32))
}
#[export]
fn keccak_contract_only_inputs_without_deposit(contract_only_inputs: structs::ContractOnlyInputs) -> Field {
let mut hash_array: [Field; 99] = [0; 99];
hash_array[0] = contract_only_inputs.timestamp;
hash_array[1] = contract_only_inputs.deadline;
hash_array[2] = contract_only_inputs.price_limit;
for i in 0..16 {
hash_array[3 + i] = contract_only_inputs.recipients[i];
hash_array[19 + i] = contract_only_inputs.swap_amounts[i];
hash_array[35 + i] = contract_only_inputs.uids[i];
hash_array[51 + (i * 3)] = contract_only_inputs.encrypted_utxo[i].secret;
hash_array[52 + (i * 3)] = contract_only_inputs.encrypted_utxo[i].amount;
hash_array[53 + (i * 3)] = contract_only_inputs.encrypted_utxo[i].data;
}
let u8_array = contract_only_without_deposit_to_u8(hash_array);
hash_to_field(std::hash::keccak256(u8_array, u8_array.len() as u32))
}
fn bytes_contract_only_inputs_without_deposit(contract_only_inputs: structs::ContractOnlyInputs) -> [u8; 3168] {
let mut hash_array: [Field; 99] = [0; 99];
hash_array[0] = contract_only_inputs.timestamp;
hash_array[1] = contract_only_inputs.deadline;
hash_array[2] = contract_only_inputs.price_limit;
for i in 0..16 {
hash_array[3 + i] = contract_only_inputs.recipients[i];
hash_array[19 + i] = contract_only_inputs.swap_amounts[i];
hash_array[35 + i] = contract_only_inputs.uids[i];
hash_array[51 + (i * 3)] = contract_only_inputs.encrypted_utxo[i].secret;
hash_array[52 + (i * 3)] = contract_only_inputs.encrypted_utxo[i].amount;
hash_array[53 + (i * 3)] = contract_only_inputs.encrypted_utxo[i].data;
}
contract_only_without_deposit_to_u8(hash_array)
}
#[export]
fn contract_only_inputs_with_signature_hash(contract_only_inputs: structs::ContractOnlyInputs) -> structs::ContractOnlyInputs {
let mut hash_array: [Field; 99] = [0; 99];
hash_array[0] = contract_only_inputs.timestamp;
hash_array[1] = contract_only_inputs.deadline;
hash_array[2] = contract_only_inputs.price_limit;
for i in 0..16 {
hash_array[3 + i] = contract_only_inputs.recipients[i];
hash_array[19 + i] = contract_only_inputs.swap_amounts[i];
hash_array[35 + i] = contract_only_inputs.uids[i];
hash_array[51 + (i * 3)] = contract_only_inputs.encrypted_utxo[i].secret;
hash_array[52 + (i * 3)] = contract_only_inputs.encrypted_utxo[i].amount;
hash_array[53 + (i * 3)] = contract_only_inputs.encrypted_utxo[i].data;
}
let u8_array = contract_only_without_deposit_to_u8(hash_array);
let contract_only_inputs_with_hash = structs::ContractOnlyInputs {
timestamp: contract_only_inputs.timestamp,
deadline: contract_only_inputs.deadline,
signature_hash: hash_to_field(std::hash::keccak256(u8_array, u8_array.len() as u32)),
price_limit: contract_only_inputs.price_limit,
recipients: contract_only_inputs.recipients,
swap_amounts: contract_only_inputs.swap_amounts,
uids: contract_only_inputs.uids,
encrypted_utxo: contract_only_inputs.encrypted_utxo
};
contract_only_inputs_with_hash
}
fn hash_tx(tx: structs::PublicInputs) -> Field {
let mut hash_array: [Field; 53] = [0; 53];
hash_array[0] = tx.current_root;
hash_array[1] = tx.utxo_root;
hash_array[2] = tx.deposit_amount;
hash_array[3] = tx.withdrawals;
for i in 0..16 {
hash_array[4 + i] = tx.commitment_in[i];
hash_array[20 + i] = tx.commitment_out[i];
hash_array[36 + i] = tx.nullifier_hashes[i];
}
hash_array[52] = tx.contract_only_inputs;
hash(hash_array)
}
fn hash_to_field(hash: [u8; 32]) -> Field {
let mut keccak_field: Field = 0;
for p in 0..32 {
let bytes_field: Field = hash[31 - p] as Field;
keccak_field += bytes_field * 256.pow_32(p as Field);
}
keccak_field
}
fn tx_to_u8(pi_fields: [Field; 53]) -> [u8; 1696] {
let mut keccak_array: [u8; 1696] = [0; 1696];
for i in 0..pi_fields.len() {
let mut byte_slice = pi_fields[i].to_be_bytes(32);
for j in 0..32 {
keccak_array[32*i + j] = byte_slice[j];
}
}
keccak_array
}
fn contract_only_to_u8(pi_fields: [Field; 100]) -> [u8; 3200] {
let mut keccak_array: [u8; 3200] = [0; 3200];
for i in 0..pi_fields.len() {
let mut byte_slice = pi_fields[i].to_be_bytes(32);
for j in 0..32 {
keccak_array[32*i + j] = byte_slice[j];
}
}
keccak_array
}
fn contract_only_without_deposit_to_u8(pi_fields: [Field; 99]) -> [u8; 3168] {
let mut keccak_array: [u8; 3168] = [0; 3168];
for i in 0..pi_fields.len() {
let mut byte_slice = pi_fields[i].to_be_bytes(32);
for j in 0..32 {
keccak_array[32*i + j] = byte_slice[j];
}
}
keccak_array
}
fn batch_to_u8(pi_fields: [Field; 19]) -> [u8; 608] {
let mut keccak_array: [u8; 608] = [0; 608];
for i in 0..pi_fields.len() {
let mut byte_slice = pi_fields[i].to_be_bytes(32);
for j in 0..32 {
keccak_array[32*i + j] = byte_slice[j];
}
}
keccak_array
}
fn hash_tree_four(leaves: [Field; 16]) -> Field {
let mut tx_tree: [Field; 16] = leaves;
for l in 0..8 { tx_tree[l] = hash([tx_tree[2*l], tx_tree[2*l + 1]]); }
for l in 0..4 { tx_tree[l] = hash([tx_tree[2*l], tx_tree[2*l + 1]]); }
for l in 0..2 { tx_tree[l] = hash([tx_tree[2*l], tx_tree[2*l + 1]]); }
hash([tx_tree[0], tx_tree[1]])
}
fn compute_merkle_root<N>(leaf: Field, index: Field, hash_path: [Field; N]) -> Field {
let n = hash_path.len();
let index_bits = index.to_le_bits(n as u32);
let mut current = leaf;
for i in 0..n {
let path_bit = index_bits[i] as bool;
let (hash_left, hash_right) = if path_bit {
(hash_path[i], current)
} else {
(current, hash_path[i])
};
current = hash([hash_left, hash_right]);
}
current
}
fn compute_sibling_path<N>(sibling_path: [Field; N], new_leaf: Field, insertion_index: Field) -> [Field; N] {
let index_bits: [u1] = insertion_index.to_le_bits(N as u32);
let mut new_sibling_path: [Field; N] = [0; N];
let mut current_hash: Field = new_leaf;
let mut zero_found: bool = false;
for i in 0..N {
let path_bit = index_bits[i] as bool;
if (!zero_found) {
if (!path_bit) {
zero_found = true;
new_sibling_path[i] = current_hash;
} else {
new_sibling_path[i] = structs::zero_hashes[i];
}
} else {
new_sibling_path[i] = sibling_path[i];
}
if (path_bit) {
current_hash = hash([sibling_path[i], current_hash]);
} else {
current_hash = hash([current_hash, sibling_path[i]]);
}
}
new_sibling_path
}
\ No newline at end of file
use dep::std;
mod structs;
mod hash;
global utxo_depth = 4;
global batch_depth = 4;
global state_depth = 20;
global ZERO_VALUE = 0x016a430aa58685aba1311244a973a3bc358859da86784be51094368e8fb6f720;
// Levels of an empty pedersen Merkle tree with zero_leaf = sha256("Momiji") % Field Modulus
global zero_hashes: [Field; 20] = [
0x016a430aa58685aba1311244a973a3bc358859da86784be51094368e8fb6f720,
0x018d39625c19fa2cfbebdb940a66d7040bb0ef1b59ff6afd92a13a6b9b2d9865,
0x096c00ebc0c52478316b6b9fd16d0cd94c5f45bbe45bbfa8c606197c6119d41f,
0x2eaefd3bdd1bfbddd8fc5d972ded58617f752b3e88bd17b791e85e7b8eaacb47,
0x11d25ff6aa8a431fbce8e8d9a87a2d7986adf38e724fbe47f15752d0931f14d8,
0x01e8677aa02546bd7105a7a9fd31c4ef16b69d1bde90f36e97585d7cc31d50e4,
0x2520a755a532994c78f35b89938fbc864ec31ec4fc00363f83f9b12f04980c6a,
0x21a666842842d5323e51fdda10300c763d6b07e1b797ef3b0bd3690d667445bc,
0x1ce681d6f42b70668c369e7f786166e421dc840f0529bbd065d6b02d14ae0fe8,
0x1819b90a43ee28f652735708b2ef01428e21b84872bb3c7576de6e35d107c8ed,
0x063d7001431110a5949f5946a501fd28f64159f36ab4b80601ca305ac107b3db,
0x09aeef7a06f7daf368d797c2e686c7e9884f000de0bd9d8e73392378b0b1be38,
0x137121fd5d795eeecc61ec6a6de66abc589d241540139178cd5408b6ccb32a6e,
0x01a93f70a1b7482e0b32e76ce49a1e3c1fc2b5cd4789b6af749d78c42791c21a,
0x217bf2cc1f1b505a29b162a7889866af2728f5c6708560c9cc5e987b194c9c81,
0x1461dae57d7df7e580279637e5d94e0d734b908dc22aec5c52ed00187050a030,
0x295933dd65294cbf4b2c64d8a0daa6983d35d1f64d5adc2c44bd9d370086d7dc,
0x24650084f0db0fa4e8234fb251ad046b3ddcb7d6f59160b35cc4086e9e196b80,
0x15086d0394bd68847e3e36241cc7b3966f96efdd30a37e9e1e85027a6dacbed2,
0x1f87a17564f06581c1e1b2d716120990f898893ecc0e076363c56c5e3b56ef7b
];
global zero_root: Field = 0x06f93f503e77fcdcacfe622e66adc639b63e8c0083f5cab5d71d461aa4562c92;
#[export]
fn keccak_tx_with_deposit(
current_root: Field,
deposit_amount: Field,
withdrawals: [Field; 16],
utxo_spendable: [structs::UTXO_Spendable; 16],
utxo_new: [structs::UTXO_New; 16],
contract_only_inputs: structs::ContractOnlyInputs
) -> Field {
let withdrawal_amount: Field = withdrawals.reduce(|a,b| a + b);
let mut commitment_in: [Field; 16] = [ZERO_VALUE; 16];
let mut commitment_out: [Field; 16] = [ZERO_VALUE; 16];
let mut utxo_root: Field = 0;
let empty_utxo = structs::UTXO {owner: 0, amount: ZERO_VALUE, asset_type: 0};
let mut utxo_out: [structs::UTXO; 16] = [empty_utxo; 16];
let mut secrets: [Field; 16] = [0; 16];
let mut oracle: [Field; 16] = [ZERO_VALUE; 16];
let mut old_root_proof: [Field; 16] = [0; 16];
let mut utxo_in: [structs::UTXO; 16] = [empty_utxo; 16];
let mut nullifier_hashes: [Field; 16] = [ZERO_VALUE; 16];
let empty_merkle_proofs = structs::MerkleProof {
path_utxo: [0; utxo_depth],
path_tx: [0; batch_depth],
path_historic: [0; state_depth],
index_utxo: 0,
index_tx: 0,
index_historic: 0
};
let mut merkle_proofs: [structs::MerkleProof; 16] = [empty_merkle_proofs; 16];
for i in 0..16 {
if (utxo_spendable[i].amount != ZERO_VALUE) {
utxo_in[i] = structs::UTXO {owner: utxo_spendable[i].owner(), amount: utxo_spendable[i].amount, asset_type: utxo_spendable[i].asset_type};
secrets[i] = utxo_spendable[i].secret;
nullifier_hashes[i] = utxo_spendable[i].nullifier_hash();
commitment_in[i] = utxo_spendable[i].to_commitment();
if (utxo_spendable[i].spend_in_same_batch == false) {
commitment_in[i] = ZERO_VALUE;
old_root_proof[i] = utxo_spendable[i].old_root_proof;
merkle_proofs[i] = utxo_spendable[i].merkle_proof;
}
}
if (utxo_new[i].secret != 0) {
commitment_out[i] = utxo_new[i].to_commitment();
utxo_out[i] = structs::UTXO { owner: utxo_new[i].owner(), amount: utxo_new[i].amount, asset_type: utxo_new[i].asset_type };
}
}
utxo_root = hash::hash_tree_four(commitment_out);
let public_inputs = structs::PublicInputs {
current_root: current_root,
utxo_root: utxo_root,
deposit_amount: deposit_amount,
withdrawals: withdrawal_amount,
commitment_in: commitment_in,
commitment_out: commitment_out,
nullifier_hashes: nullifier_hashes,
contract_only_inputs: contract_only_inputs.as_keccak()
};
public_inputs.as_keccak()
}
#[export]
fn keccak_tx_no_deposit(
current_root: Field,
deposit_amount: Field,
withdrawals: [Field; 16],
utxo_spendable: [structs::UTXO_Spendable; 16],
utxo_new: [structs::UTXO_New; 16],
contract_only_inputs: structs::ContractOnlyInputs
) -> Field {
let withdrawal_amount: Field = withdrawals.reduce(|a,b| a + b);
let mut commitment_in: [Field; 16] = [ZERO_VALUE; 16];
let mut commitment_out: [Field; 16] = [ZERO_VALUE; 16];
let mut utxo_root: Field = 0;
let empty_utxo = structs::UTXO {owner: 0, amount: ZERO_VALUE, asset_type: 0};
let mut utxo_out: [structs::UTXO; 16] = [empty_utxo; 16];
let mut secrets: [Field; 16] = [0; 16];
let mut oracle: [Field; 16] = [ZERO_VALUE; 16];
let mut old_root_proof: [Field; 16] = [0; 16];
let mut utxo_in: [structs::UTXO; 16] = [empty_utxo; 16];
let mut nullifier_hashes: [Field; 16] = [ZERO_VALUE; 16];
let empty_merkle_proofs = structs::MerkleProof {
path_utxo: [0; utxo_depth],
path_tx: [0; batch_depth],
path_historic: [0; state_depth],
index_utxo: 0,
index_tx: 0,
index_historic: 0
};
let mut merkle_proofs: [structs::MerkleProof; 16] = [empty_merkle_proofs; 16];
for i in 0..16 {
if (utxo_spendable[i].amount != ZERO_VALUE) {
utxo_in[i] = structs::UTXO {owner: utxo_spendable[i].owner(), amount: utxo_spendable[i].amount, asset_type: utxo_spendable[i].asset_type};
secrets[i] = utxo_spendable[i].secret;
nullifier_hashes[i] = utxo_spendable[i].nullifier_hash();
commitment_in[i] = utxo_spendable[i].to_commitment();
if (utxo_spendable[i].spend_in_same_batch == false) {
commitment_in[i] = ZERO_VALUE;
old_root_proof[i] = utxo_spendable[i].old_root_proof;
merkle_proofs[i] = utxo_spendable[i].merkle_proof;
}
}
if (utxo_new[i].secret != 0) {
commitment_out[i] = utxo_new[i].to_commitment();
utxo_out[i] = structs::UTXO { owner: utxo_new[i].owner(), amount: utxo_new[i].amount, asset_type: utxo_new[i].asset_type };
}
}
utxo_root = hash::hash_tree_four(commitment_out);
let public_inputs = structs::PublicInputs {
current_root: current_root,
utxo_root: utxo_root,
deposit_amount: deposit_amount,
withdrawals: withdrawal_amount,
commitment_in: commitment_in,
commitment_out: commitment_out,
nullifier_hashes: nullifier_hashes,
contract_only_inputs: contract_only_inputs.as_keccak_without_deposit()
};
public_inputs.as_keccak()
}
#[export]
fn create_transaction(
current_root: Field,
deposit_amount: Field,
withdrawals: [Field; 16],
utxo_spendable: [structs::UTXO_Spendable; 16],
utxo_new: [structs::UTXO_New; 16],
contract_only_inputs: structs::ContractOnlyInputs
) -> pub structs::TransactionInputs {
let withdrawal_amount: Field = withdrawals.reduce(|a,b| a + b);
let mut commitment_in: [Field; 16] = [ZERO_VALUE; 16];
let mut commitment_out: [Field; 16] = [ZERO_VALUE; 16];
let mut utxo_root: Field = 0;
let empty_utxo = structs::UTXO {owner: 0, amount: ZERO_VALUE, asset_type: 0};
let mut utxo_out: [structs::UTXO; 16] = [empty_utxo; 16];
let mut secrets: [Field; 16] = [0; 16];
let mut oracle: [Field; 16] = [ZERO_VALUE; 16];
let mut old_root_proof: [Field; 16] = [0; 16];
let mut utxo_in: [structs::UTXO; 16] = [empty_utxo; 16];
let mut nullifier_hashes: [Field; 16] = [ZERO_VALUE; 16];
let empty_merkle_proofs = structs::MerkleProof {
path_utxo: [0; utxo_depth],
path_tx: [0; batch_depth],
path_historic: [0; state_depth],
index_utxo: 0,
index_tx: 0,
index_historic: 0
};
let mut merkle_proofs: [structs::MerkleProof; 16] = [empty_merkle_proofs; 16];
for i in 0..16 {
if (utxo_spendable[i].amount != ZERO_VALUE) {
utxo_in[i] = structs::UTXO {owner: utxo_spendable[i].owner(), amount: utxo_spendable[i].amount, asset_type: utxo_spendable[i].asset_type};
secrets[i] = utxo_spendable[i].secret;
nullifier_hashes[i] = utxo_spendable[i].nullifier_hash();
commitment_in[i] = utxo_spendable[i].to_commitment();
if (utxo_spendable[i].spend_in_same_batch == false) {
commitment_in[i] = ZERO_VALUE;
old_root_proof[i] = utxo_spendable[i].old_root_proof;
merkle_proofs[i] = utxo_spendable[i].merkle_proof;
}
}
if (utxo_new[i].secret != 0) {
commitment_out[i] = utxo_new[i].to_commitment();
utxo_out[i] = structs::UTXO { owner: utxo_new[i].owner(), amount: utxo_new[i].amount, asset_type: utxo_new[i].asset_type };
}
}
utxo_root = hash::hash_tree_four(commitment_out);
let public_inputs = structs::PublicInputs {
current_root: current_root,
utxo_root: utxo_root,
deposit_amount: deposit_amount,
withdrawals: withdrawal_amount,
commitment_in: commitment_in,
commitment_out: commitment_out,
nullifier_hashes: nullifier_hashes,
contract_only_inputs: contract_only_inputs.as_keccak()
};
let private_inputs = structs::PrivateInputs {
oracle: oracle,
old_root_proof: old_root_proof,
secrets: secrets,
utxo_in: utxo_in,
merkle_proofs: merkle_proofs,
utxo_out: utxo_out
};
let public_inputs_hash: Field = hash::hash_tx(public_inputs);
let transaction_inputs = structs::TransactionInputs {
public_inputs_hash: public_inputs_hash,
public_inputs: public_inputs,
private_inputs: private_inputs
};
transaction_inputs
}
#[export]
fn tx_as_hash(
tx: structs::PublicInputs
) -> Field {
tx.as_hash()
}
#[export]
fn rollup_transaction(
tx_verifier: structs::VerifierTx,
recursion_verifier: structs::Verifier,
previous_accumulator: Field,
tx: structs::PublicInputs
) -> pub structs::RecursionInputs {
let tx_as_keccak: Field = tx.as_keccak();
let mut accumulator_preimage: [u8; 128] = [0; 128];
for i in 0..32 {
accumulator_preimage[i] = hash::field_to_u8(previous_accumulator)[i];
accumulator_preimage[i + 32] = hash::field_to_u8(tx_as_keccak)[i];
accumulator_preimage[i + 64] = hash::field_to_u8(tx_verifier.key_hash)[i];
accumulator_preimage[i + 96] = hash::field_to_u8(recursion_verifier.key_hash)[i];
}
let accumulator: Field = hash::hash_to_field(std::hash::keccak256(accumulator_preimage, accumulator_preimage.len() as u32));
let recursion_inputs = structs::RecursionInputs {
accumulator: accumulator,
tx_verifier: tx_verifier,
recursion_verifier: recursion_verifier,
previous_accumulator: previous_accumulator,
tx: tx
};
recursion_inputs
}
#[export]
fn publish_batch(
accumulator: Field,
hist_tree_input: structs::HistoricTreeInput,
tx_verifier: structs::VerifierTx,
recursion_verifier: structs::Verifier
) -> pub structs::PublishInputs {
let old_state_root: Field = hist_tree_input.leaf;
let tx_root: Field = hash::hash_tree_four(hist_tree_input.utxo_roots);
let batch_oracle: Field = ZERO_VALUE;
let batch_root: Field = hash::hash([tx_root, batch_oracle]);
let new_state_root: Field = hash::hash([batch_root, old_state_root]);
let new_historic_path: [Field; state_depth] = hash::compute_sibling_path(
hist_tree_input.path,
hist_tree_input.leaf,
hist_tree_input.index
);
let new_historic_root: Field = hash::compute_merkle_root(
new_state_root,
hist_tree_input.index + 1,
new_historic_path
);
let hist_tree = structs::HistoricTree {
root: hist_tree_input.root,
new_root: new_historic_root,
leaf: hist_tree_input.leaf,
index: hist_tree_input.index,
old_path: hist_tree_input.path,
new_path: new_historic_path
};
let batch = structs::Batch {
hist_tree: hist_tree,
old_state_root: old_state_root,
new_state_root: new_state_root,
batch_oracle: batch_oracle,
utxo_roots: hist_tree_input.utxo_roots
};
let mut hash_validation: [u8; 832] = [0; 832];
for i in 0..32 {
hash_validation[i] = hash::field_to_u8(batch.new_state_root)[i];
hash_validation[i + 32] = hash::field_to_u8(batch.hist_tree.root)[i];
hash_validation[i + 64] = hash::field_to_u8(batch.hist_tree.new_root)[i];
hash_validation[i + 96] = hash::field_to_u8(accumulator)[i];
hash_validation[i + 128] = hash::field_to_u8(tx_verifier.key_hash)[i];
hash_validation[i + 160] = hash::field_to_u8(recursion_verifier.key_hash)[i];
for j in 0..20 {
hash_validation[i + 192 + (32 * j)] = hash::field_to_u8(batch.hist_tree.new_path[j])[i];
}
}
let pi_contract_hash: Field = hash::hash_to_field(std::hash::keccak256(hash_validation, hash_validation.len() as u32));
let publish_inputs = structs::PublishInputs {
pi_contract_hash: pi_contract_hash,
accumulator: accumulator,
batch: batch,
tx_verifier: tx_verifier,
recursion_verifier: recursion_verifier
};
publish_inputs
}
\ No newline at end of file
use crate::hash;
global utxo_depth = 4;
global batch_depth = 4;
global state_depth = 20;
global ZERO_VALUE = 0x016a430aa58685aba1311244a973a3bc358859da86784be51094368e8fb6f720;
// Levels of an empty pedersen Merkle tree with zero_leaf = sha256("Momiji") % Field Modulus
global zero_hashes: [Field; 20] = [
0x016a430aa58685aba1311244a973a3bc358859da86784be51094368e8fb6f720,
0x018d39625c19fa2cfbebdb940a66d7040bb0ef1b59ff6afd92a13a6b9b2d9865,
0x096c00ebc0c52478316b6b9fd16d0cd94c5f45bbe45bbfa8c606197c6119d41f,
0x2eaefd3bdd1bfbddd8fc5d972ded58617f752b3e88bd17b791e85e7b8eaacb47,
0x11d25ff6aa8a431fbce8e8d9a87a2d7986adf38e724fbe47f15752d0931f14d8,
0x01e8677aa02546bd7105a7a9fd31c4ef16b69d1bde90f36e97585d7cc31d50e4,
0x2520a755a532994c78f35b89938fbc864ec31ec4fc00363f83f9b12f04980c6a,
0x21a666842842d5323e51fdda10300c763d6b07e1b797ef3b0bd3690d667445bc,
0x1ce681d6f42b70668c369e7f786166e421dc840f0529bbd065d6b02d14ae0fe8,
0x1819b90a43ee28f652735708b2ef01428e21b84872bb3c7576de6e35d107c8ed,
0x063d7001431110a5949f5946a501fd28f64159f36ab4b80601ca305ac107b3db,
0x09aeef7a06f7daf368d797c2e686c7e9884f000de0bd9d8e73392378b0b1be38,
0x137121fd5d795eeecc61ec6a6de66abc589d241540139178cd5408b6ccb32a6e,
0x01a93f70a1b7482e0b32e76ce49a1e3c1fc2b5cd4789b6af749d78c42791c21a,
0x217bf2cc1f1b505a29b162a7889866af2728f5c6708560c9cc5e987b194c9c81,
0x1461dae57d7df7e580279637e5d94e0d734b908dc22aec5c52ed00187050a030,
0x295933dd65294cbf4b2c64d8a0daa6983d35d1f64d5adc2c44bd9d370086d7dc,
0x24650084f0db0fa4e8234fb251ad046b3ddcb7d6f59160b35cc4086e9e196b80,
0x15086d0394bd68847e3e36241cc7b3966f96efdd30a37e9e1e85027a6dacbed2,
0x1f87a17564f06581c1e1b2d716120990f898893ecc0e076363c56c5e3b56ef7b
];
// sha256("Momiji") % Field Modulus
global zero_root: Field = 0x06f93f503e77fcdcacfe622e66adc639b63e8c0083f5cab5d71d461aa4562c92;
struct Verifier {
key_hash: Field,
verification_key: [Field; 114],
proof: [Field; 109],
aggregation_object: [Field; 16]
}
struct VerifierTx {
key_hash: Field,
verification_key: [Field; 114],
proof: [Field; 93]
}
impl Verifier {
fn as_fields(self) -> [Field; 208] {
let mut verifier_as_fields: [Field; 208] = [0; 208];
verifier_as_fields[0] = self.key_hash;
for i in 0..114 {
verifier_as_fields[i + 1] = self.verification_key[i];
}
for j in 0..93 {
verifier_as_fields[j + 115] = self.proof[j];
}
verifier_as_fields
}
fn concatenate(self) -> [Field; 109] {
let mut proof_with_agg: [Field; 109] = [0; 109];
for i in 0..16 {
proof_with_agg[i] = self.aggregation_object[i];
}
for j in 0..93 {
proof_with_agg[j + 16] = self.proof[j];
}
proof_with_agg
}
}
struct UTXO {
owner: Field,
amount: Field,
asset_type: Field
}
struct UTXO_New {
secret: Field,
amount: Field,
asset_type: Field
}
impl UTXO_New {
fn owner(self) -> Field {
hash::hash([self.secret])
}
fn nullifier_hash(self) -> Field {
hash::hash([self.secret, self.secret])
}
fn to_commitment(self) -> Field {
hash::hash([self.owner(), self.amount, self.asset_type])
}
}
struct UTXO_Spendable {
secret: Field,
amount: Field,
asset_type: Field,
oracle: Field,
old_root_proof: Field,
merkle_proof: MerkleProof,
spend_in_same_batch: bool
}
impl UTXO_Spendable {
fn owner(self) -> Field {
hash::hash([self.secret])
}
fn nullifier_hash(self) -> Field {
hash::hash([self.secret, self.secret])
}
fn to_commitment(self) -> Field {
hash::hash([self.owner(), self.amount, self.asset_type])
}
}
struct PublicInputs {
current_root: Field,
utxo_root: Field,
deposit_amount: Field,
withdrawals: Field,
commitment_in: [Field; 16],
commitment_out: [Field; 16],
nullifier_hashes: [Field; 16],
contract_only_inputs: Field
}
impl PublicInputs {
fn as_fields(self) -> [Field; 53] {
let mut public_fields: [Field; 53] = [0; 53];
public_fields[0] = self.current_root;
public_fields[1] = self.utxo_root;
public_fields[2] = self.deposit_amount;
public_fields[3] = self.withdrawals;
for i in 0..16 {
public_fields[4 + i] = self.commitment_in[i];
public_fields[20 + i] = self.commitment_out[i];
public_fields[36 + i] = self.nullifier_hashes[i];
}
public_fields[50] = self.contract_only_inputs;
public_fields
}
fn as_hash(self) -> Field {
hash::hash_tx(self)
}
fn as_keccak(self) -> Field {
hash::keccak_tx(self)
}
fn as_u8(self) -> [u8; 1696] {
hash::tx_to_u8(self.as_fields())
}
}
struct PrivateInputs {
oracle: [Field; 16],
old_root_proof: [Field; 16],
secrets: [Field; 16],
utxo_in: [UTXO; 16],
merkle_proofs: [MerkleProof; 16],
utxo_out: [UTXO; 16],
}
struct TransactionInputs {
public_inputs_hash: Field,
public_inputs: PublicInputs,
private_inputs: PrivateInputs
}
struct RecursionInputs {
accumulator: Field,
tx_verifier: VerifierTx,
recursion_verifier: Verifier,
previous_accumulator: Field,
tx: PublicInputs
}
struct PublishInputs {
pi_contract_hash: Field,
accumulator: Field,
batch: Batch,
tx_verifier: VerifierTx,
recursion_verifier: Verifier
}
struct Batch {
hist_tree: HistoricTree,
old_state_root: Field,
new_state_root: Field,
batch_oracle: Field,
utxo_roots: [Field; 16]
}
struct EncryptedUTXO {
secret: Field,
amount: Field,
data: Field
}
struct ContractOnlyInputs {
timestamp: Field,
deadline: Field,
signature_hash: Field,
price_limit: Field,
recipients: [Field; 16],
swap_amounts: [Field; 16],
uids: [Field; 16],
encrypted_utxo: [EncryptedUTXO; 16]
}
impl ContractOnlyInputs {
fn as_keccak_without_deposit(self) -> Field {
hash::keccak_contract_only_inputs_without_deposit(self)
}
fn as_keccak(self) -> Field {
hash::keccak_contract_only_inputs(self)
}
}
struct MerkleProof {
path_utxo: [Field; utxo_depth],
path_tx: [Field; batch_depth],
path_historic: [Field; state_depth],
index_utxo: Field,
index_tx: Field,
index_historic: Field
}
struct HistoricTree {
root: Field,
new_root: Field,
leaf: Field,
index: Field,
old_path: [Field; state_depth],
new_path: [Field; state_depth],
}
struct HistoricTreeInput {
root: Field,
leaf: Field,
index: Field,
path: [Field; state_depth],
utxo_roots: [Field; 16],
}
\ No newline at end of file
[package]
name = "publish"
type = "bin"
authors = [""]
compiler_version = ">=0.19.2"
[dependencies]
helpers = { path = "../helpers" }
\ No newline at end of file
// Verification Key Hash: 066e0b06b13ec4c45dbeb0b625dbfab874ecdc411cdb43bae4346e880b09e16c
// SPDX-License-Identifier: Apache-2.0
// Copyright 2022 Aztec
pragma solidity >=0.8.4;
library UltraVerificationKey {
function verificationKeyHash() internal pure returns(bytes32) {
return 0x066e0b06b13ec4c45dbeb0b625dbfab874ecdc411cdb43bae4346e880b09e16c;
}
function loadVerificationKey(uint256 _vk, uint256 _omegaInverseLoc) internal pure {
assembly {
mstore(add(_vk, 0x00), 0x0000000000000000000000000000000000000000000000000000000000080000) // vk.circuit_size
mstore(add(_vk, 0x20), 0x0000000000000000000000000000000000000000000000000000000000000011) // vk.num_inputs
mstore(add(_vk, 0x40), 0x2260e724844bca5251829353968e4915305258418357473a5c1d597f613f6cbd) // vk.work_root
mstore(add(_vk, 0x60), 0x3064486657634403844b0eac78ca882cfd284341fcb0615a15cfcd17b14d8201) // vk.domain_inverse
mstore(add(_vk, 0x80), 0x174c5c19a31444028bb6368ff05acd095502e7d022af18c746a364dab4c7346e) // vk.Q1.x
mstore(add(_vk, 0xa0), 0x2e6d6b74f09bdcf37e2be6f0a05552f34840f8693d1c73edce05639d0698ea36) // vk.Q1.y
mstore(add(_vk, 0xc0), 0x243678b1fa4062f90887453567ef78c256421a5907513427b5c52aa3cb1e6a17) // vk.Q2.x
mstore(add(_vk, 0xe0), 0x10425e273dc8dd43dbb983a295f7f990b9475fca570f56f1d7f972166e5bf0d8) // vk.Q2.y
mstore(add(_vk, 0x100), 0x23b6836088c547e90ddfdb8146a5f7b3608aad75f4d0d5a050f119e6eea92c22) // vk.Q3.x
mstore(add(_vk, 0x120), 0x0cafbde5fed53b2d75343797c5718363f54c3b391730f7b49a8ae09cd79b8b20) // vk.Q3.y
mstore(add(_vk, 0x140), 0x06acf52ef347e83eae253c24631c08d66d76669d97c0cce87b0320363e0188bd) // vk.Q4.x
mstore(add(_vk, 0x160), 0x1f8257e9bfac704d9b0171c74f4f35ec94c608ae5ff55962db5bc4fd9e1aba89) // vk.Q4.y
mstore(add(_vk, 0x180), 0x11931c54c38fba5131f7a317e782a12a8e3e3726dc2eaa490049ce2ca8ca1cbb) // vk.Q_M.x
mstore(add(_vk, 0x1a0), 0x0565f2552174d90b7b0e9645969affd0015d483dffec9d93ede1b63be6dc3c16) // vk.Q_M.y
mstore(add(_vk, 0x1c0), 0x0bca92d0bac2e3d19be0265337ec7f4574f8195e313af04d651f342bb3159fb8) // vk.Q_C.x
mstore(add(_vk, 0x1e0), 0x1ecb4bbed3bfe4b4e91afe37b7871694eda583ff1d1111be20aa9eb8edf50a77) // vk.Q_C.y
mstore(add(_vk, 0x200), 0x0c1be01afc0073d9a1f1e23184a1d005a89d087265ea3a85f523ecd062212260) // vk.Q_ARITHMETIC.x
mstore(add(_vk, 0x220), 0x25a9e732ab54e2ebe8d031c166997a26a5d5dc143dc246b4b046b170406da4f1) // vk.Q_ARITHMETIC.y
mstore(add(_vk, 0x240), 0x2498b2e14394f700cac156f42ef63ba194ed99764e03f2e433cb0be94d0fc89f) // vk.QSORT.x
mstore(add(_vk, 0x260), 0x206103c2ff58564c9a4746efd3fff7637a46052eb129af96844c2d850d11d8f3) // vk.QSORT.y
mstore(add(_vk, 0x280), 0x212dfe13df5b9cfb846570ecf8269ed3648c24827e7dceafdeca6235e1dd5e38) // vk.Q_ELLIPTIC.x
mstore(add(_vk, 0x2a0), 0x284d15304c372e9c33d76628c66bd6c48ef526d08146e9b401dc7d1e373ccd3a) // vk.Q_ELLIPTIC.y
mstore(add(_vk, 0x2c0), 0x126a3a8e4c678d476c3c864cde50f9776d880443976f62a438b9db897b433ffa) // vk.Q_AUX.x
mstore(add(_vk, 0x2e0), 0x2866186a79faf6dde910250342628d6a7a1c6d32f88b1991b276852874bd73dc) // vk.Q_AUX.y
mstore(add(_vk, 0x300), 0x2bd3d50520f90ab39921a0f2ce6c8f5d5cc8a989720e939dbb4a8951d878f579) // vk.SIGMA1.x
mstore(add(_vk, 0x320), 0x0b5652d8de0be47d4a6496260931f15984d6cc0eb21e61c15991554eee8bfe91) // vk.SIGMA1.y
mstore(add(_vk, 0x340), 0x135cdf64147af6820b82eff62e1e62efb848f20c81dffc2ed36cc4d3ef9d6ee3) // vk.SIGMA2.x
mstore(add(_vk, 0x360), 0x16ce92a14e25ca8f353781ad6bf4c105139a2029c682b0e62606b721e9d8c848) // vk.SIGMA2.y
mstore(add(_vk, 0x380), 0x0c9177240c1a64d39bbdc9113347b71c8ad5899a9a617b70450e43ca0f08c0b9) // vk.SIGMA3.x
mstore(add(_vk, 0x3a0), 0x26486bfd9b1989300948793829dda7ebd3c783b85ef797363b15bdf9932d624f) // vk.SIGMA3.y
mstore(add(_vk, 0x3c0), 0x0933b476f487753e6b6a6c07579f099b3bd03e1d4fad080cdf864233e378e6f7) // vk.SIGMA4.x
mstore(add(_vk, 0x3e0), 0x19fa964521f482600ad8f48f2d74204aa35665cefec49c73255bcd7bb9cf1d2e) // vk.SIGMA4.y
mstore(add(_vk, 0x400), 0x1cadeb85ec1d913dbbebd04a2e9c216fffb782a908252de688cb3f87a9960239) // vk.TABLE1.x
mstore(add(_vk, 0x420), 0x140ce35846522600428e89cedfc30ef4f906e759e4b262f982f41b11e232bef0) // vk.TABLE1.y
mstore(add(_vk, 0x440), 0x186c47255bd1cac8572abe17f302a4a778dc042d05b5ad10ec72ccf87db72cbd) // vk.TABLE2.x
mstore(add(_vk, 0x460), 0x2696b8ea9abe2e21817826ff8ffdd70455306100f722aee5047d7b61d23be470) // vk.TABLE2.y
mstore(add(_vk, 0x480), 0x203c78601f28e6739d2031447af9f5015f00e4a92298c8e0af4906929ce4aa23) // vk.TABLE3.x
mstore(add(_vk, 0x4a0), 0x0b82d8af3657b8dacf80c03e267415e27c1b22c5b110041433f5f7a0077d6163) // vk.TABLE3.y
mstore(add(_vk, 0x4c0), 0x1ca5d2fc76408b3d82cd070612b1f00b2910e92b606943f0a66d4064ac05d6f3) // vk.TABLE4.x
mstore(add(_vk, 0x4e0), 0x12889680e44151655a6fb88176f0eb0e09e60039ec5cbc978bcb081ca4d41c1e) // vk.TABLE4.y
mstore(add(_vk, 0x500), 0x269c3c1e6be15df4cd6e18678103fb6a22fc4e79890d0e7d18259353640bf580) // vk.TABLE_TYPE.x
mstore(add(_vk, 0x520), 0x073ef16b8836c3c34ac6e02937c25aaf47728e8aaa50eea7a2a7e2d87b2a051c) // vk.TABLE_TYPE.y
mstore(add(_vk, 0x540), 0x0642a0979a2c84f7f2c1ae9c5285e8b54bc1a57e51ec50588ec003e5ccaa1ef0) // vk.ID1.x
mstore(add(_vk, 0x560), 0x10759f71ebeb64070c9bde2e55126711646725544ef4273f9a391b9b9b0af13d) // vk.ID1.y
mstore(add(_vk, 0x580), 0x05107532383658086d131205993b32158da27ddcf4a41708cf50e4e6ca986205) // vk.ID2.x
mstore(add(_vk, 0x5a0), 0x1c67ba6b417eddf6928c56e1fece6ba9c73c4579c446ff0bd86e794121b4957c) // vk.ID2.y
mstore(add(_vk, 0x5c0), 0x030c58a082fe2a8a256a3c7596ca585a024396e2377db7e854bf7f14514759b8) // vk.ID3.x
mstore(add(_vk, 0x5e0), 0x1606bd0cae6fbb354de28025f6deb12a003c43b7dafa272c94b35c752ceaa696) // vk.ID3.y
mstore(add(_vk, 0x600), 0x1e1a0bd62a0746fb76e5d6727d5db536662c58d163cf29375fff76b669856e5f) // vk.ID4.x
mstore(add(_vk, 0x620), 0x0f786242d4fd388fd0e83bc4074cef1a36162d3403af146af654e72acb72e4c6) // vk.ID4.y
mstore(add(_vk, 0x640), 0x01) // vk.contains_recursive_proof
mstore(add(_vk, 0x660), 1) // vk.recursive_proof_public_input_indices
mstore(add(_vk, 0x680), 0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1) // vk.g2_x.X.c1
mstore(add(_vk, 0x6a0), 0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0) // vk.g2_x.X.c0
mstore(add(_vk, 0x6c0), 0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4) // vk.g2_x.Y.c1
mstore(add(_vk, 0x6e0), 0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55) // vk.g2_x.Y.c0
mstore(_omegaInverseLoc, 0x06e402c0a314fb67a15cf806664ae1b722dbc0efe66e6c81d98f9924ca535321) // vk.work_root_inverse
}
}
}
/**
* @title Ultra Plonk proof verification contract
* @dev Top level Plonk proof verification contract, which allows Plonk proof to be verified
*/
abstract contract BaseUltraVerifier {
// VERIFICATION KEY MEMORY LOCATIONS
uint256 internal constant N_LOC = 0x380;
uint256 internal constant NUM_INPUTS_LOC = 0x3a0;
uint256 internal constant OMEGA_LOC = 0x3c0;
uint256 internal constant DOMAIN_INVERSE_LOC = 0x3e0;
uint256 internal constant Q1_X_LOC = 0x400;
uint256 internal constant Q1_Y_LOC = 0x420;
uint256 internal constant Q2_X_LOC = 0x440;
uint256 internal constant Q2_Y_LOC = 0x460;
uint256 internal constant Q3_X_LOC = 0x480;
uint256 internal constant Q3_Y_LOC = 0x4a0;
uint256 internal constant Q4_X_LOC = 0x4c0;
uint256 internal constant Q4_Y_LOC = 0x4e0;
uint256 internal constant QM_X_LOC = 0x500;
uint256 internal constant QM_Y_LOC = 0x520;
uint256 internal constant QC_X_LOC = 0x540;
uint256 internal constant QC_Y_LOC = 0x560;
uint256 internal constant QARITH_X_LOC = 0x580;
uint256 internal constant QARITH_Y_LOC = 0x5a0;
uint256 internal constant QSORT_X_LOC = 0x5c0;
uint256 internal constant QSORT_Y_LOC = 0x5e0;
uint256 internal constant QELLIPTIC_X_LOC = 0x600;
uint256 internal constant QELLIPTIC_Y_LOC = 0x620;
uint256 internal constant QAUX_X_LOC = 0x640;
uint256 internal constant QAUX_Y_LOC = 0x660;
uint256 internal constant SIGMA1_X_LOC = 0x680;
uint256 internal constant SIGMA1_Y_LOC = 0x6a0;
uint256 internal constant SIGMA2_X_LOC = 0x6c0;
uint256 internal constant SIGMA2_Y_LOC = 0x6e0;
uint256 internal constant SIGMA3_X_LOC = 0x700;
uint256 internal constant SIGMA3_Y_LOC = 0x720;
uint256 internal constant SIGMA4_X_LOC = 0x740;
uint256 internal constant SIGMA4_Y_LOC = 0x760;
uint256 internal constant TABLE1_X_LOC = 0x780;
uint256 internal constant TABLE1_Y_LOC = 0x7a0;
uint256 internal constant TABLE2_X_LOC = 0x7c0;
uint256 internal constant TABLE2_Y_LOC = 0x7e0;
uint256 internal constant TABLE3_X_LOC = 0x800;
uint256 internal constant TABLE3_Y_LOC = 0x820;
uint256 internal constant TABLE4_X_LOC = 0x840;
uint256 internal constant TABLE4_Y_LOC = 0x860;
uint256 internal constant TABLE_TYPE_X_LOC = 0x880;
uint256 internal constant TABLE_TYPE_Y_LOC = 0x8a0;
uint256 internal constant ID1_X_LOC = 0x8c0;
uint256 internal constant ID1_Y_LOC = 0x8e0;
uint256 internal constant ID2_X_LOC = 0x900;
uint256 internal constant ID2_Y_LOC = 0x920;
uint256 internal constant ID3_X_LOC = 0x940;
uint256 internal constant ID3_Y_LOC = 0x960;
uint256 internal constant ID4_X_LOC = 0x980;
uint256 internal constant ID4_Y_LOC = 0x9a0;
uint256 internal constant CONTAINS_RECURSIVE_PROOF_LOC = 0x9c0;
uint256 internal constant RECURSIVE_PROOF_PUBLIC_INPUT_INDICES_LOC = 0x9e0;
uint256 internal constant G2X_X0_LOC = 0xa00;
uint256 internal constant G2X_X1_LOC = 0xa20;
uint256 internal constant G2X_Y0_LOC = 0xa40;
uint256 internal constant G2X_Y1_LOC = 0xa60;
// ### PROOF DATA MEMORY LOCATIONS
uint256 internal constant W1_X_LOC = 0x1200;
uint256 internal constant W1_Y_LOC = 0x1220;
uint256 internal constant W2_X_LOC = 0x1240;
uint256 internal constant W2_Y_LOC = 0x1260;
uint256 internal constant W3_X_LOC = 0x1280;
uint256 internal constant W3_Y_LOC = 0x12a0;
uint256 internal constant W4_X_LOC = 0x12c0;
uint256 internal constant W4_Y_LOC = 0x12e0;
uint256 internal constant S_X_LOC = 0x1300;
uint256 internal constant S_Y_LOC = 0x1320;
uint256 internal constant Z_X_LOC = 0x1340;
uint256 internal constant Z_Y_LOC = 0x1360;
uint256 internal constant Z_LOOKUP_X_LOC = 0x1380;
uint256 internal constant Z_LOOKUP_Y_LOC = 0x13a0;
uint256 internal constant T1_X_LOC = 0x13c0;
uint256 internal constant T1_Y_LOC = 0x13e0;
uint256 internal constant T2_X_LOC = 0x1400;
uint256 internal constant T2_Y_LOC = 0x1420;
uint256 internal constant T3_X_LOC = 0x1440;
uint256 internal constant T3_Y_LOC = 0x1460;
uint256 internal constant T4_X_LOC = 0x1480;
uint256 internal constant T4_Y_LOC = 0x14a0;
uint256 internal constant W1_EVAL_LOC = 0x1600;
uint256 internal constant W2_EVAL_LOC = 0x1620;
uint256 internal constant W3_EVAL_LOC = 0x1640;
uint256 internal constant W4_EVAL_LOC = 0x1660;
uint256 internal constant S_EVAL_LOC = 0x1680;
uint256 internal constant Z_EVAL_LOC = 0x16a0;
uint256 internal constant Z_LOOKUP_EVAL_LOC = 0x16c0;
uint256 internal constant Q1_EVAL_LOC = 0x16e0;
uint256 internal constant Q2_EVAL_LOC = 0x1700;
uint256 internal constant Q3_EVAL_LOC = 0x1720;
uint256 internal constant Q4_EVAL_LOC = 0x1740;
uint256 internal constant QM_EVAL_LOC = 0x1760;
uint256 internal constant QC_EVAL_LOC = 0x1780;
uint256 internal constant QARITH_EVAL_LOC = 0x17a0;
uint256 internal constant QSORT_EVAL_LOC = 0x17c0;
uint256 internal constant QELLIPTIC_EVAL_LOC = 0x17e0;
uint256 internal constant QAUX_EVAL_LOC = 0x1800;
uint256 internal constant TABLE1_EVAL_LOC = 0x1840;
uint256 internal constant TABLE2_EVAL_LOC = 0x1860;
uint256 internal constant TABLE3_EVAL_LOC = 0x1880;
uint256 internal constant TABLE4_EVAL_LOC = 0x18a0;
uint256 internal constant TABLE_TYPE_EVAL_LOC = 0x18c0;
uint256 internal constant ID1_EVAL_LOC = 0x18e0;
uint256 internal constant ID2_EVAL_LOC = 0x1900;
uint256 internal constant ID3_EVAL_LOC = 0x1920;
uint256 internal constant ID4_EVAL_LOC = 0x1940;
uint256 internal constant SIGMA1_EVAL_LOC = 0x1960;
uint256 internal constant SIGMA2_EVAL_LOC = 0x1980;
uint256 internal constant SIGMA3_EVAL_LOC = 0x19a0;
uint256 internal constant SIGMA4_EVAL_LOC = 0x19c0;
uint256 internal constant W1_OMEGA_EVAL_LOC = 0x19e0;
uint256 internal constant W2_OMEGA_EVAL_LOC = 0x2000;
uint256 internal constant W3_OMEGA_EVAL_LOC = 0x2020;
uint256 internal constant W4_OMEGA_EVAL_LOC = 0x2040;
uint256 internal constant S_OMEGA_EVAL_LOC = 0x2060;
uint256 internal constant Z_OMEGA_EVAL_LOC = 0x2080;
uint256 internal constant Z_LOOKUP_OMEGA_EVAL_LOC = 0x20a0;
uint256 internal constant TABLE1_OMEGA_EVAL_LOC = 0x20c0;
uint256 internal constant TABLE2_OMEGA_EVAL_LOC = 0x20e0;
uint256 internal constant TABLE3_OMEGA_EVAL_LOC = 0x2100;
uint256 internal constant TABLE4_OMEGA_EVAL_LOC = 0x2120;
uint256 internal constant PI_Z_X_LOC = 0x2300;
uint256 internal constant PI_Z_Y_LOC = 0x2320;
uint256 internal constant PI_Z_OMEGA_X_LOC = 0x2340;
uint256 internal constant PI_Z_OMEGA_Y_LOC = 0x2360;
// Used for elliptic widget. These are alias names for wire + shifted wire evaluations
uint256 internal constant X1_EVAL_LOC = W2_EVAL_LOC;
uint256 internal constant X2_EVAL_LOC = W1_OMEGA_EVAL_LOC;
uint256 internal constant X3_EVAL_LOC = W2_OMEGA_EVAL_LOC;
uint256 internal constant Y1_EVAL_LOC = W3_EVAL_LOC;
uint256 internal constant Y2_EVAL_LOC = W4_OMEGA_EVAL_LOC;
uint256 internal constant Y3_EVAL_LOC = W3_OMEGA_EVAL_LOC;
uint256 internal constant QBETA_LOC = Q3_EVAL_LOC;
uint256 internal constant QBETA_SQR_LOC = Q4_EVAL_LOC;
uint256 internal constant QSIGN_LOC = Q1_EVAL_LOC;
// ### CHALLENGES MEMORY OFFSETS
uint256 internal constant C_BETA_LOC = 0x2600;
uint256 internal constant C_GAMMA_LOC = 0x2620;
uint256 internal constant C_ALPHA_LOC = 0x2640;
uint256 internal constant C_ETA_LOC = 0x2660;
uint256 internal constant C_ETA_SQR_LOC = 0x2680;
uint256 internal constant C_ETA_CUBE_LOC = 0x26a0;
uint256 internal constant C_ZETA_LOC = 0x26c0;
uint256 internal constant C_CURRENT_LOC = 0x26e0;
uint256 internal constant C_V0_LOC = 0x2700;
uint256 internal constant C_V1_LOC = 0x2720;
uint256 internal constant C_V2_LOC = 0x2740;
uint256 internal constant C_V3_LOC = 0x2760;
uint256 internal constant C_V4_LOC = 0x2780;
uint256 internal constant C_V5_LOC = 0x27a0;
uint256 internal constant C_V6_LOC = 0x27c0;
uint256 internal constant C_V7_LOC = 0x27e0;
uint256 internal constant C_V8_LOC = 0x2800;
uint256 internal constant C_V9_LOC = 0x2820;
uint256 internal constant C_V10_LOC = 0x2840;
uint256 internal constant C_V11_LOC = 0x2860;
uint256 internal constant C_V12_LOC = 0x2880;
uint256 internal constant C_V13_LOC = 0x28a0;
uint256 internal constant C_V14_LOC = 0x28c0;
uint256 internal constant C_V15_LOC = 0x28e0;
uint256 internal constant C_V16_LOC = 0x2900;
uint256 internal constant C_V17_LOC = 0x2920;
uint256 internal constant C_V18_LOC = 0x2940;
uint256 internal constant C_V19_LOC = 0x2960;
uint256 internal constant C_V20_LOC = 0x2980;
uint256 internal constant C_V21_LOC = 0x29a0;
uint256 internal constant C_V22_LOC = 0x29c0;
uint256 internal constant C_V23_LOC = 0x29e0;
uint256 internal constant C_V24_LOC = 0x2a00;
uint256 internal constant C_V25_LOC = 0x2a20;
uint256 internal constant C_V26_LOC = 0x2a40;
uint256 internal constant C_V27_LOC = 0x2a60;
uint256 internal constant C_V28_LOC = 0x2a80;
uint256 internal constant C_V29_LOC = 0x2aa0;
uint256 internal constant C_V30_LOC = 0x2ac0;
uint256 internal constant C_U_LOC = 0x2b00;
// ### LOCAL VARIABLES MEMORY OFFSETS
uint256 internal constant DELTA_NUMERATOR_LOC = 0x3000;
uint256 internal constant DELTA_DENOMINATOR_LOC = 0x3020;
uint256 internal constant ZETA_POW_N_LOC = 0x3040;
uint256 internal constant PUBLIC_INPUT_DELTA_LOC = 0x3060;
uint256 internal constant ZERO_POLY_LOC = 0x3080;
uint256 internal constant L_START_LOC = 0x30a0;
uint256 internal constant L_END_LOC = 0x30c0;
uint256 internal constant R_ZERO_EVAL_LOC = 0x30e0;
uint256 internal constant PLOOKUP_DELTA_NUMERATOR_LOC = 0x3100;
uint256 internal constant PLOOKUP_DELTA_DENOMINATOR_LOC = 0x3120;
uint256 internal constant PLOOKUP_DELTA_LOC = 0x3140;
uint256 internal constant ACCUMULATOR_X_LOC = 0x3160;
uint256 internal constant ACCUMULATOR_Y_LOC = 0x3180;
uint256 internal constant ACCUMULATOR2_X_LOC = 0x31a0;
uint256 internal constant ACCUMULATOR2_Y_LOC = 0x31c0;
uint256 internal constant PAIRING_LHS_X_LOC = 0x31e0;
uint256 internal constant PAIRING_LHS_Y_LOC = 0x3200;
uint256 internal constant PAIRING_RHS_X_LOC = 0x3220;
uint256 internal constant PAIRING_RHS_Y_LOC = 0x3240;
// misc stuff
uint256 internal constant OMEGA_INVERSE_LOC = 0x3300;
uint256 internal constant C_ALPHA_SQR_LOC = 0x3320;
uint256 internal constant C_ALPHA_CUBE_LOC = 0x3340;
uint256 internal constant C_ALPHA_QUAD_LOC = 0x3360;
uint256 internal constant C_ALPHA_BASE_LOC = 0x3380;
// ### RECURSION VARIABLE MEMORY LOCATIONS
uint256 internal constant RECURSIVE_P1_X_LOC = 0x3400;
uint256 internal constant RECURSIVE_P1_Y_LOC = 0x3420;
uint256 internal constant RECURSIVE_P2_X_LOC = 0x3440;
uint256 internal constant RECURSIVE_P2_Y_LOC = 0x3460;
uint256 internal constant PUBLIC_INPUTS_HASH_LOCATION = 0x3480;
// sub-identity storage
uint256 internal constant PERMUTATION_IDENTITY = 0x3500;
uint256 internal constant PLOOKUP_IDENTITY = 0x3520;
uint256 internal constant ARITHMETIC_IDENTITY = 0x3540;
uint256 internal constant SORT_IDENTITY = 0x3560;
uint256 internal constant ELLIPTIC_IDENTITY = 0x3580;
uint256 internal constant AUX_IDENTITY = 0x35a0;
uint256 internal constant AUX_NON_NATIVE_FIELD_EVALUATION = 0x35c0;
uint256 internal constant AUX_LIMB_ACCUMULATOR_EVALUATION = 0x35e0;
uint256 internal constant AUX_RAM_CONSISTENCY_EVALUATION = 0x3600;
uint256 internal constant AUX_ROM_CONSISTENCY_EVALUATION = 0x3620;
uint256 internal constant AUX_MEMORY_EVALUATION = 0x3640;
uint256 internal constant QUOTIENT_EVAL_LOC = 0x3660;
uint256 internal constant ZERO_POLY_INVERSE_LOC = 0x3680;
// when hashing public inputs we use memory at NU_CHALLENGE_INPUT_LOC_A, as the hash input size is unknown at compile time
uint256 internal constant NU_CHALLENGE_INPUT_LOC_A = 0x36a0;
uint256 internal constant NU_CHALLENGE_INPUT_LOC_B = 0x36c0;
uint256 internal constant NU_CHALLENGE_INPUT_LOC_C = 0x36e0;
bytes4 internal constant INVALID_VERIFICATION_KEY_SELECTOR = 0x7e5769bf;
bytes4 internal constant POINT_NOT_ON_CURVE_SELECTOR = 0xa3dad654;
bytes4 internal constant PUBLIC_INPUT_INVALID_BN128_G1_POINT_SELECTOR = 0xeba9f4a6;
bytes4 internal constant PUBLIC_INPUT_GE_P_SELECTOR = 0x374a972f;
bytes4 internal constant MOD_EXP_FAILURE_SELECTOR = 0xf894a7bc;
bytes4 internal constant PAIRING_PREAMBLE_FAILED_SELECTOR = 0x01882d81;
bytes4 internal constant OPENING_COMMITMENT_FAILED_SELECTOR = 0x4e719763;
bytes4 internal constant PAIRING_FAILED_SELECTOR = 0xd71fd263;
uint256 internal constant ETA_INPUT_LENGTH = 0xc0; // W1, W2, W3 = 6 * 0x20 bytes
// We need to hash 41 field elements when generating the NU challenge
// w1, w2, w3, w4, s, z, z_lookup, q1, q2, q3, q4, qm, qc, qarith (14)
// qsort, qelliptic, qaux, sigma1, sigma2, sigma, sigma4, (7)
// table1, table2, table3, table4, tabletype, id1, id2, id3, id4, (9)
// w1_omega, w2_omega, w3_omega, w4_omega, s_omega, z_omega, z_lookup_omega, (7)
// table1_omega, table2_omega, table3_omega, table4_omega (4)
uint256 internal constant NU_INPUT_LENGTH = 0x520; // 0x520 = 41 * 0x20
// There are ELEVEN G1 group elements added into the transcript in the `beta` round, that we need to skip over
// W1, W2, W3, W4, S, Z, Z_LOOKUP, T1, T2, T3, T4
uint256 internal constant NU_CALLDATA_SKIP_LENGTH = 0x2c0; // 11 * 0x40 = 0x2c0
uint256 internal constant NEGATIVE_INVERSE_OF_2_MODULO_P =
0x183227397098d014dc2822db40c0ac2e9419f4243cdcb848a1f0fac9f8000000;
uint256 internal constant LIMB_SIZE = 0x100000000000000000; // 2<<68
uint256 internal constant SUBLIMB_SHIFT = 0x4000; // 2<<14
// y^2 = x^3 + ax + b
// for Grumpkin, a = 0 and b = -17. We use b in a custom gate relation that evaluates elliptic curve arithmetic
uint256 internal constant GRUMPKIN_CURVE_B_PARAMETER_NEGATED = 17;
error INVALID_VERIFICATION_KEY();
error POINT_NOT_ON_CURVE();
error PUBLIC_INPUT_COUNT_INVALID(uint256 expected, uint256 actual);
error PUBLIC_INPUT_INVALID_BN128_G1_POINT();
error PUBLIC_INPUT_GE_P();
error MOD_EXP_FAILURE();
error PAIRING_PREAMBLE_FAILED();
error OPENING_COMMITMENT_FAILED();
error PAIRING_FAILED();
function getVerificationKeyHash() public pure virtual returns (bytes32);
/**
* @dev We assume that the verification key loaded by this function is constant as we only verify it on deployment
*/
function loadVerificationKey(uint256 _vk, uint256 _omegaInverseLoc) internal pure virtual;
constructor() {
loadVerificationKey(N_LOC, OMEGA_INVERSE_LOC);
// We verify that all of the EC points in the verification key lie on the bn128 curve.
assembly {
let q := 21888242871839275222246405745257275088696311157297823662689037894645226208583 // EC group order
let success := 1
// VALIDATE Q1
{
let x := mload(Q1_X_LOC)
let y := mload(Q1_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE Q2
{
let x := mload(Q2_X_LOC)
let y := mload(Q2_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE Q3
{
let x := mload(Q3_X_LOC)
let y := mload(Q3_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE Q4
{
let x := mload(Q4_X_LOC)
let y := mload(Q4_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
mstore(0x00, x)
mstore(0x20, y)
}
// VALIDATE QM
{
let x := mload(QM_X_LOC)
let y := mload(QM_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE QC
{
let x := mload(QC_X_LOC)
let y := mload(QC_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE QARITH
{
let x := mload(QARITH_X_LOC)
let y := mload(QARITH_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE QSORT
{
let x := mload(QSORT_X_LOC)
let y := mload(QSORT_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE QELLIPTIC
{
let x := mload(QELLIPTIC_X_LOC)
let y := mload(QELLIPTIC_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE QAUX
{
let x := mload(QAUX_X_LOC)
let y := mload(QAUX_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE SIGMA1
{
let x := mload(SIGMA1_X_LOC)
let y := mload(SIGMA1_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE SIGMA2
{
let x := mload(SIGMA2_X_LOC)
let y := mload(SIGMA2_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE SIGMA3
{
let x := mload(SIGMA3_X_LOC)
let y := mload(SIGMA3_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE SIGMA4
{
let x := mload(SIGMA4_X_LOC)
let y := mload(SIGMA4_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE TABLE1
{
let x := mload(TABLE1_X_LOC)
let y := mload(TABLE1_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE TABLE2
{
let x := mload(TABLE2_X_LOC)
let y := mload(TABLE2_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE TABLE3
{
let x := mload(TABLE3_X_LOC)
let y := mload(TABLE3_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE TABLE4
{
let x := mload(TABLE4_X_LOC)
let y := mload(TABLE4_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE TABLE_TYPE
{
let x := mload(TABLE_TYPE_X_LOC)
let y := mload(TABLE_TYPE_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE ID1
{
let x := mload(ID1_X_LOC)
let y := mload(ID1_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE ID2
{
let x := mload(ID2_X_LOC)
let y := mload(ID2_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE ID3
{
let x := mload(ID3_X_LOC)
let y := mload(ID3_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
// VALIDATE ID4
{
let x := mload(ID4_X_LOC)
let y := mload(ID4_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
success := and(success, eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q)))
}
if iszero(success) {
mstore(0x0, INVALID_VERIFICATION_KEY_SELECTOR)
revert(0x00, 0x04)
}
}
}
/**
* @notice Verify a Ultra Plonk proof
* @param _proof - The serialized proof
* @param _publicInputs - An array of the public inputs
* @return True if proof is valid, reverts otherwise
*/
function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external view returns (bool) {
loadVerificationKey(N_LOC, OMEGA_INVERSE_LOC);
uint256 requiredPublicInputCount;
assembly {
requiredPublicInputCount := mload(NUM_INPUTS_LOC)
}
if (requiredPublicInputCount != _publicInputs.length) {
revert PUBLIC_INPUT_COUNT_INVALID(requiredPublicInputCount, _publicInputs.length);
}
assembly {
let q := 21888242871839275222246405745257275088696311157297823662689037894645226208583 // EC group order
let p := 21888242871839275222246405745257275088548364400416034343698204186575808495617 // Prime field order
/**
* LOAD PROOF FROM CALLDATA
*/
{
let data_ptr := add(calldataload(0x04), 0x24)
mstore(W1_Y_LOC, mod(calldataload(data_ptr), q))
mstore(W1_X_LOC, mod(calldataload(add(data_ptr, 0x20)), q))
mstore(W2_Y_LOC, mod(calldataload(add(data_ptr, 0x40)), q))
mstore(W2_X_LOC, mod(calldataload(add(data_ptr, 0x60)), q))
mstore(W3_Y_LOC, mod(calldataload(add(data_ptr, 0x80)), q))
mstore(W3_X_LOC, mod(calldataload(add(data_ptr, 0xa0)), q))
mstore(W4_Y_LOC, mod(calldataload(add(data_ptr, 0xc0)), q))
mstore(W4_X_LOC, mod(calldataload(add(data_ptr, 0xe0)), q))
mstore(S_Y_LOC, mod(calldataload(add(data_ptr, 0x100)), q))
mstore(S_X_LOC, mod(calldataload(add(data_ptr, 0x120)), q))
mstore(Z_Y_LOC, mod(calldataload(add(data_ptr, 0x140)), q))
mstore(Z_X_LOC, mod(calldataload(add(data_ptr, 0x160)), q))
mstore(Z_LOOKUP_Y_LOC, mod(calldataload(add(data_ptr, 0x180)), q))
mstore(Z_LOOKUP_X_LOC, mod(calldataload(add(data_ptr, 0x1a0)), q))
mstore(T1_Y_LOC, mod(calldataload(add(data_ptr, 0x1c0)), q))
mstore(T1_X_LOC, mod(calldataload(add(data_ptr, 0x1e0)), q))
mstore(T2_Y_LOC, mod(calldataload(add(data_ptr, 0x200)), q))
mstore(T2_X_LOC, mod(calldataload(add(data_ptr, 0x220)), q))
mstore(T3_Y_LOC, mod(calldataload(add(data_ptr, 0x240)), q))
mstore(T3_X_LOC, mod(calldataload(add(data_ptr, 0x260)), q))
mstore(T4_Y_LOC, mod(calldataload(add(data_ptr, 0x280)), q))
mstore(T4_X_LOC, mod(calldataload(add(data_ptr, 0x2a0)), q))
mstore(W1_EVAL_LOC, mod(calldataload(add(data_ptr, 0x2c0)), p))
mstore(W2_EVAL_LOC, mod(calldataload(add(data_ptr, 0x2e0)), p))
mstore(W3_EVAL_LOC, mod(calldataload(add(data_ptr, 0x300)), p))
mstore(W4_EVAL_LOC, mod(calldataload(add(data_ptr, 0x320)), p))
mstore(S_EVAL_LOC, mod(calldataload(add(data_ptr, 0x340)), p))
mstore(Z_EVAL_LOC, mod(calldataload(add(data_ptr, 0x360)), p))
mstore(Z_LOOKUP_EVAL_LOC, mod(calldataload(add(data_ptr, 0x380)), p))
mstore(Q1_EVAL_LOC, mod(calldataload(add(data_ptr, 0x3a0)), p))
mstore(Q2_EVAL_LOC, mod(calldataload(add(data_ptr, 0x3c0)), p))
mstore(Q3_EVAL_LOC, mod(calldataload(add(data_ptr, 0x3e0)), p))
mstore(Q4_EVAL_LOC, mod(calldataload(add(data_ptr, 0x400)), p))
mstore(QM_EVAL_LOC, mod(calldataload(add(data_ptr, 0x420)), p))
mstore(QC_EVAL_LOC, mod(calldataload(add(data_ptr, 0x440)), p))
mstore(QARITH_EVAL_LOC, mod(calldataload(add(data_ptr, 0x460)), p))
mstore(QSORT_EVAL_LOC, mod(calldataload(add(data_ptr, 0x480)), p))
mstore(QELLIPTIC_EVAL_LOC, mod(calldataload(add(data_ptr, 0x4a0)), p))
mstore(QAUX_EVAL_LOC, mod(calldataload(add(data_ptr, 0x4c0)), p))
mstore(SIGMA1_EVAL_LOC, mod(calldataload(add(data_ptr, 0x4e0)), p))
mstore(SIGMA2_EVAL_LOC, mod(calldataload(add(data_ptr, 0x500)), p))
mstore(SIGMA3_EVAL_LOC, mod(calldataload(add(data_ptr, 0x520)), p))
mstore(SIGMA4_EVAL_LOC, mod(calldataload(add(data_ptr, 0x540)), p))
mstore(TABLE1_EVAL_LOC, mod(calldataload(add(data_ptr, 0x560)), p))
mstore(TABLE2_EVAL_LOC, mod(calldataload(add(data_ptr, 0x580)), p))
mstore(TABLE3_EVAL_LOC, mod(calldataload(add(data_ptr, 0x5a0)), p))
mstore(TABLE4_EVAL_LOC, mod(calldataload(add(data_ptr, 0x5c0)), p))
mstore(TABLE_TYPE_EVAL_LOC, mod(calldataload(add(data_ptr, 0x5e0)), p))
mstore(ID1_EVAL_LOC, mod(calldataload(add(data_ptr, 0x600)), p))
mstore(ID2_EVAL_LOC, mod(calldataload(add(data_ptr, 0x620)), p))
mstore(ID3_EVAL_LOC, mod(calldataload(add(data_ptr, 0x640)), p))
mstore(ID4_EVAL_LOC, mod(calldataload(add(data_ptr, 0x660)), p))
mstore(W1_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x680)), p))
mstore(W2_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x6a0)), p))
mstore(W3_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x6c0)), p))
mstore(W4_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x6e0)), p))
mstore(S_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x700)), p))
mstore(Z_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x720)), p))
mstore(Z_LOOKUP_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x740)), p))
mstore(TABLE1_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x760)), p))
mstore(TABLE2_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x780)), p))
mstore(TABLE3_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x7a0)), p))
mstore(TABLE4_OMEGA_EVAL_LOC, mod(calldataload(add(data_ptr, 0x7c0)), p))
mstore(PI_Z_Y_LOC, mod(calldataload(add(data_ptr, 0x7e0)), q))
mstore(PI_Z_X_LOC, mod(calldataload(add(data_ptr, 0x800)), q))
mstore(PI_Z_OMEGA_Y_LOC, mod(calldataload(add(data_ptr, 0x820)), q))
mstore(PI_Z_OMEGA_X_LOC, mod(calldataload(add(data_ptr, 0x840)), q))
}
/**
* LOAD RECURSIVE PROOF INTO MEMORY
*/
{
if mload(CONTAINS_RECURSIVE_PROOF_LOC) {
let public_inputs_ptr := add(calldataload(0x24), 0x24)
let index_counter := add(shl(5, mload(RECURSIVE_PROOF_PUBLIC_INPUT_INDICES_LOC)), public_inputs_ptr)
let x0 := calldataload(index_counter)
x0 := add(x0, shl(68, calldataload(add(index_counter, 0x20))))
x0 := add(x0, shl(136, calldataload(add(index_counter, 0x40))))
x0 := add(x0, shl(204, calldataload(add(index_counter, 0x60))))
let y0 := calldataload(add(index_counter, 0x80))
y0 := add(y0, shl(68, calldataload(add(index_counter, 0xa0))))
y0 := add(y0, shl(136, calldataload(add(index_counter, 0xc0))))
y0 := add(y0, shl(204, calldataload(add(index_counter, 0xe0))))
let x1 := calldataload(add(index_counter, 0x100))
x1 := add(x1, shl(68, calldataload(add(index_counter, 0x120))))
x1 := add(x1, shl(136, calldataload(add(index_counter, 0x140))))
x1 := add(x1, shl(204, calldataload(add(index_counter, 0x160))))
let y1 := calldataload(add(index_counter, 0x180))
y1 := add(y1, shl(68, calldataload(add(index_counter, 0x1a0))))
y1 := add(y1, shl(136, calldataload(add(index_counter, 0x1c0))))
y1 := add(y1, shl(204, calldataload(add(index_counter, 0x1e0))))
mstore(RECURSIVE_P1_X_LOC, x0)
mstore(RECURSIVE_P1_Y_LOC, y0)
mstore(RECURSIVE_P2_X_LOC, x1)
mstore(RECURSIVE_P2_Y_LOC, y1)
// validate these are valid bn128 G1 points
if iszero(and(and(lt(x0, q), lt(x1, q)), and(lt(y0, q), lt(y1, q)))) {
mstore(0x00, PUBLIC_INPUT_INVALID_BN128_G1_POINT_SELECTOR)
revert(0x00, 0x04)
}
}
}
{
/**
* Generate initial challenge
*/
mstore(0x00, shl(224, mload(N_LOC)))
mstore(0x04, shl(224, mload(NUM_INPUTS_LOC)))
let challenge := keccak256(0x00, 0x08)
/**
* Generate eta challenge
*/
mstore(PUBLIC_INPUTS_HASH_LOCATION, challenge)
// The public input location is stored at 0x24, we then add 0x24 to skip selector and the length of public inputs
let public_inputs_start := add(calldataload(0x24), 0x24)
// copy the public inputs over
let public_input_size := mul(mload(NUM_INPUTS_LOC), 0x20)
calldatacopy(add(PUBLIC_INPUTS_HASH_LOCATION, 0x20), public_inputs_start, public_input_size)
// copy W1, W2, W3 into challenge. Each point is 0x40 bytes, so load 0xc0 = 3 * 0x40 bytes (ETA input length)
let w_start := add(calldataload(0x04), 0x24)
calldatacopy(add(add(PUBLIC_INPUTS_HASH_LOCATION, 0x20), public_input_size), w_start, ETA_INPUT_LENGTH)
// Challenge is the old challenge + public inputs + W1, W2, W3 (0x20 + public_input_size + 0xc0)
let challenge_bytes_size := add(0x20, add(public_input_size, ETA_INPUT_LENGTH))
challenge := keccak256(PUBLIC_INPUTS_HASH_LOCATION, challenge_bytes_size)
{
let eta := mod(challenge, p)
mstore(C_ETA_LOC, eta)
mstore(C_ETA_SQR_LOC, mulmod(eta, eta, p))
mstore(C_ETA_CUBE_LOC, mulmod(mload(C_ETA_SQR_LOC), eta, p))
}
/**
* Generate beta challenge
*/
mstore(0x00, challenge)
mstore(0x20, mload(W4_Y_LOC))
mstore(0x40, mload(W4_X_LOC))
mstore(0x60, mload(S_Y_LOC))
mstore(0x80, mload(S_X_LOC))
challenge := keccak256(0x00, 0xa0)
mstore(C_BETA_LOC, mod(challenge, p))
/**
* Generate gamma challenge
*/
mstore(0x00, challenge)
mstore8(0x20, 0x01)
challenge := keccak256(0x00, 0x21)
mstore(C_GAMMA_LOC, mod(challenge, p))
/**
* Generate alpha challenge
*/
mstore(0x00, challenge)
mstore(0x20, mload(Z_Y_LOC))
mstore(0x40, mload(Z_X_LOC))
mstore(0x60, mload(Z_LOOKUP_Y_LOC))
mstore(0x80, mload(Z_LOOKUP_X_LOC))
challenge := keccak256(0x00, 0xa0)
mstore(C_ALPHA_LOC, mod(challenge, p))
/**
* Compute and store some powers of alpha for future computations
*/
let alpha := mload(C_ALPHA_LOC)
mstore(C_ALPHA_SQR_LOC, mulmod(alpha, alpha, p))
mstore(C_ALPHA_CUBE_LOC, mulmod(mload(C_ALPHA_SQR_LOC), alpha, p))
mstore(C_ALPHA_QUAD_LOC, mulmod(mload(C_ALPHA_CUBE_LOC), alpha, p))
mstore(C_ALPHA_BASE_LOC, alpha)
/**
* Generate zeta challenge
*/
mstore(0x00, challenge)
mstore(0x20, mload(T1_Y_LOC))
mstore(0x40, mload(T1_X_LOC))
mstore(0x60, mload(T2_Y_LOC))
mstore(0x80, mload(T2_X_LOC))
mstore(0xa0, mload(T3_Y_LOC))
mstore(0xc0, mload(T3_X_LOC))
mstore(0xe0, mload(T4_Y_LOC))
mstore(0x100, mload(T4_X_LOC))
challenge := keccak256(0x00, 0x120)
mstore(C_ZETA_LOC, mod(challenge, p))
mstore(C_CURRENT_LOC, challenge)
}
/**
* EVALUATE FIELD OPERATIONS
*/
/**
* COMPUTE PUBLIC INPUT DELTA
* ΔPI = ∏ᵢ∈ℓ(wᵢ + β σ(i) + γ) / ∏ᵢ∈ℓ(wᵢ + β σ'(i) + γ)
*/
{
let beta := mload(C_BETA_LOC) // β
let gamma := mload(C_GAMMA_LOC) // γ
let work_root := mload(OMEGA_LOC) // ω
let numerator_value := 1
let denominator_value := 1
let p_clone := p // move p to the front of the stack
let valid_inputs := true
// Load the starting point of the public inputs (jump over the selector and the length of public inputs [0x24])
let public_inputs_ptr := add(calldataload(0x24), 0x24)
// endpoint_ptr = public_inputs_ptr + num_inputs * 0x20. // every public input is 0x20 bytes
let endpoint_ptr := add(public_inputs_ptr, mul(mload(NUM_INPUTS_LOC), 0x20))
// root_1 = β * 0x05
let root_1 := mulmod(beta, 0x05, p_clone) // k1.β
// root_2 = β * 0x0c
let root_2 := mulmod(beta, 0x0c, p_clone)
// @note 0x05 + 0x07 == 0x0c == external coset generator
for {} lt(public_inputs_ptr, endpoint_ptr) { public_inputs_ptr := add(public_inputs_ptr, 0x20) } {
/**
* input = public_input[i]
* valid_inputs &= input < p
* temp = input + gamma
* numerator_value *= (β.σ(i) + wᵢ + γ) // σ(i) = 0x05.ωⁱ
* denominator_value *= (β.σ'(i) + wᵢ + γ) // σ'(i) = 0x0c.ωⁱ
* root_1 *= ω
* root_2 *= ω
*/
let input := calldataload(public_inputs_ptr)
valid_inputs := and(valid_inputs, lt(input, p_clone))
let temp := addmod(input, gamma, p_clone)
numerator_value := mulmod(numerator_value, add(root_1, temp), p_clone)
denominator_value := mulmod(denominator_value, add(root_2, temp), p_clone)
root_1 := mulmod(root_1, work_root, p_clone)
root_2 := mulmod(root_2, work_root, p_clone)
}
// Revert if not all public inputs are field elements (i.e. < p)
if iszero(valid_inputs) {
mstore(0x00, PUBLIC_INPUT_GE_P_SELECTOR)
revert(0x00, 0x04)
}
mstore(DELTA_NUMERATOR_LOC, numerator_value)
mstore(DELTA_DENOMINATOR_LOC, denominator_value)
}
/**
* Compute Plookup delta factor [γ(1 + β)]^{n-k}
* k = num roots cut out of Z_H = 4
*/
{
let delta_base := mulmod(mload(C_GAMMA_LOC), addmod(mload(C_BETA_LOC), 1, p), p)
let delta_numerator := delta_base
{
let exponent := mload(N_LOC)
let count := 1
for {} lt(count, exponent) { count := add(count, count) } {
delta_numerator := mulmod(delta_numerator, delta_numerator, p)
}
}
mstore(PLOOKUP_DELTA_NUMERATOR_LOC, delta_numerator)
let delta_denominator := mulmod(delta_base, delta_base, p)
delta_denominator := mulmod(delta_denominator, delta_denominator, p)
mstore(PLOOKUP_DELTA_DENOMINATOR_LOC, delta_denominator)
}
/**
* Compute lagrange poly and vanishing poly fractions
*/
{
/**
* vanishing_numerator = zeta
* ZETA_POW_N = zeta^n
* vanishing_numerator -= 1
* accumulating_root = omega_inverse
* work_root = p - accumulating_root
* domain_inverse = domain_inverse
* vanishing_denominator = zeta + work_root
* work_root *= accumulating_root
* vanishing_denominator *= (zeta + work_root)
* work_root *= accumulating_root
* vanishing_denominator *= (zeta + work_root)
* vanishing_denominator *= (zeta + (zeta + accumulating_root))
* work_root = omega
* lagrange_numerator = vanishing_numerator * domain_inverse
* l_start_denominator = zeta - 1
* accumulating_root = work_root^2
* l_end_denominator = accumulating_root^2 * work_root * zeta - 1
* Note: l_end_denominator term contains a term \omega^5 to cut out 5 roots of unity from vanishing poly
*/
let zeta := mload(C_ZETA_LOC)
// compute zeta^n, where n is a power of 2
let vanishing_numerator := zeta
{
// pow_small
let exponent := mload(N_LOC)
let count := 1
for {} lt(count, exponent) { count := add(count, count) } {
vanishing_numerator := mulmod(vanishing_numerator, vanishing_numerator, p)
}
}
mstore(ZETA_POW_N_LOC, vanishing_numerator)
vanishing_numerator := addmod(vanishing_numerator, sub(p, 1), p)
let accumulating_root := mload(OMEGA_INVERSE_LOC)
let work_root := sub(p, accumulating_root)
let domain_inverse := mload(DOMAIN_INVERSE_LOC)
let vanishing_denominator := addmod(zeta, work_root, p)
work_root := mulmod(work_root, accumulating_root, p)
vanishing_denominator := mulmod(vanishing_denominator, addmod(zeta, work_root, p), p)
work_root := mulmod(work_root, accumulating_root, p)
vanishing_denominator := mulmod(vanishing_denominator, addmod(zeta, work_root, p), p)
vanishing_denominator :=
mulmod(vanishing_denominator, addmod(zeta, mulmod(work_root, accumulating_root, p), p), p)
work_root := mload(OMEGA_LOC)
let lagrange_numerator := mulmod(vanishing_numerator, domain_inverse, p)
let l_start_denominator := addmod(zeta, sub(p, 1), p)
accumulating_root := mulmod(work_root, work_root, p)
let l_end_denominator :=
addmod(
mulmod(mulmod(mulmod(accumulating_root, accumulating_root, p), work_root, p), zeta, p), sub(p, 1), p
)
/**
* Compute inversions using Montgomery's batch inversion trick
*/
let accumulator := mload(DELTA_DENOMINATOR_LOC)
let t0 := accumulator
accumulator := mulmod(accumulator, vanishing_denominator, p)
let t1 := accumulator
accumulator := mulmod(accumulator, vanishing_numerator, p)
let t2 := accumulator
accumulator := mulmod(accumulator, l_start_denominator, p)
let t3 := accumulator
accumulator := mulmod(accumulator, mload(PLOOKUP_DELTA_DENOMINATOR_LOC), p)
let t4 := accumulator
{
mstore(0, 0x20)
mstore(0x20, 0x20)
mstore(0x40, 0x20)
mstore(0x60, mulmod(accumulator, l_end_denominator, p))
mstore(0x80, sub(p, 2))
mstore(0xa0, p)
if iszero(staticcall(gas(), 0x05, 0x00, 0xc0, 0x00, 0x20)) {
mstore(0x0, MOD_EXP_FAILURE_SELECTOR)
revert(0x00, 0x04)
}
accumulator := mload(0x00)
}
t4 := mulmod(accumulator, t4, p)
accumulator := mulmod(accumulator, l_end_denominator, p)
t3 := mulmod(accumulator, t3, p)
accumulator := mulmod(accumulator, mload(PLOOKUP_DELTA_DENOMINATOR_LOC), p)
t2 := mulmod(accumulator, t2, p)
accumulator := mulmod(accumulator, l_start_denominator, p)
t1 := mulmod(accumulator, t1, p)
accumulator := mulmod(accumulator, vanishing_numerator, p)
t0 := mulmod(accumulator, t0, p)
accumulator := mulmod(accumulator, vanishing_denominator, p)
accumulator := mulmod(mulmod(accumulator, accumulator, p), mload(DELTA_DENOMINATOR_LOC), p)
mstore(PUBLIC_INPUT_DELTA_LOC, mulmod(mload(DELTA_NUMERATOR_LOC), accumulator, p))
mstore(ZERO_POLY_LOC, mulmod(vanishing_numerator, t0, p))
mstore(ZERO_POLY_INVERSE_LOC, mulmod(vanishing_denominator, t1, p))
mstore(L_START_LOC, mulmod(lagrange_numerator, t2, p))
mstore(PLOOKUP_DELTA_LOC, mulmod(mload(PLOOKUP_DELTA_NUMERATOR_LOC), t3, p))
mstore(L_END_LOC, mulmod(lagrange_numerator, t4, p))
}
/**
* UltraPlonk Widget Ordering:
*
* 1. Permutation widget
* 2. Plookup widget
* 3. Arithmetic widget
* 4. Fixed base widget (?)
* 5. GenPermSort widget
* 6. Elliptic widget
* 7. Auxiliary widget
*/
/**
* COMPUTE PERMUTATION WIDGET EVALUATION
*/
{
let alpha := mload(C_ALPHA_LOC)
let beta := mload(C_BETA_LOC)
let gamma := mload(C_GAMMA_LOC)
/**
* t1 = (W1 + gamma + beta * ID1) * (W2 + gamma + beta * ID2)
* t2 = (W3 + gamma + beta * ID3) * (W4 + gamma + beta * ID4)
* result = alpha_base * z_eval * t1 * t2
* t1 = (W1 + gamma + beta * sigma_1_eval) * (W2 + gamma + beta * sigma_2_eval)
* t2 = (W2 + gamma + beta * sigma_3_eval) * (W3 + gamma + beta * sigma_4_eval)
* result -= (alpha_base * z_omega_eval * t1 * t2)
*/
let t1 :=
mulmod(
add(add(mload(W1_EVAL_LOC), gamma), mulmod(beta, mload(ID1_EVAL_LOC), p)),
add(add(mload(W2_EVAL_LOC), gamma), mulmod(beta, mload(ID2_EVAL_LOC), p)),
p
)
let t2 :=
mulmod(
add(add(mload(W3_EVAL_LOC), gamma), mulmod(beta, mload(ID3_EVAL_LOC), p)),
add(add(mload(W4_EVAL_LOC), gamma), mulmod(beta, mload(ID4_EVAL_LOC), p)),
p
)
let result := mulmod(mload(C_ALPHA_BASE_LOC), mulmod(mload(Z_EVAL_LOC), mulmod(t1, t2, p), p), p)
t1 :=
mulmod(
add(add(mload(W1_EVAL_LOC), gamma), mulmod(beta, mload(SIGMA1_EVAL_LOC), p)),
add(add(mload(W2_EVAL_LOC), gamma), mulmod(beta, mload(SIGMA2_EVAL_LOC), p)),
p
)
t2 :=
mulmod(
add(add(mload(W3_EVAL_LOC), gamma), mulmod(beta, mload(SIGMA3_EVAL_LOC), p)),
add(add(mload(W4_EVAL_LOC), gamma), mulmod(beta, mload(SIGMA4_EVAL_LOC), p)),
p
)
result :=
addmod(
result,
sub(p, mulmod(mload(C_ALPHA_BASE_LOC), mulmod(mload(Z_OMEGA_EVAL_LOC), mulmod(t1, t2, p), p), p)),
p
)
/**
* alpha_base *= alpha
* result += alpha_base . (L_{n-k}(ʓ) . (z(ʓ.ω) - ∆_{PI}))
* alpha_base *= alpha
* result += alpha_base . (L_1(ʓ)(Z(ʓ) - 1))
* alpha_Base *= alpha
*/
mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p))
result :=
addmod(
result,
mulmod(
mload(C_ALPHA_BASE_LOC),
mulmod(
mload(L_END_LOC),
addmod(mload(Z_OMEGA_EVAL_LOC), sub(p, mload(PUBLIC_INPUT_DELTA_LOC)), p),
p
),
p
),
p
)
mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p))
mstore(
PERMUTATION_IDENTITY,
addmod(
result,
mulmod(
mload(C_ALPHA_BASE_LOC),
mulmod(mload(L_START_LOC), addmod(mload(Z_EVAL_LOC), sub(p, 1), p), p),
p
),
p
)
)
mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p))
}
/**
* COMPUTE PLOOKUP WIDGET EVALUATION
*/
{
/**
* Goal: f = (w1(z) + q2.w1(zω)) + η(w2(z) + qm.w2(zω)) + η²(w3(z) + qc.w_3(zω)) + q3(z).η³
* f = η.q3(z)
* f += (w3(z) + qc.w_3(zω))
* f *= η
* f += (w2(z) + qm.w2(zω))
* f *= η
* f += (w1(z) + q2.w1(zω))
*/
let f := mulmod(mload(C_ETA_LOC), mload(Q3_EVAL_LOC), p)
f :=
addmod(f, addmod(mload(W3_EVAL_LOC), mulmod(mload(QC_EVAL_LOC), mload(W3_OMEGA_EVAL_LOC), p), p), p)
f := mulmod(f, mload(C_ETA_LOC), p)
f :=
addmod(f, addmod(mload(W2_EVAL_LOC), mulmod(mload(QM_EVAL_LOC), mload(W2_OMEGA_EVAL_LOC), p), p), p)
f := mulmod(f, mload(C_ETA_LOC), p)
f :=
addmod(f, addmod(mload(W1_EVAL_LOC), mulmod(mload(Q2_EVAL_LOC), mload(W1_OMEGA_EVAL_LOC), p), p), p)
// t(z) = table4(z).η³ + table3(z).η² + table2(z).η + table1(z)
let t :=
addmod(
addmod(
addmod(
mulmod(mload(TABLE4_EVAL_LOC), mload(C_ETA_CUBE_LOC), p),
mulmod(mload(TABLE3_EVAL_LOC), mload(C_ETA_SQR_LOC), p),
p
),
mulmod(mload(TABLE2_EVAL_LOC), mload(C_ETA_LOC), p),
p
),
mload(TABLE1_EVAL_LOC),
p
)
// t(zw) = table4(zw).η³ + table3(zw).η² + table2(zw).η + table1(zw)
let t_omega :=
addmod(
addmod(
addmod(
mulmod(mload(TABLE4_OMEGA_EVAL_LOC), mload(C_ETA_CUBE_LOC), p),
mulmod(mload(TABLE3_OMEGA_EVAL_LOC), mload(C_ETA_SQR_LOC), p),
p
),
mulmod(mload(TABLE2_OMEGA_EVAL_LOC), mload(C_ETA_LOC), p),
p
),
mload(TABLE1_OMEGA_EVAL_LOC),
p
)
/**
* Goal: numerator = (TABLE_TYPE_EVAL * f(z) + γ) * (t(z) + βt(zω) + γ(β + 1)) * (β + 1)
* gamma_beta_constant = γ(β + 1)
* numerator = f * TABLE_TYPE_EVAL + gamma
* temp0 = t(z) + t(zω) * β + gamma_beta_constant
* numerator *= temp0
* numerator *= (β + 1)
* temp0 = alpha * l_1
* numerator += temp0
* numerator *= z_lookup(z)
* numerator -= temp0
*/
let gamma_beta_constant := mulmod(mload(C_GAMMA_LOC), addmod(mload(C_BETA_LOC), 1, p), p)
let numerator := addmod(mulmod(f, mload(TABLE_TYPE_EVAL_LOC), p), mload(C_GAMMA_LOC), p)
let temp0 := addmod(addmod(t, mulmod(t_omega, mload(C_BETA_LOC), p), p), gamma_beta_constant, p)
numerator := mulmod(numerator, temp0, p)
numerator := mulmod(numerator, addmod(mload(C_BETA_LOC), 1, p), p)
temp0 := mulmod(mload(C_ALPHA_LOC), mload(L_START_LOC), p)
numerator := addmod(numerator, temp0, p)
numerator := mulmod(numerator, mload(Z_LOOKUP_EVAL_LOC), p)
numerator := addmod(numerator, sub(p, temp0), p)
/**
* Goal: denominator = z_lookup(zω)*[s(z) + βs(zω) + γ(1 + β)] - [z_lookup(zω) - [γ(1 + β)]^{n-k}]*α²L_end(z)
* note: delta_factor = [γ(1 + β)]^{n-k}
* denominator = s(z) + βs(zω) + γ(β + 1)
* temp1 = α²L_end(z)
* denominator -= temp1
* denominator *= z_lookup(zω)
* denominator += temp1 * delta_factor
* PLOOKUP_IDENTITY = (numerator - denominator).alpha_base
* alpha_base *= alpha^3
*/
let denominator :=
addmod(
addmod(mload(S_EVAL_LOC), mulmod(mload(S_OMEGA_EVAL_LOC), mload(C_BETA_LOC), p), p),
gamma_beta_constant,
p
)
let temp1 := mulmod(mload(C_ALPHA_SQR_LOC), mload(L_END_LOC), p)
denominator := addmod(denominator, sub(p, temp1), p)
denominator := mulmod(denominator, mload(Z_LOOKUP_OMEGA_EVAL_LOC), p)
denominator := addmod(denominator, mulmod(temp1, mload(PLOOKUP_DELTA_LOC), p), p)
mstore(PLOOKUP_IDENTITY, mulmod(addmod(numerator, sub(p, denominator), p), mload(C_ALPHA_BASE_LOC), p))
// update alpha
mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_CUBE_LOC), p))
}
/**
* COMPUTE ARITHMETIC WIDGET EVALUATION
*/
{
/**
* The basic arithmetic gate identity in standard plonk is as follows.
* (w_1 . w_2 . q_m) + (w_1 . q_1) + (w_2 . q_2) + (w_3 . q_3) + (w_4 . q_4) + q_c = 0
* However, for Ultraplonk, we extend this to support "passing" wires between rows (shown without alpha scaling below):
* q_arith * ( ( (-1/2) * (q_arith - 3) * q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c ) +
* (q_arith - 1)*( α * (q_arith - 2) * (w_1 + w_4 - w_1_omega + q_m) + w_4_omega) ) = 0
*
* This formula results in several cases depending on q_arith:
* 1. q_arith == 0: Arithmetic gate is completely disabled
*
* 2. q_arith == 1: Everything in the minigate on the right is disabled. The equation is just a standard plonk equation
* with extra wires: q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c = 0
*
* 3. q_arith == 2: The (w_1 + w_4 - ...) term is disabled. THe equation is:
* (1/2) * q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + w_4_omega = 0
* It allows defining w_4 at next index (w_4_omega) in terms of current wire values
*
* 4. q_arith == 3: The product of w_1 and w_2 is disabled, but a mini addition gate is enabled. α allows us to split
* the equation into two:
*
* q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + 2 * w_4_omega = 0
* and
* w_1 + w_4 - w_1_omega + q_m = 0 (we are reusing q_m here)
*
* 5. q_arith > 3: The product of w_1 and w_2 is scaled by (q_arith - 3), while the w_4_omega term is scaled by (q_arith - 1).
* The equation can be split into two:
*
* (q_arith - 3)* q_m * w_1 * w_ 2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + (q_arith - 1) * w_4_omega = 0
* and
* w_1 + w_4 - w_1_omega + q_m = 0
*
* The problem that q_m is used both in both equations can be dealt with by appropriately changing selector values at
* the next gate. Then we can treat (q_arith - 1) as a simulated q_6 selector and scale q_m to handle (q_arith - 3) at
* product.
*/
let w1q1 := mulmod(mload(W1_EVAL_LOC), mload(Q1_EVAL_LOC), p)
let w2q2 := mulmod(mload(W2_EVAL_LOC), mload(Q2_EVAL_LOC), p)
let w3q3 := mulmod(mload(W3_EVAL_LOC), mload(Q3_EVAL_LOC), p)
let w4q3 := mulmod(mload(W4_EVAL_LOC), mload(Q4_EVAL_LOC), p)
// @todo - Add a explicit test that hits QARITH == 3
// w1w2qm := (w_1 . w_2 . q_m . (QARITH_EVAL_LOC - 3)) / 2
let w1w2qm :=
mulmod(
mulmod(
mulmod(mulmod(mload(W1_EVAL_LOC), mload(W2_EVAL_LOC), p), mload(QM_EVAL_LOC), p),
addmod(mload(QARITH_EVAL_LOC), sub(p, 3), p),
p
),
NEGATIVE_INVERSE_OF_2_MODULO_P,
p
)
// (w_1 . w_2 . q_m . (q_arith - 3)) / -2) + (w_1 . q_1) + (w_2 . q_2) + (w_3 . q_3) + (w_4 . q_4) + q_c
let identity :=
addmod(
mload(QC_EVAL_LOC), addmod(w4q3, addmod(w3q3, addmod(w2q2, addmod(w1q1, w1w2qm, p), p), p), p), p
)
// if q_arith == 3 we evaluate an additional mini addition gate (on top of the regular one), where:
// w_1 + w_4 - w_1_omega + q_m = 0
// we use this gate to save an addition gate when adding or subtracting non-native field elements
// α * (q_arith - 2) * (w_1 + w_4 - w_1_omega + q_m)
let extra_small_addition_gate_identity :=
mulmod(
mload(C_ALPHA_LOC),
mulmod(
addmod(mload(QARITH_EVAL_LOC), sub(p, 2), p),
addmod(
mload(QM_EVAL_LOC),
addmod(
sub(p, mload(W1_OMEGA_EVAL_LOC)), addmod(mload(W1_EVAL_LOC), mload(W4_EVAL_LOC), p), p
),
p
),
p
),
p
)
// if q_arith == 2 OR q_arith == 3 we add the 4th wire of the NEXT gate into the arithmetic identity
// N.B. if q_arith > 2, this wire value will be scaled by (q_arith - 1) relative to the other gate wires!
// alpha_base * q_arith * (identity + (q_arith - 1) * (w_4_omega + extra_small_addition_gate_identity))
mstore(
ARITHMETIC_IDENTITY,
mulmod(
mload(C_ALPHA_BASE_LOC),
mulmod(
mload(QARITH_EVAL_LOC),
addmod(
identity,
mulmod(
addmod(mload(QARITH_EVAL_LOC), sub(p, 1), p),
addmod(mload(W4_OMEGA_EVAL_LOC), extra_small_addition_gate_identity, p),
p
),
p
),
p
),
p
)
)
// update alpha
mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_SQR_LOC), p))
}
/**
* COMPUTE GENPERMSORT WIDGET EVALUATION
*/
{
/**
* D1 = (w2 - w1)
* D2 = (w3 - w2)
* D3 = (w4 - w3)
* D4 = (w1_omega - w4)
*
* α_a = alpha_base
* α_b = alpha_base * α
* α_c = alpha_base * α^2
* α_d = alpha_base * α^3
*
* range_accumulator = (
* D1(D1 - 1)(D1 - 2)(D1 - 3).α_a +
* D2(D2 - 1)(D2 - 2)(D2 - 3).α_b +
* D3(D3 - 1)(D3 - 2)(D3 - 3).α_c +
* D4(D4 - 1)(D4 - 2)(D4 - 3).α_d +
* ) . q_sort
*/
let minus_two := sub(p, 2)
let minus_three := sub(p, 3)
let d1 := addmod(mload(W2_EVAL_LOC), sub(p, mload(W1_EVAL_LOC)), p)
let d2 := addmod(mload(W3_EVAL_LOC), sub(p, mload(W2_EVAL_LOC)), p)
let d3 := addmod(mload(W4_EVAL_LOC), sub(p, mload(W3_EVAL_LOC)), p)
let d4 := addmod(mload(W1_OMEGA_EVAL_LOC), sub(p, mload(W4_EVAL_LOC)), p)
let range_accumulator :=
mulmod(
mulmod(
mulmod(addmod(mulmod(d1, d1, p), sub(p, d1), p), addmod(d1, minus_two, p), p),
addmod(d1, minus_three, p),
p
),
mload(C_ALPHA_BASE_LOC),
p
)
range_accumulator :=
addmod(
range_accumulator,
mulmod(
mulmod(
mulmod(addmod(mulmod(d2, d2, p), sub(p, d2), p), addmod(d2, minus_two, p), p),
addmod(d2, minus_three, p),
p
),
mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p),
p
),
p
)
range_accumulator :=
addmod(
range_accumulator,
mulmod(
mulmod(
mulmod(addmod(mulmod(d3, d3, p), sub(p, d3), p), addmod(d3, minus_two, p), p),
addmod(d3, minus_three, p),
p
),
mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_SQR_LOC), p),
p
),
p
)
range_accumulator :=
addmod(
range_accumulator,
mulmod(
mulmod(
mulmod(addmod(mulmod(d4, d4, p), sub(p, d4), p), addmod(d4, minus_two, p), p),
addmod(d4, minus_three, p),
p
),
mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_CUBE_LOC), p),
p
),
p
)
range_accumulator := mulmod(range_accumulator, mload(QSORT_EVAL_LOC), p)
mstore(SORT_IDENTITY, range_accumulator)
// update alpha
mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_QUAD_LOC), p))
}
/**
* COMPUTE ELLIPTIC WIDGET EVALUATION
*/
{
/**
* endo_term = (-x_2) * x_1 * (x_3 * 2 + x_1) * q_beta
* endo_sqr_term = x_2^2
* endo_sqr_term *= (x_3 - x_1)
* endo_sqr_term *= q_beta^2
* leftovers = x_2^2
* leftovers *= x_2
* leftovers += x_1^2 * (x_3 + x_1) @follow-up Invalid comment in BB widget
* leftovers -= (y_2^2 + y_1^2)
* sign_term = y_2 * y_1
* sign_term += sign_term
* sign_term *= q_sign
*/
// q_elliptic * (x3 + x2 + x1)(x2 - x1)(x2 - x1) - y2^2 - y1^2 + 2(y2y1)*q_sign = 0
let x_diff := addmod(mload(X2_EVAL_LOC), sub(p, mload(X1_EVAL_LOC)), p)
let y2_sqr := mulmod(mload(Y2_EVAL_LOC), mload(Y2_EVAL_LOC), p)
let y1_sqr := mulmod(mload(Y1_EVAL_LOC), mload(Y1_EVAL_LOC), p)
let y1y2 := mulmod(mulmod(mload(Y1_EVAL_LOC), mload(Y2_EVAL_LOC), p), mload(QSIGN_LOC), p)
let x_add_identity :=
addmod(
mulmod(
addmod(mload(X3_EVAL_LOC), addmod(mload(X2_EVAL_LOC), mload(X1_EVAL_LOC), p), p),
mulmod(x_diff, x_diff, p),
p
),
addmod(
sub(
p,
addmod(y2_sqr, y1_sqr, p)
),
addmod(y1y2, y1y2, p),
p
),
p
)
x_add_identity :=
mulmod(
mulmod(
x_add_identity,
addmod(
1,
sub(p, mload(QM_EVAL_LOC)),
p
),
p
),
mload(C_ALPHA_BASE_LOC),
p
)
// q_elliptic * (x3 + x2 + x1)(x2 - x1)(x2 - x1) - y2^2 - y1^2 + 2(y2y1)*q_sign = 0
let y1_plus_y3 := addmod(
mload(Y1_EVAL_LOC),
mload(Y3_EVAL_LOC),
p
)
let y_diff := addmod(mulmod(mload(Y2_EVAL_LOC), mload(QSIGN_LOC), p), sub(p, mload(Y1_EVAL_LOC)), p)
let y_add_identity :=
addmod(
mulmod(y1_plus_y3, x_diff, p),
mulmod(addmod(mload(X3_EVAL_LOC), sub(p, mload(X1_EVAL_LOC)), p), y_diff, p),
p
)
y_add_identity :=
mulmod(
mulmod(y_add_identity, addmod(1, sub(p, mload(QM_EVAL_LOC)), p), p),
mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p),
p
)
// ELLIPTIC_IDENTITY = (x_identity + y_identity) * Q_ELLIPTIC_EVAL
mstore(
ELLIPTIC_IDENTITY, mulmod(addmod(x_add_identity, y_add_identity, p), mload(QELLIPTIC_EVAL_LOC), p)
)
}
{
/**
* x_pow_4 = (y_1_sqr - curve_b) * x_1;
* y_1_sqr_mul_4 = y_1_sqr + y_1_sqr;
* y_1_sqr_mul_4 += y_1_sqr_mul_4;
* x_1_pow_4_mul_9 = x_pow_4;
* x_1_pow_4_mul_9 += x_1_pow_4_mul_9;
* x_1_pow_4_mul_9 += x_1_pow_4_mul_9;
* x_1_pow_4_mul_9 += x_1_pow_4_mul_9;
* x_1_pow_4_mul_9 += x_pow_4;
* x_1_sqr_mul_3 = x_1_sqr + x_1_sqr + x_1_sqr;
* x_double_identity = (x_3 + x_1 + x_1) * y_1_sqr_mul_4 - x_1_pow_4_mul_9;
* y_double_identity = x_1_sqr_mul_3 * (x_1 - x_3) - (y_1 + y_1) * (y_1 + y_3);
*/
// (x3 + x1 + x1) (4y1*y1) - 9 * x1 * x1 * x1 * x1 = 0
let x1_sqr := mulmod(mload(X1_EVAL_LOC), mload(X1_EVAL_LOC), p)
let y1_sqr := mulmod(mload(Y1_EVAL_LOC), mload(Y1_EVAL_LOC), p)
let x_pow_4 := mulmod(addmod(y1_sqr, GRUMPKIN_CURVE_B_PARAMETER_NEGATED, p), mload(X1_EVAL_LOC), p)
let y1_sqr_mul_4 := mulmod(y1_sqr, 4, p)
let x1_pow_4_mul_9 := mulmod(x_pow_4, 9, p)
let x1_sqr_mul_3 := mulmod(x1_sqr, 3, p)
let x_double_identity :=
addmod(
mulmod(
addmod(mload(X3_EVAL_LOC), addmod(mload(X1_EVAL_LOC), mload(X1_EVAL_LOC), p), p),
y1_sqr_mul_4,
p
),
sub(p, x1_pow_4_mul_9),
p
)
// (y1 + y1) (2y1) - (3 * x1 * x1)(x1 - x3) = 0
let y_double_identity :=
addmod(
mulmod(x1_sqr_mul_3, addmod(mload(X1_EVAL_LOC), sub(p, mload(X3_EVAL_LOC)), p), p),
sub(
p,
mulmod(
addmod(mload(Y1_EVAL_LOC), mload(Y1_EVAL_LOC), p),
addmod(mload(Y1_EVAL_LOC), mload(Y3_EVAL_LOC), p),
p
)
),
p
)
x_double_identity := mulmod(x_double_identity, mload(C_ALPHA_BASE_LOC), p)
y_double_identity :=
mulmod(y_double_identity, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_LOC), p), p)
x_double_identity := mulmod(x_double_identity, mload(QM_EVAL_LOC), p)
y_double_identity := mulmod(y_double_identity, mload(QM_EVAL_LOC), p)
// ELLIPTIC_IDENTITY += (x_double_identity + y_double_identity) * Q_DOUBLE_EVAL
mstore(
ELLIPTIC_IDENTITY,
addmod(
mload(ELLIPTIC_IDENTITY),
mulmod(addmod(x_double_identity, y_double_identity, p), mload(QELLIPTIC_EVAL_LOC), p),
p
)
)
// update alpha
mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_QUAD_LOC), p))
}
/**
* COMPUTE AUXILIARY WIDGET EVALUATION
*/
{
{
/**
* Non native field arithmetic gate 2
* _ _
* / _ _ _ 14 \
* q_2 . q_4 | (w_1 . w_2) + (w_1 . w_2) + (w_1 . w_4 + w_2 . w_3 - w_3) . 2 - w_3 - w_4 |
* \_ _/
*
* limb_subproduct = w_1 . w_2_omega + w_1_omega . w_2
* non_native_field_gate_2 = w_1 * w_4 + w_4 * w_3 - w_3_omega
* non_native_field_gate_2 = non_native_field_gate_2 * limb_size
* non_native_field_gate_2 -= w_4_omega
* non_native_field_gate_2 += limb_subproduct
* non_native_field_gate_2 *= q_4
* limb_subproduct *= limb_size
* limb_subproduct += w_1_omega * w_2_omega
* non_native_field_gate_1 = (limb_subproduct + w_3 + w_4) * q_3
* non_native_field_gate_3 = (limb_subproduct + w_4 - (w_3_omega + w_4_omega)) * q_m
* non_native_field_identity = (non_native_field_gate_1 + non_native_field_gate_2 + non_native_field_gate_3) * q_2
*/
let limb_subproduct :=
addmod(
mulmod(mload(W1_EVAL_LOC), mload(W2_OMEGA_EVAL_LOC), p),
mulmod(mload(W1_OMEGA_EVAL_LOC), mload(W2_EVAL_LOC), p),
p
)
let non_native_field_gate_2 :=
addmod(
addmod(
mulmod(mload(W1_EVAL_LOC), mload(W4_EVAL_LOC), p),
mulmod(mload(W2_EVAL_LOC), mload(W3_EVAL_LOC), p),
p
),
sub(p, mload(W3_OMEGA_EVAL_LOC)),
p
)
non_native_field_gate_2 := mulmod(non_native_field_gate_2, LIMB_SIZE, p)
non_native_field_gate_2 := addmod(non_native_field_gate_2, sub(p, mload(W4_OMEGA_EVAL_LOC)), p)
non_native_field_gate_2 := addmod(non_native_field_gate_2, limb_subproduct, p)
non_native_field_gate_2 := mulmod(non_native_field_gate_2, mload(Q4_EVAL_LOC), p)
limb_subproduct := mulmod(limb_subproduct, LIMB_SIZE, p)
limb_subproduct :=
addmod(limb_subproduct, mulmod(mload(W1_OMEGA_EVAL_LOC), mload(W2_OMEGA_EVAL_LOC), p), p)
let non_native_field_gate_1 :=
mulmod(
addmod(limb_subproduct, sub(p, addmod(mload(W3_EVAL_LOC), mload(W4_EVAL_LOC), p)), p),
mload(Q3_EVAL_LOC),
p
)
let non_native_field_gate_3 :=
mulmod(
addmod(
addmod(limb_subproduct, mload(W4_EVAL_LOC), p),
sub(p, addmod(mload(W3_OMEGA_EVAL_LOC), mload(W4_OMEGA_EVAL_LOC), p)),
p
),
mload(QM_EVAL_LOC),
p
)
let non_native_field_identity :=
mulmod(
addmod(addmod(non_native_field_gate_1, non_native_field_gate_2, p), non_native_field_gate_3, p),
mload(Q2_EVAL_LOC),
p
)
mstore(AUX_NON_NATIVE_FIELD_EVALUATION, non_native_field_identity)
}
{
/**
* limb_accumulator_1 = w_2_omega;
* limb_accumulator_1 *= SUBLIMB_SHIFT;
* limb_accumulator_1 += w_1_omega;
* limb_accumulator_1 *= SUBLIMB_SHIFT;
* limb_accumulator_1 += w_3;
* limb_accumulator_1 *= SUBLIMB_SHIFT;
* limb_accumulator_1 += w_2;
* limb_accumulator_1 *= SUBLIMB_SHIFT;
* limb_accumulator_1 += w_1;
* limb_accumulator_1 -= w_4;
* limb_accumulator_1 *= q_4;
*/
let limb_accumulator_1 := mulmod(mload(W2_OMEGA_EVAL_LOC), SUBLIMB_SHIFT, p)
limb_accumulator_1 := addmod(limb_accumulator_1, mload(W1_OMEGA_EVAL_LOC), p)
limb_accumulator_1 := mulmod(limb_accumulator_1, SUBLIMB_SHIFT, p)
limb_accumulator_1 := addmod(limb_accumulator_1, mload(W3_EVAL_LOC), p)
limb_accumulator_1 := mulmod(limb_accumulator_1, SUBLIMB_SHIFT, p)
limb_accumulator_1 := addmod(limb_accumulator_1, mload(W2_EVAL_LOC), p)
limb_accumulator_1 := mulmod(limb_accumulator_1, SUBLIMB_SHIFT, p)
limb_accumulator_1 := addmod(limb_accumulator_1, mload(W1_EVAL_LOC), p)
limb_accumulator_1 := addmod(limb_accumulator_1, sub(p, mload(W4_EVAL_LOC)), p)
limb_accumulator_1 := mulmod(limb_accumulator_1, mload(Q4_EVAL_LOC), p)
/**
* limb_accumulator_2 = w_3_omega;
* limb_accumulator_2 *= SUBLIMB_SHIFT;
* limb_accumulator_2 += w_2_omega;
* limb_accumulator_2 *= SUBLIMB_SHIFT;
* limb_accumulator_2 += w_1_omega;
* limb_accumulator_2 *= SUBLIMB_SHIFT;
* limb_accumulator_2 += w_4;
* limb_accumulator_2 *= SUBLIMB_SHIFT;
* limb_accumulator_2 += w_3;
* limb_accumulator_2 -= w_4_omega;
* limb_accumulator_2 *= q_m;
*/
let limb_accumulator_2 := mulmod(mload(W3_OMEGA_EVAL_LOC), SUBLIMB_SHIFT, p)
limb_accumulator_2 := addmod(limb_accumulator_2, mload(W2_OMEGA_EVAL_LOC), p)
limb_accumulator_2 := mulmod(limb_accumulator_2, SUBLIMB_SHIFT, p)
limb_accumulator_2 := addmod(limb_accumulator_2, mload(W1_OMEGA_EVAL_LOC), p)
limb_accumulator_2 := mulmod(limb_accumulator_2, SUBLIMB_SHIFT, p)
limb_accumulator_2 := addmod(limb_accumulator_2, mload(W4_EVAL_LOC), p)
limb_accumulator_2 := mulmod(limb_accumulator_2, SUBLIMB_SHIFT, p)
limb_accumulator_2 := addmod(limb_accumulator_2, mload(W3_EVAL_LOC), p)
limb_accumulator_2 := addmod(limb_accumulator_2, sub(p, mload(W4_OMEGA_EVAL_LOC)), p)
limb_accumulator_2 := mulmod(limb_accumulator_2, mload(QM_EVAL_LOC), p)
mstore(
AUX_LIMB_ACCUMULATOR_EVALUATION,
mulmod(addmod(limb_accumulator_1, limb_accumulator_2, p), mload(Q3_EVAL_LOC), p)
)
}
{
/**
* memory_record_check = w_3;
* memory_record_check *= eta;
* memory_record_check += w_2;
* memory_record_check *= eta;
* memory_record_check += w_1;
* memory_record_check *= eta;
* memory_record_check += q_c;
*
* partial_record_check = memory_record_check;
*
* memory_record_check -= w_4;
*/
let memory_record_check := mulmod(mload(W3_EVAL_LOC), mload(C_ETA_LOC), p)
memory_record_check := addmod(memory_record_check, mload(W2_EVAL_LOC), p)
memory_record_check := mulmod(memory_record_check, mload(C_ETA_LOC), p)
memory_record_check := addmod(memory_record_check, mload(W1_EVAL_LOC), p)
memory_record_check := mulmod(memory_record_check, mload(C_ETA_LOC), p)
memory_record_check := addmod(memory_record_check, mload(QC_EVAL_LOC), p)
let partial_record_check := memory_record_check
memory_record_check := addmod(memory_record_check, sub(p, mload(W4_EVAL_LOC)), p)
mstore(AUX_MEMORY_EVALUATION, memory_record_check)
// index_delta = w_1_omega - w_1
let index_delta := addmod(mload(W1_OMEGA_EVAL_LOC), sub(p, mload(W1_EVAL_LOC)), p)
// record_delta = w_4_omega - w_4
let record_delta := addmod(mload(W4_OMEGA_EVAL_LOC), sub(p, mload(W4_EVAL_LOC)), p)
// index_is_monotonically_increasing = index_delta * (index_delta - 1)
let index_is_monotonically_increasing := mulmod(index_delta, addmod(index_delta, sub(p, 1), p), p)
// adjacent_values_match_if_adjacent_indices_match = record_delta * (1 - index_delta)
let adjacent_values_match_if_adjacent_indices_match :=
mulmod(record_delta, addmod(1, sub(p, index_delta), p), p)
// AUX_ROM_CONSISTENCY_EVALUATION = ((adjacent_values_match_if_adjacent_indices_match * alpha) + index_is_monotonically_increasing) * alpha + partial_record_check
mstore(
AUX_ROM_CONSISTENCY_EVALUATION,
addmod(
mulmod(
addmod(
mulmod(adjacent_values_match_if_adjacent_indices_match, mload(C_ALPHA_LOC), p),
index_is_monotonically_increasing,
p
),
mload(C_ALPHA_LOC),
p
),
memory_record_check,
p
)
)
{
/**
* next_gate_access_type = w_3_omega;
* next_gate_access_type *= eta;
* next_gate_access_type += w_2_omega;
* next_gate_access_type *= eta;
* next_gate_access_type += w_1_omega;
* next_gate_access_type *= eta;
* next_gate_access_type = w_4_omega - next_gate_access_type;
*/
let next_gate_access_type := mulmod(mload(W3_OMEGA_EVAL_LOC), mload(C_ETA_LOC), p)
next_gate_access_type := addmod(next_gate_access_type, mload(W2_OMEGA_EVAL_LOC), p)
next_gate_access_type := mulmod(next_gate_access_type, mload(C_ETA_LOC), p)
next_gate_access_type := addmod(next_gate_access_type, mload(W1_OMEGA_EVAL_LOC), p)
next_gate_access_type := mulmod(next_gate_access_type, mload(C_ETA_LOC), p)
next_gate_access_type := addmod(mload(W4_OMEGA_EVAL_LOC), sub(p, next_gate_access_type), p)
// value_delta = w_3_omega - w_3
let value_delta := addmod(mload(W3_OMEGA_EVAL_LOC), sub(p, mload(W3_EVAL_LOC)), p)
// adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation = (1 - index_delta) * value_delta * (1 - next_gate_access_type);
let adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation :=
mulmod(
addmod(1, sub(p, index_delta), p),
mulmod(value_delta, addmod(1, sub(p, next_gate_access_type), p), p),
p
)
// AUX_RAM_CONSISTENCY_EVALUATION
/**
* access_type = w_4 - partial_record_check
* access_check = access_type^2 - access_type
* next_gate_access_type_is_boolean = next_gate_access_type^2 - next_gate_access_type
* RAM_consistency_check_identity = adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation;
* RAM_consistency_check_identity *= alpha;
* RAM_consistency_check_identity += index_is_monotonically_increasing;
* RAM_consistency_check_identity *= alpha;
* RAM_consistency_check_identity += next_gate_access_type_is_boolean;
* RAM_consistency_check_identity *= alpha;
* RAM_consistency_check_identity += access_check;
*/
let access_type := addmod(mload(W4_EVAL_LOC), sub(p, partial_record_check), p)
let access_check := mulmod(access_type, addmod(access_type, sub(p, 1), p), p)
let next_gate_access_type_is_boolean :=
mulmod(next_gate_access_type, addmod(next_gate_access_type, sub(p, 1), p), p)
let RAM_cci :=
mulmod(
adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation,
mload(C_ALPHA_LOC),
p
)
RAM_cci := addmod(RAM_cci, index_is_monotonically_increasing, p)
RAM_cci := mulmod(RAM_cci, mload(C_ALPHA_LOC), p)
RAM_cci := addmod(RAM_cci, next_gate_access_type_is_boolean, p)
RAM_cci := mulmod(RAM_cci, mload(C_ALPHA_LOC), p)
RAM_cci := addmod(RAM_cci, access_check, p)
mstore(AUX_RAM_CONSISTENCY_EVALUATION, RAM_cci)
}
{
// timestamp_delta = w_2_omega - w_2
let timestamp_delta := addmod(mload(W2_OMEGA_EVAL_LOC), sub(p, mload(W2_EVAL_LOC)), p)
// RAM_timestamp_check_identity = (1 - index_delta) * timestamp_delta - w_3
let RAM_timestamp_check_identity :=
addmod(
mulmod(timestamp_delta, addmod(1, sub(p, index_delta), p), p), sub(p, mload(W3_EVAL_LOC)), p
)
/**
* memory_identity = ROM_consistency_check_identity * q_2;
* memory_identity += RAM_timestamp_check_identity * q_4;
* memory_identity += memory_record_check * q_m;
* memory_identity *= q_1;
* memory_identity += (RAM_consistency_check_identity * q_arith);
*
* auxiliary_identity = memory_identity + non_native_field_identity + limb_accumulator_identity;
* auxiliary_identity *= q_aux;
* auxiliary_identity *= alpha_base;
*/
let memory_identity := mulmod(mload(AUX_ROM_CONSISTENCY_EVALUATION), mload(Q2_EVAL_LOC), p)
memory_identity :=
addmod(memory_identity, mulmod(RAM_timestamp_check_identity, mload(Q4_EVAL_LOC), p), p)
memory_identity :=
addmod(memory_identity, mulmod(mload(AUX_MEMORY_EVALUATION), mload(QM_EVAL_LOC), p), p)
memory_identity := mulmod(memory_identity, mload(Q1_EVAL_LOC), p)
memory_identity :=
addmod(
memory_identity, mulmod(mload(AUX_RAM_CONSISTENCY_EVALUATION), mload(QARITH_EVAL_LOC), p), p
)
let auxiliary_identity := addmod(memory_identity, mload(AUX_NON_NATIVE_FIELD_EVALUATION), p)
auxiliary_identity := addmod(auxiliary_identity, mload(AUX_LIMB_ACCUMULATOR_EVALUATION), p)
auxiliary_identity := mulmod(auxiliary_identity, mload(QAUX_EVAL_LOC), p)
auxiliary_identity := mulmod(auxiliary_identity, mload(C_ALPHA_BASE_LOC), p)
mstore(AUX_IDENTITY, auxiliary_identity)
// update alpha
mstore(C_ALPHA_BASE_LOC, mulmod(mload(C_ALPHA_BASE_LOC), mload(C_ALPHA_CUBE_LOC), p))
}
}
}
{
/**
* quotient = ARITHMETIC_IDENTITY
* quotient += PERMUTATION_IDENTITY
* quotient += PLOOKUP_IDENTITY
* quotient += SORT_IDENTITY
* quotient += ELLIPTIC_IDENTITY
* quotient += AUX_IDENTITY
* quotient *= ZERO_POLY_INVERSE
*/
mstore(
QUOTIENT_EVAL_LOC,
mulmod(
addmod(
addmod(
addmod(
addmod(
addmod(mload(PERMUTATION_IDENTITY), mload(PLOOKUP_IDENTITY), p),
mload(ARITHMETIC_IDENTITY),
p
),
mload(SORT_IDENTITY),
p
),
mload(ELLIPTIC_IDENTITY),
p
),
mload(AUX_IDENTITY),
p
),
mload(ZERO_POLY_INVERSE_LOC),
p
)
)
}
/**
* GENERATE NU AND SEPARATOR CHALLENGES
*/
{
let current_challenge := mload(C_CURRENT_LOC)
// get a calldata pointer that points to the start of the data we want to copy
let calldata_ptr := add(calldataload(0x04), 0x24)
calldata_ptr := add(calldata_ptr, NU_CALLDATA_SKIP_LENGTH)
mstore(NU_CHALLENGE_INPUT_LOC_A, current_challenge)
mstore(NU_CHALLENGE_INPUT_LOC_B, mload(QUOTIENT_EVAL_LOC))
calldatacopy(NU_CHALLENGE_INPUT_LOC_C, calldata_ptr, NU_INPUT_LENGTH)
// hash length = (0x20 + num field elements), we include the previous challenge in the hash
let challenge := keccak256(NU_CHALLENGE_INPUT_LOC_A, add(NU_INPUT_LENGTH, 0x40))
mstore(C_V0_LOC, mod(challenge, p))
// We need THIRTY-ONE independent nu challenges!
mstore(0x00, challenge)
mstore8(0x20, 0x01)
mstore(C_V1_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x02)
mstore(C_V2_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x03)
mstore(C_V3_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x04)
mstore(C_V4_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x05)
mstore(C_V5_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x06)
mstore(C_V6_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x07)
mstore(C_V7_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x08)
mstore(C_V8_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x09)
mstore(C_V9_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x0a)
mstore(C_V10_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x0b)
mstore(C_V11_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x0c)
mstore(C_V12_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x0d)
mstore(C_V13_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x0e)
mstore(C_V14_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x0f)
mstore(C_V15_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x10)
mstore(C_V16_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x11)
mstore(C_V17_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x12)
mstore(C_V18_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x13)
mstore(C_V19_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x14)
mstore(C_V20_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x15)
mstore(C_V21_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x16)
mstore(C_V22_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x17)
mstore(C_V23_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x18)
mstore(C_V24_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x19)
mstore(C_V25_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x1a)
mstore(C_V26_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x1b)
mstore(C_V27_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x1c)
mstore(C_V28_LOC, mod(keccak256(0x00, 0x21), p))
mstore8(0x20, 0x1d)
mstore(C_V29_LOC, mod(keccak256(0x00, 0x21), p))
// @follow-up - Why are both v29 and v30 using appending 0x1d to the prior challenge and hashing, should it not change?
mstore8(0x20, 0x1d)
challenge := keccak256(0x00, 0x21)
mstore(C_V30_LOC, mod(challenge, p))
// separator
mstore(0x00, challenge)
mstore(0x20, mload(PI_Z_Y_LOC))
mstore(0x40, mload(PI_Z_X_LOC))
mstore(0x60, mload(PI_Z_OMEGA_Y_LOC))
mstore(0x80, mload(PI_Z_OMEGA_X_LOC))
mstore(C_U_LOC, mod(keccak256(0x00, 0xa0), p))
}
let success := 0
// VALIDATE T1
{
let x := mload(T1_X_LOC)
let y := mload(T1_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
if iszero(eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) {
mstore(0x0, POINT_NOT_ON_CURVE_SELECTOR)
revert(0x00, 0x04)
}
mstore(ACCUMULATOR_X_LOC, x)
mstore(add(ACCUMULATOR_X_LOC, 0x20), y)
}
// VALIDATE T2
{
let x := mload(T2_X_LOC) // 0x1400
let y := mload(T2_Y_LOC) // 0x1420
let xx := mulmod(x, x, q)
// validate on curve
if iszero(eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) {
mstore(0x0, POINT_NOT_ON_CURVE_SELECTOR)
revert(0x00, 0x04)
}
mstore(0x00, x)
mstore(0x20, y)
}
mstore(0x40, mload(ZETA_POW_N_LOC))
// accumulator_2 = [T2].zeta^n
success := staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)
// accumulator = [T1] + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// VALIDATE T3
{
let x := mload(T3_X_LOC)
let y := mload(T3_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
if iszero(eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) {
mstore(0x0, POINT_NOT_ON_CURVE_SELECTOR)
revert(0x00, 0x04)
}
mstore(0x00, x)
mstore(0x20, y)
}
mstore(0x40, mulmod(mload(ZETA_POW_N_LOC), mload(ZETA_POW_N_LOC), p))
// accumulator_2 = [T3].zeta^{2n}
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// VALIDATE T4
{
let x := mload(T4_X_LOC)
let y := mload(T4_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
if iszero(eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) {
mstore(0x0, POINT_NOT_ON_CURVE_SELECTOR)
revert(0x00, 0x04)
}
mstore(0x00, x)
mstore(0x20, y)
}
mstore(0x40, mulmod(mulmod(mload(ZETA_POW_N_LOC), mload(ZETA_POW_N_LOC), p), mload(ZETA_POW_N_LOC), p))
// accumulator_2 = [T4].zeta^{3n}
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// VALIDATE W1
{
let x := mload(W1_X_LOC)
let y := mload(W1_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
if iszero(eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) {
mstore(0x0, POINT_NOT_ON_CURVE_SELECTOR)
revert(0x00, 0x04)
}
mstore(0x00, x)
mstore(0x20, y)
}
mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V0_LOC), p))
// accumulator_2 = v0.(u + 1).[W1]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// VALIDATE W2
{
let x := mload(W2_X_LOC)
let y := mload(W2_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
if iszero(eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) {
mstore(0x0, POINT_NOT_ON_CURVE_SELECTOR)
revert(0x00, 0x04)
}
mstore(0x00, x)
mstore(0x20, y)
}
mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V1_LOC), p))
// accumulator_2 = v1.(u + 1).[W2]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// VALIDATE W3
{
let x := mload(W3_X_LOC)
let y := mload(W3_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
if iszero(eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) {
mstore(0x0, POINT_NOT_ON_CURVE_SELECTOR)
revert(0x00, 0x04)
}
mstore(0x00, x)
mstore(0x20, y)
}
mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V2_LOC), p))
// accumulator_2 = v2.(u + 1).[W3]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// VALIDATE W4
{
let x := mload(W4_X_LOC)
let y := mload(W4_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
if iszero(eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) {
mstore(0x0, POINT_NOT_ON_CURVE_SELECTOR)
revert(0x00, 0x04)
}
mstore(0x00, x)
mstore(0x20, y)
}
mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V3_LOC), p))
// accumulator_2 = v3.(u + 1).[W4]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// VALIDATE S
{
let x := mload(S_X_LOC)
let y := mload(S_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
if iszero(eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) {
mstore(0x0, POINT_NOT_ON_CURVE_SELECTOR)
revert(0x00, 0x04)
}
mstore(0x00, x)
mstore(0x20, y)
}
mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V4_LOC), p))
// accumulator_2 = v4.(u + 1).[S]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// VALIDATE Z
{
let x := mload(Z_X_LOC)
let y := mload(Z_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
if iszero(eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) {
mstore(0x0, POINT_NOT_ON_CURVE_SELECTOR)
revert(0x00, 0x04)
}
mstore(0x00, x)
mstore(0x20, y)
}
mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V5_LOC), p))
// accumulator_2 = v5.(u + 1).[Z]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// VALIDATE Z_LOOKUP
{
let x := mload(Z_LOOKUP_X_LOC)
let y := mload(Z_LOOKUP_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
if iszero(eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) {
mstore(0x0, POINT_NOT_ON_CURVE_SELECTOR)
revert(0x00, 0x04)
}
mstore(0x00, x)
mstore(0x20, y)
}
mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V6_LOC), p))
// accumulator_2 = v6.(u + 1).[Z_LOOKUP]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE Q1
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(Q1_X_LOC))
mstore(0x20, mload(Q1_Y_LOC))
mstore(0x40, mload(C_V7_LOC))
// accumulator_2 = v7.[Q1]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE Q2
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(Q2_X_LOC))
mstore(0x20, mload(Q2_Y_LOC))
mstore(0x40, mload(C_V8_LOC))
// accumulator_2 = v8.[Q2]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE Q3
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(Q3_X_LOC))
mstore(0x20, mload(Q3_Y_LOC))
mstore(0x40, mload(C_V9_LOC))
// accumulator_2 = v9.[Q3]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE Q4
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(Q4_X_LOC))
mstore(0x20, mload(Q4_Y_LOC))
mstore(0x40, mload(C_V10_LOC))
// accumulator_2 = v10.[Q4]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE QM
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(QM_X_LOC))
mstore(0x20, mload(QM_Y_LOC))
mstore(0x40, mload(C_V11_LOC))
// accumulator_2 = v11.[Q;]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE QC
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(QC_X_LOC))
mstore(0x20, mload(QC_Y_LOC))
mstore(0x40, mload(C_V12_LOC))
// accumulator_2 = v12.[QC]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE QARITH
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(QARITH_X_LOC))
mstore(0x20, mload(QARITH_Y_LOC))
mstore(0x40, mload(C_V13_LOC))
// accumulator_2 = v13.[QARITH]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE QSORT
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(QSORT_X_LOC))
mstore(0x20, mload(QSORT_Y_LOC))
mstore(0x40, mload(C_V14_LOC))
// accumulator_2 = v14.[QSORT]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE QELLIPTIC
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(QELLIPTIC_X_LOC))
mstore(0x20, mload(QELLIPTIC_Y_LOC))
mstore(0x40, mload(C_V15_LOC))
// accumulator_2 = v15.[QELLIPTIC]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE QAUX
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(QAUX_X_LOC))
mstore(0x20, mload(QAUX_Y_LOC))
mstore(0x40, mload(C_V16_LOC))
// accumulator_2 = v15.[Q_AUX]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE SIGMA1
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(SIGMA1_X_LOC))
mstore(0x20, mload(SIGMA1_Y_LOC))
mstore(0x40, mload(C_V17_LOC))
// accumulator_2 = v17.[sigma1]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE SIGMA2
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(SIGMA2_X_LOC))
mstore(0x20, mload(SIGMA2_Y_LOC))
mstore(0x40, mload(C_V18_LOC))
// accumulator_2 = v18.[sigma2]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE SIGMA3
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(SIGMA3_X_LOC))
mstore(0x20, mload(SIGMA3_Y_LOC))
mstore(0x40, mload(C_V19_LOC))
// accumulator_2 = v19.[sigma3]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE SIGMA4
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(SIGMA4_X_LOC))
mstore(0x20, mload(SIGMA4_Y_LOC))
mstore(0x40, mload(C_V20_LOC))
// accumulator_2 = v20.[sigma4]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE TABLE1
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(TABLE1_X_LOC))
mstore(0x20, mload(TABLE1_Y_LOC))
mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V21_LOC), p))
// accumulator_2 = u.[table1]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE TABLE2
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(TABLE2_X_LOC))
mstore(0x20, mload(TABLE2_Y_LOC))
mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V22_LOC), p))
// accumulator_2 = u.[table2]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE TABLE3
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(TABLE3_X_LOC))
mstore(0x20, mload(TABLE3_Y_LOC))
mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V23_LOC), p))
// accumulator_2 = u.[table3]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE TABLE4
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(TABLE4_X_LOC))
mstore(0x20, mload(TABLE4_Y_LOC))
mstore(0x40, mulmod(addmod(mload(C_U_LOC), 0x1, p), mload(C_V24_LOC), p))
// accumulator_2 = u.[table4]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE TABLE_TYPE
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(TABLE_TYPE_X_LOC))
mstore(0x20, mload(TABLE_TYPE_Y_LOC))
mstore(0x40, mload(C_V25_LOC))
// accumulator_2 = v25.[TableType]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE ID1
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(ID1_X_LOC))
mstore(0x20, mload(ID1_Y_LOC))
mstore(0x40, mload(C_V26_LOC))
// accumulator_2 = v26.[ID1]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE ID2
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(ID2_X_LOC))
mstore(0x20, mload(ID2_Y_LOC))
mstore(0x40, mload(C_V27_LOC))
// accumulator_2 = v27.[ID2]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE ID3
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(ID3_X_LOC))
mstore(0x20, mload(ID3_Y_LOC))
mstore(0x40, mload(C_V28_LOC))
// accumulator_2 = v28.[ID3]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// ACCUMULATE ID4
// Verification key fields verified to be on curve at contract deployment
mstore(0x00, mload(ID4_X_LOC))
mstore(0x20, mload(ID4_Y_LOC))
mstore(0x40, mload(C_V29_LOC))
// accumulator_2 = v29.[ID4]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
/**
* COMPUTE BATCH EVALUATION SCALAR MULTIPLIER
*/
{
/**
* batch_evaluation = v0 * (w_1_omega * u + w_1_eval)
* batch_evaluation += v1 * (w_2_omega * u + w_2_eval)
* batch_evaluation += v2 * (w_3_omega * u + w_3_eval)
* batch_evaluation += v3 * (w_4_omega * u + w_4_eval)
* batch_evaluation += v4 * (s_omega_eval * u + s_eval)
* batch_evaluation += v5 * (z_omega_eval * u + z_eval)
* batch_evaluation += v6 * (z_lookup_omega_eval * u + z_lookup_eval)
*/
let batch_evaluation :=
mulmod(
mload(C_V0_LOC),
addmod(mulmod(mload(W1_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(W1_EVAL_LOC), p),
p
)
batch_evaluation :=
addmod(
batch_evaluation,
mulmod(
mload(C_V1_LOC),
addmod(mulmod(mload(W2_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(W2_EVAL_LOC), p),
p
),
p
)
batch_evaluation :=
addmod(
batch_evaluation,
mulmod(
mload(C_V2_LOC),
addmod(mulmod(mload(W3_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(W3_EVAL_LOC), p),
p
),
p
)
batch_evaluation :=
addmod(
batch_evaluation,
mulmod(
mload(C_V3_LOC),
addmod(mulmod(mload(W4_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(W4_EVAL_LOC), p),
p
),
p
)
batch_evaluation :=
addmod(
batch_evaluation,
mulmod(
mload(C_V4_LOC),
addmod(mulmod(mload(S_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(S_EVAL_LOC), p),
p
),
p
)
batch_evaluation :=
addmod(
batch_evaluation,
mulmod(
mload(C_V5_LOC),
addmod(mulmod(mload(Z_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(Z_EVAL_LOC), p),
p
),
p
)
batch_evaluation :=
addmod(
batch_evaluation,
mulmod(
mload(C_V6_LOC),
addmod(mulmod(mload(Z_LOOKUP_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(Z_LOOKUP_EVAL_LOC), p),
p
),
p
)
/**
* batch_evaluation += v7 * Q1_EVAL
* batch_evaluation += v8 * Q2_EVAL
* batch_evaluation += v9 * Q3_EVAL
* batch_evaluation += v10 * Q4_EVAL
* batch_evaluation += v11 * QM_EVAL
* batch_evaluation += v12 * QC_EVAL
* batch_evaluation += v13 * QARITH_EVAL
* batch_evaluation += v14 * QSORT_EVAL_LOC
* batch_evaluation += v15 * QELLIPTIC_EVAL_LOC
* batch_evaluation += v16 * QAUX_EVAL_LOC
* batch_evaluation += v17 * SIGMA1_EVAL_LOC
* batch_evaluation += v18 * SIGMA2_EVAL_LOC
* batch_evaluation += v19 * SIGMA3_EVAL_LOC
* batch_evaluation += v20 * SIGMA4_EVAL_LOC
*/
batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V7_LOC), mload(Q1_EVAL_LOC), p), p)
batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V8_LOC), mload(Q2_EVAL_LOC), p), p)
batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V9_LOC), mload(Q3_EVAL_LOC), p), p)
batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V10_LOC), mload(Q4_EVAL_LOC), p), p)
batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V11_LOC), mload(QM_EVAL_LOC), p), p)
batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V12_LOC), mload(QC_EVAL_LOC), p), p)
batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V13_LOC), mload(QARITH_EVAL_LOC), p), p)
batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V14_LOC), mload(QSORT_EVAL_LOC), p), p)
batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V15_LOC), mload(QELLIPTIC_EVAL_LOC), p), p)
batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V16_LOC), mload(QAUX_EVAL_LOC), p), p)
batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V17_LOC), mload(SIGMA1_EVAL_LOC), p), p)
batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V18_LOC), mload(SIGMA2_EVAL_LOC), p), p)
batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V19_LOC), mload(SIGMA3_EVAL_LOC), p), p)
batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V20_LOC), mload(SIGMA4_EVAL_LOC), p), p)
/**
* batch_evaluation += v21 * (table1(zw) * u + table1(z))
* batch_evaluation += v22 * (table2(zw) * u + table2(z))
* batch_evaluation += v23 * (table3(zw) * u + table3(z))
* batch_evaluation += v24 * (table4(zw) * u + table4(z))
* batch_evaluation += v25 * table_type_eval
* batch_evaluation += v26 * id1_eval
* batch_evaluation += v27 * id2_eval
* batch_evaluation += v28 * id3_eval
* batch_evaluation += v29 * id4_eval
* batch_evaluation += quotient_eval
*/
batch_evaluation :=
addmod(
batch_evaluation,
mulmod(
mload(C_V21_LOC),
addmod(mulmod(mload(TABLE1_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(TABLE1_EVAL_LOC), p),
p
),
p
)
batch_evaluation :=
addmod(
batch_evaluation,
mulmod(
mload(C_V22_LOC),
addmod(mulmod(mload(TABLE2_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(TABLE2_EVAL_LOC), p),
p
),
p
)
batch_evaluation :=
addmod(
batch_evaluation,
mulmod(
mload(C_V23_LOC),
addmod(mulmod(mload(TABLE3_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(TABLE3_EVAL_LOC), p),
p
),
p
)
batch_evaluation :=
addmod(
batch_evaluation,
mulmod(
mload(C_V24_LOC),
addmod(mulmod(mload(TABLE4_OMEGA_EVAL_LOC), mload(C_U_LOC), p), mload(TABLE4_EVAL_LOC), p),
p
),
p
)
batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V25_LOC), mload(TABLE_TYPE_EVAL_LOC), p), p)
batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V26_LOC), mload(ID1_EVAL_LOC), p), p)
batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V27_LOC), mload(ID2_EVAL_LOC), p), p)
batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V28_LOC), mload(ID3_EVAL_LOC), p), p)
batch_evaluation := addmod(batch_evaluation, mulmod(mload(C_V29_LOC), mload(ID4_EVAL_LOC), p), p)
batch_evaluation := addmod(batch_evaluation, mload(QUOTIENT_EVAL_LOC), p)
mstore(0x00, 0x01) // [1].x
mstore(0x20, 0x02) // [1].y
mstore(0x40, sub(p, batch_evaluation))
// accumulator_2 = -[1].(batch_evaluation)
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
if iszero(success) {
mstore(0x0, OPENING_COMMITMENT_FAILED_SELECTOR)
revert(0x00, 0x04)
}
}
/**
* PERFORM PAIRING PREAMBLE
*/
{
let u := mload(C_U_LOC)
let zeta := mload(C_ZETA_LOC)
// VALIDATE PI_Z
{
let x := mload(PI_Z_X_LOC)
let y := mload(PI_Z_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
if iszero(eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) {
mstore(0x0, POINT_NOT_ON_CURVE_SELECTOR)
revert(0x00, 0x04)
}
mstore(0x00, x)
mstore(0x20, y)
}
// compute zeta.[PI_Z] and add into accumulator
mstore(0x40, zeta)
success := staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40)
// accumulator = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, ACCUMULATOR_X_LOC, 0x40))
// VALIDATE PI_Z_OMEGA
{
let x := mload(PI_Z_OMEGA_X_LOC)
let y := mload(PI_Z_OMEGA_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
if iszero(eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) {
mstore(0x0, POINT_NOT_ON_CURVE_SELECTOR)
revert(0x00, 0x04)
}
mstore(0x00, x)
mstore(0x20, y)
}
mstore(0x40, mulmod(mulmod(u, zeta, p), mload(OMEGA_LOC), p))
// accumulator_2 = u.zeta.omega.[PI_Z_OMEGA]
success := and(success, staticcall(gas(), 7, 0x00, 0x60, ACCUMULATOR2_X_LOC, 0x40))
// PAIRING_RHS = accumulator + accumulator_2
success := and(success, staticcall(gas(), 6, ACCUMULATOR_X_LOC, 0x80, PAIRING_RHS_X_LOC, 0x40))
mstore(0x00, mload(PI_Z_X_LOC))
mstore(0x20, mload(PI_Z_Y_LOC))
mstore(0x40, mload(PI_Z_OMEGA_X_LOC))
mstore(0x60, mload(PI_Z_OMEGA_Y_LOC))
mstore(0x80, u)
success := and(success, staticcall(gas(), 7, 0x40, 0x60, 0x40, 0x40))
// PAIRING_LHS = [PI_Z] + [PI_Z_OMEGA] * u
success := and(success, staticcall(gas(), 6, 0x00, 0x80, PAIRING_LHS_X_LOC, 0x40))
// negate lhs y-coordinate
mstore(PAIRING_LHS_Y_LOC, sub(q, mload(PAIRING_LHS_Y_LOC)))
if mload(CONTAINS_RECURSIVE_PROOF_LOC) {
// VALIDATE RECURSIVE P1
{
let x := mload(RECURSIVE_P1_X_LOC)
let y := mload(RECURSIVE_P1_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
if iszero(eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) {
mstore(0x0, POINT_NOT_ON_CURVE_SELECTOR)
revert(0x00, 0x04)
}
mstore(0x00, x)
mstore(0x20, y)
}
// compute u.u.[recursive_p1] and write into 0x60
mstore(0x40, mulmod(u, u, p))
success := and(success, staticcall(gas(), 7, 0x00, 0x60, 0x60, 0x40))
// VALIDATE RECURSIVE P2
{
let x := mload(RECURSIVE_P2_X_LOC)
let y := mload(RECURSIVE_P2_Y_LOC)
let xx := mulmod(x, x, q)
// validate on curve
if iszero(eq(mulmod(y, y, q), addmod(mulmod(x, xx, q), 3, q))) {
mstore(0x0, POINT_NOT_ON_CURVE_SELECTOR)
revert(0x00, 0x04)
}
mstore(0x00, x)
mstore(0x20, y)
}
// compute u.u.[recursive_p2] and write into 0x00
// 0x40 still contains u*u
success := and(success, staticcall(gas(), 7, 0x00, 0x60, 0x00, 0x40))
// compute u.u.[recursiveP1] + rhs and write into rhs
mstore(0xa0, mload(PAIRING_RHS_X_LOC))
mstore(0xc0, mload(PAIRING_RHS_Y_LOC))
success := and(success, staticcall(gas(), 6, 0x60, 0x80, PAIRING_RHS_X_LOC, 0x40))
// compute u.u.[recursiveP2] + lhs and write into lhs
mstore(0x40, mload(PAIRING_LHS_X_LOC))
mstore(0x60, mload(PAIRING_LHS_Y_LOC))
success := and(success, staticcall(gas(), 6, 0x00, 0x80, PAIRING_LHS_X_LOC, 0x40))
}
if iszero(success) {
mstore(0x0, PAIRING_PREAMBLE_FAILED_SELECTOR)
revert(0x00, 0x04)
}
}
/**
* PERFORM PAIRING
*/
{
// rhs paired with [1]_2
// lhs paired with [x]_2
mstore(0x00, mload(PAIRING_RHS_X_LOC))
mstore(0x20, mload(PAIRING_RHS_Y_LOC))
mstore(0x40, 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2) // this is [1]_2
mstore(0x60, 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed)
mstore(0x80, 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b)
mstore(0xa0, 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa)
mstore(0xc0, mload(PAIRING_LHS_X_LOC))
mstore(0xe0, mload(PAIRING_LHS_Y_LOC))
mstore(0x100, mload(G2X_X0_LOC))
mstore(0x120, mload(G2X_X1_LOC))
mstore(0x140, mload(G2X_Y0_LOC))
mstore(0x160, mload(G2X_Y1_LOC))
success := staticcall(gas(), 8, 0x00, 0x180, 0x00, 0x20)
if iszero(and(success, mload(0x00))) {
mstore(0x0, PAIRING_FAILED_SELECTOR)
revert(0x00, 0x04)
}
}
{
mstore(0x00, 0x01)
return(0x00, 0x20) // Proof succeeded!
}
}
}
}
contract UltraVerifier is BaseUltraVerifier {
function getVerificationKeyHash() public pure override(BaseUltraVerifier) returns (bytes32) {
return UltraVerificationKey.verificationKeyHash();
}
function loadVerificationKey(uint256 vk, uint256 _omegaInverseLoc) internal pure virtual override(BaseUltraVerifier) {
UltraVerificationKey.loadVerificationKey(vk, _omegaInverseLoc);
}
}
0x
\ No newline at end of file
use dep::std;
use dep::helpers;
use dep::helpers::hash;
use dep::helpers::structs;
fn main(
pi_contract_hash: pub Field,
accumulator: Field,
batch: structs::Batch,
tx_verifier: structs::VerifierTx,
recursion_verifier: structs::Verifier
) {
let tx_root_calc: Field = hash::hash_tree_four(batch.utxo_roots);
assert(batch.batch_oracle == helpers::ZERO_VALUE);
let batch_root_calc: Field = hash::hash([tx_root_calc, batch.batch_oracle]);
let new_root_calc: Field = hash::hash([batch_root_calc, batch.old_state_root]);
let new_path_calc: [Field; 20] = hash::compute_sibling_path(
batch.hist_tree.old_path,
batch.hist_tree.leaf,
batch.hist_tree.index
);
assert(batch.new_state_root == new_root_calc);
let calc_hist_root = hash::compute_merkle_root(
batch.hist_tree.leaf,
batch.hist_tree.index,
batch.hist_tree.old_path
);
assert(calc_hist_root == batch.hist_tree.root);
let hist_root_calc = hash::compute_merkle_root(
new_root_calc,
batch.hist_tree.index + 1,
new_path_calc
);
assert(hist_root_calc == batch.hist_tree.new_root);
let mut hash_validation: [u8; 832] = [0; 832];
for i in 0..32 {
hash_validation[i] = hash::field_to_u8(batch.new_state_root)[i];
hash_validation[i + 32] = hash::field_to_u8(batch.hist_tree.root)[i];
hash_validation[i + 64] = hash::field_to_u8(batch.hist_tree.new_root)[i];
hash_validation[i + 96] = hash::field_to_u8(accumulator)[i];
hash_validation[i + 128] = hash::field_to_u8(tx_verifier.key_hash)[i];
hash_validation[i + 160] = hash::field_to_u8(recursion_verifier.key_hash)[i];
for j in 0..20 {
hash_validation[i + 192 + (32 * j)] = hash::field_to_u8(new_path_calc[j])[i];
}
}
let hash_generated: Field = hash::hash_to_field(std::hash::keccak256(hash_validation, hash_validation.len() as u32));
assert(pi_contract_hash == hash_generated);
std::verify_proof(
recursion_verifier.verification_key.as_slice(),
recursion_verifier.proof.as_slice(),
[accumulator].as_slice(),
recursion_verifier.key_hash
)
}
\ No newline at end of file
[package]
name = "recursion"
type = "bin"
authors = [""]
compiler_version = ">=0.19.2"
[dependencies]
helpers = { path = "../helpers" }
use dep::std;
use dep::helpers;
use dep::helpers::structs;
use dep::helpers::hash;
#[recursive]
fn main(
accumulator: pub Field,
tx_verifier: structs::VerifierTx,
recursion_verifier: structs::Verifier,
previous_accumulator: Field,
tx: structs::PublicInputs
) {
let tx_as_keccak = tx.as_keccak();
let mut generated_accumulator_preimage: [u8; 128] = [0; 128];
for i in 0..32 {
generated_accumulator_preimage[i] = hash::field_to_u8(previous_accumulator)[i];
generated_accumulator_preimage[i + 32] = hash::field_to_u8(tx_as_keccak)[i];
generated_accumulator_preimage[i + 64] = hash::field_to_u8(tx_verifier.key_hash)[i];
generated_accumulator_preimage[i + 96] = hash::field_to_u8(recursion_verifier.key_hash)[i];
}
let generated_accumulator = hash::hash_to_field(std::hash::keccak256(generated_accumulator_preimage, generated_accumulator_preimage.len() as u32));
assert(accumulator == generated_accumulator);
let mut recursion_pi: Field = previous_accumulator;
let mut recursive_proof: [Field] = recursion_verifier.proof.as_slice();
if (previous_accumulator == helpers::ZERO_VALUE) {
assert(recursion_verifier.key_hash == 0x083764da4a71646a7c2d27cf8f17adc4f9e4f3d2c5a574b643f79864c280b4ce);
recursion_pi = tx.as_hash();
}
std::verify_proof(
recursion_verifier.verification_key.as_slice(),
recursive_proof,
[recursion_pi].as_slice(),
recursion_verifier.key_hash
);
std::verify_proof(
tx_verifier.verification_key.as_slice(),
tx_verifier.proof.as_slice(),
[tx.as_hash()].as_slice(),
tx_verifier.key_hash
);
}
\ No newline at end of file
File added
File added
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment