From 5b4ba05b6576011abc9ec503b4aa1b54e11f2250 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 17 Feb 2026 16:25:20 +0100 Subject: [PATCH] fix q_mod_t and add q_mod_t_centered --- circuits/benchmarks/config.json | 1 + .../benchmarks/results_insecure/report.md | 158 +- circuits/benchmarks/results_secure/report.md | 32 + .../scripts/generate_prover_toml.sh | 13 + .../benchmarks/scripts/generate_report.sh | 64 +- circuits/benchmarks/scripts/run_benchmarks.sh | 5 + circuits/bin/config/src/main.nr | 39 +- circuits/lib/src/configs/insecure/dkg.nr | 1 + .../lib/src/configs/insecure/threshold.nr | 3 +- circuits/lib/src/configs/secure/dkg.nr | 1 + circuits/lib/src/configs/secure/threshold.nr | 3 +- .../circuits/dkg/share_encryption/codegen.rs | 2 + .../dkg/share_encryption/computation.rs | 11 +- .../decrypted_shares_aggregation/codegen.rs | 2 + .../computation.rs | 8 +- examples/CRISP/circuits/src/main.nr | 4 +- examples/CRISP/circuits/src/utils.nr | 32 +- .../contracts/CRISPVerifier.sol | 4299 ++++++++--------- 18 files changed, 2415 insertions(+), 2263 deletions(-) diff --git a/circuits/benchmarks/config.json b/circuits/benchmarks/config.json index 51da1adbb4..acb165f37e 100644 --- a/circuits/benchmarks/config.json +++ b/circuits/benchmarks/config.json @@ -1,5 +1,6 @@ { "circuits": [ + "config", "dkg/pk", "dkg/sk_share_computation", "dkg/e_sm_share_computation", diff --git a/circuits/benchmarks/results_insecure/report.md b/circuits/benchmarks/results_insecure/report.md index a0b18a30cb..d9ee9cf18d 100644 --- a/circuits/benchmarks/results_insecure/report.md +++ b/circuits/benchmarks/results_insecure/report.md @@ -1,9 +1,9 @@ # Enclave ZK Circuit Benchmarks -**Generated:** 2026-02-13 12:59:43 UTC +**Generated:** 2026-02-17 15:08:16 UTC -**Git Branch:** `ref/circuits-comp` -**Git Commit:** `2403461592f5c628cb22b493586995f79bb698e1` +**Git Branch:** `configs/fixqmt` +**Git Commit:** `689e56cb90251b34e67af87cb7abfed03bedcd1c` --- @@ -15,21 +15,21 @@ | Circuit | Compile | Execute | Prove | Verify | | ---------------------- | ------- | ------- | ------ | ------ | -| e_sm_share_computation | 0.31 s | 0.52 s | 1.55 s | 0.02 s | -| pk | 0.25 s | 0.27 s | 0.12 s | 0.03 s | -| share_decryption | 0.25 s | 0.29 s | 0.23 s | 0.02 s | -| share_encryption | 0.29 s | 0.44 s | 0.61 s | 0.03 s | -| sk_share_computation | 0.30 s | 0.51 s | 1.54 s | 0.03 s | +| e_sm_share_computation | 3.29 s | 0.54 s | 1.63 s | 0.03 s | +| pk | 0.33 s | 0.26 s | 0.12 s | 0.02 s | +| share_decryption | 0.71 s | 0.29 s | 0.23 s | 0.02 s | +| share_encryption | 1.99 s | 0.43 s | 0.62 s | 0.03 s | +| sk_share_computation | 3.18 s | 0.51 s | 1.62 s | 0.02 s | #### Size & Circuit Metrics | Circuit | Opcodes | Gates | Circuit Size | Witness | VK Size | Proof Size | | ---------------------- | ------- | ------- | ------------ | --------- | ------- | ---------- | -| e_sm_share_computation | 90956 | 328.74K | 1.39 MB | 477.72 KB | 3.59 KB | 15.88 KB | -| pk | 344 | 6.85K | 87.84 KB | 29.07 KB | 3.59 KB | 15.88 KB | -| share_decryption | 3093 | 28.72K | 158.27 KB | 148.91 KB | 3.59 KB | 15.88 KB | -| share_encryption | 47758 | 127.69K | 798.14 KB | 512.11 KB | 3.59 KB | 15.88 KB | -| sk_share_computation | 90827 | 326.14K | 1.38 MB | 463.68 KB | 3.59 KB | 15.88 KB | +| e_sm_share_computation | 90956 | 328.74K | 1.39 MB | 477.88 KB | 3.59 KB | 15.88 KB | +| pk | 344 | 6.85K | 87.84 KB | 29.08 KB | 3.59 KB | 15.88 KB | +| share_decryption | 3093 | 28.72K | 158.27 KB | 148.90 KB | 3.59 KB | 15.88 KB | +| share_encryption | 47758 | 127.69K | 798.14 KB | 512.29 KB | 3.59 KB | 15.88 KB | +| sk_share_computation | 90827 | 326.14K | 1.38 MB | 463.75 KB | 3.59 KB | 15.88 KB | ### Threshold @@ -37,21 +37,33 @@ | Circuit | Compile | Execute | Prove | Verify | | -------------------------------- | ------- | ------- | ------ | ------ | -| decrypted_shares_aggregation_mod | 0.27 s | 0.33 s | 0.46 s | 0.02 s | -| pk_aggregation | 0.29 s | 0.45 s | 0.87 s | 0.02 s | -| pk_generation | 0.28 s | 0.38 s | 0.48 s | 0.03 s | -| share_decryption | 0.28 s | 0.40 s | 0.53 s | 0.03 s | -| user_data_encryption | 0.30 s | 0.47 s | 0.58 s | 0.02 s | +| decrypted_shares_aggregation_mod | 0.54 s | 0.32 s | 0.47 s | 0.03 s | +| pk_aggregation | 1.45 s | 0.43 s | 0.88 s | 0.02 s | +| pk_generation | 1.24 s | 0.37 s | 0.50 s | 0.03 s | +| share_decryption | 1.18 s | 0.37 s | 0.52 s | 0.03 s | +| user_data_encryption | 2.01 s | 0.47 s | 0.61 s | 0.02 s | #### Size & Circuit Metrics | Circuit | Opcodes | Gates | Circuit Size | Witness | VK Size | Proof Size | | -------------------------------- | ------- | ------- | ------------ | --------- | ------- | ---------- | -| decrypted_shares_aggregation_mod | 31544 | 80.74K | 509.84 KB | 77.56 KB | 3.59 KB | 15.88 KB | -| pk_aggregation | 47817 | 169.89K | 884.11 KB | 360.86 KB | 3.59 KB | 15.88 KB | -| pk_generation | 30019 | 65.61K | 542.16 KB | 447.05 KB | 3.59 KB | 15.88 KB | -| share_decryption | 30570 | 85.48K | 541.56 KB | 522.86 KB | 3.59 KB | 15.88 KB | -| user_data_encryption | 56601 | 106.72K | 847.64 KB | 691.13 KB | 3.59 KB | 15.88 KB | +| decrypted_shares_aggregation_mod | 31544 | 80.74K | 509.84 KB | 77.50 KB | 3.59 KB | 15.88 KB | +| pk_aggregation | 47817 | 169.89K | 884.11 KB | 360.70 KB | 3.59 KB | 15.88 KB | +| pk_generation | 30019 | 65.61K | 542.16 KB | 446.98 KB | 3.59 KB | 15.88 KB | +| share_decryption | 22378 | 74.21K | 460.26 KB | 494.32 KB | 3.59 KB | 15.88 KB | +| user_data_encryption | 56601 | 106.72K | 847.53 KB | 691.33 KB | 3.59 KB | 15.88 KB | + +### Config + +#### Timing Metrics + +| Circuit | Compile | Execute | Prove | Verify | +| ------- | ------- | ------- | ----- | ------ | + +#### Size & Circuit Metrics + +| Circuit | Opcodes | Gates | Circuit Size | Witness | VK Size | Proof Size | +| ------- | ------- | ----- | ------------ | ------- | ------- | ---------- | ## Circuit Details @@ -61,15 +73,15 @@ | Metric | Value | | -------------------- | --------- | -| **Compilation** | 0.31 s | -| **Execution** | 0.52 s | -| **VK Generation** | 0.59 s | -| **Proof Generation** | 1.55 s | -| **Verification** | 0.02 s | +| **Compilation** | 3.29 s | +| **Execution** | 0.54 s | +| **VK Generation** | 0.64 s | +| **Proof Generation** | 1.63 s | +| **Verification** | 0.03 s | | **ACIR Opcodes** | "90956" | | **Total Gates** | "328743" | | **Circuit Size** | 1.39 MB | -| **Witness Size** | 477.72 KB | +| **Witness Size** | 477.88 KB | | **VK Size** | 3.59 KB | | **Proof Size** | 15.88 KB | @@ -77,15 +89,15 @@ | Metric | Value | | -------------------- | -------- | -| **Compilation** | 0.25 s | -| **Execution** | 0.27 s | +| **Compilation** | 0.33 s | +| **Execution** | 0.26 s | | **VK Generation** | 0.05 s | | **Proof Generation** | 0.12 s | -| **Verification** | 0.03 s | +| **Verification** | 0.02 s | | **ACIR Opcodes** | "344" | | **Total Gates** | "6846" | | **Circuit Size** | 87.84 KB | -| **Witness Size** | 29.07 KB | +| **Witness Size** | 29.08 KB | | **VK Size** | 3.59 KB | | **Proof Size** | 15.88 KB | @@ -93,7 +105,7 @@ | Metric | Value | | -------------------- | --------- | -| **Compilation** | 0.25 s | +| **Compilation** | 0.71 s | | **Execution** | 0.29 s | | **VK Generation** | 0.09 s | | **Proof Generation** | 0.23 s | @@ -101,7 +113,7 @@ | **ACIR Opcodes** | "3093" | | **Total Gates** | "28720" | | **Circuit Size** | 158.27 KB | -| **Witness Size** | 148.91 KB | +| **Witness Size** | 148.90 KB | | **VK Size** | 3.59 KB | | **Proof Size** | 15.88 KB | @@ -109,15 +121,15 @@ | Metric | Value | | -------------------- | --------- | -| **Compilation** | 0.29 s | -| **Execution** | 0.44 s | +| **Compilation** | 1.99 s | +| **Execution** | 0.43 s | | **VK Generation** | 0.26 s | -| **Proof Generation** | 0.61 s | +| **Proof Generation** | 0.62 s | | **Verification** | 0.03 s | | **ACIR Opcodes** | "47758" | | **Total Gates** | "127691" | | **Circuit Size** | 798.14 KB | -| **Witness Size** | 512.11 KB | +| **Witness Size** | 512.29 KB | | **VK Size** | 3.59 KB | | **Proof Size** | 15.88 KB | @@ -125,15 +137,15 @@ | Metric | Value | | -------------------- | --------- | -| **Compilation** | 0.30 s | +| **Compilation** | 3.18 s | | **Execution** | 0.51 s | -| **VK Generation** | 0.66 s | -| **Proof Generation** | 1.54 s | -| **Verification** | 0.03 s | +| **VK Generation** | 0.60 s | +| **Proof Generation** | 1.62 s | +| **Verification** | 0.02 s | | **ACIR Opcodes** | "90827" | | **Total Gates** | "326138" | | **Circuit Size** | 1.38 MB | -| **Witness Size** | 463.68 KB | +| **Witness Size** | 463.75 KB | | **VK Size** | 3.59 KB | | **Proof Size** | 15.88 KB | @@ -143,15 +155,15 @@ | Metric | Value | | -------------------- | --------- | -| **Compilation** | 0.27 s | -| **Execution** | 0.33 s | -| **VK Generation** | 0.18 s | -| **Proof Generation** | 0.46 s | -| **Verification** | 0.02 s | +| **Compilation** | 0.54 s | +| **Execution** | 0.32 s | +| **VK Generation** | 0.19 s | +| **Proof Generation** | 0.47 s | +| **Verification** | 0.03 s | | **ACIR Opcodes** | "31544" | | **Total Gates** | "80740" | | **Circuit Size** | 509.84 KB | -| **Witness Size** | 77.56 KB | +| **Witness Size** | 77.50 KB | | **VK Size** | 3.59 KB | | **Proof Size** | 15.88 KB | @@ -159,15 +171,15 @@ | Metric | Value | | -------------------- | --------- | -| **Compilation** | 0.29 s | -| **Execution** | 0.45 s | +| **Compilation** | 1.45 s | +| **Execution** | 0.43 s | | **VK Generation** | 0.35 s | -| **Proof Generation** | 0.87 s | +| **Proof Generation** | 0.88 s | | **Verification** | 0.02 s | | **ACIR Opcodes** | "47817" | | **Total Gates** | "169890" | | **Circuit Size** | 884.11 KB | -| **Witness Size** | 360.86 KB | +| **Witness Size** | 360.70 KB | | **VK Size** | 3.59 KB | | **Proof Size** | 15.88 KB | @@ -175,15 +187,15 @@ | Metric | Value | | -------------------- | --------- | -| **Compilation** | 0.28 s | -| **Execution** | 0.38 s | -| **VK Generation** | 0.16 s | -| **Proof Generation** | 0.48 s | +| **Compilation** | 1.24 s | +| **Execution** | 0.37 s | +| **VK Generation** | 0.17 s | +| **Proof Generation** | 0.50 s | | **Verification** | 0.03 s | | **ACIR Opcodes** | "30019" | | **Total Gates** | "65606" | | **Circuit Size** | 542.16 KB | -| **Witness Size** | 447.05 KB | +| **Witness Size** | 446.98 KB | | **VK Size** | 3.59 KB | | **Proof Size** | 15.88 KB | @@ -191,15 +203,15 @@ | Metric | Value | | -------------------- | --------- | -| **Compilation** | 0.28 s | -| **Execution** | 0.40 s | -| **VK Generation** | 0.19 s | -| **Proof Generation** | 0.53 s | +| **Compilation** | 1.18 s | +| **Execution** | 0.37 s | +| **VK Generation** | 0.17 s | +| **Proof Generation** | 0.52 s | | **Verification** | 0.03 s | -| **ACIR Opcodes** | "30570" | -| **Total Gates** | "85478" | -| **Circuit Size** | 541.56 KB | -| **Witness Size** | 522.86 KB | +| **ACIR Opcodes** | "22378" | +| **Total Gates** | "74214" | +| **Circuit Size** | 460.26 KB | +| **Witness Size** | 494.32 KB | | **VK Size** | 3.59 KB | | **Proof Size** | 15.88 KB | @@ -207,18 +219,20 @@ | Metric | Value | | -------------------- | --------- | -| **Compilation** | 0.30 s | +| **Compilation** | 2.01 s | | **Execution** | 0.47 s | -| **VK Generation** | 0.23 s | -| **Proof Generation** | 0.58 s | +| **VK Generation** | 0.24 s | +| **Proof Generation** | 0.61 s | | **Verification** | 0.02 s | | **ACIR Opcodes** | "56601" | | **Total Gates** | "106725" | -| **Circuit Size** | 847.64 KB | -| **Witness Size** | 691.13 KB | +| **Circuit Size** | 847.53 KB | +| **Witness Size** | 691.33 KB | | **VK Size** | 3.59 KB | | **Proof Size** | 15.88 KB | +### Config + ## System Information ### Hardware diff --git a/circuits/benchmarks/results_secure/report.md b/circuits/benchmarks/results_secure/report.md index d2431eee22..1e73073390 100644 --- a/circuits/benchmarks/results_secure/report.md +++ b/circuits/benchmarks/results_secure/report.md @@ -53,6 +53,20 @@ | share_decryption | 1012104 | 3.54M | 12.98 MB | 19.20 MB | 3.59 KB | 15.88 KB | | user_data_encryption | 1684299 | 4.02M | 20.75 MB | 23.82 MB | 3.59 KB | 15.88 KB | +### Config + +#### Timing Metrics + +| Circuit | Compile | Execute | Prove | Verify | +| ----------------------- | ------- | ------- | ------ | ------ | +| validate_secure_configs | 0.26 s | 0.26 s | 0.05 s | 0.02 s | + +#### Size & Circuit Metrics + +| Circuit | Opcodes | Gates | Circuit Size | Witness | VK Size | Proof Size | +| ----------------------- | ------- | ----- | ------------ | ------- | ------- | ---------- | +| validate_secure_configs | 0 | 54 | 568 B | 23 B | 3.59 KB | 15.88 KB | + ## Circuit Details ### DKG @@ -219,6 +233,24 @@ | **VK Size** | 3.59 KB | | **Proof Size** | 15.88 KB | +### Config + +#### validate_secure_configs + +| Metric | Value | +| -------------------- | -------- | +| **Compilation** | 0.26 s | +| **Execution** | 0.26 s | +| **VK Generation** | 0.03 s | +| **Proof Generation** | 0.05 s | +| **Verification** | 0.02 s | +| **ACIR Opcodes** | "0" | +| **Total Gates** | "54" | +| **Circuit Size** | 568 B | +| **Witness Size** | 23 B | +| **VK Size** | 3.59 KB | +| **Proof Size** | 15.88 KB | + ## System Information ### Hardware diff --git a/circuits/benchmarks/scripts/generate_prover_toml.sh b/circuits/benchmarks/scripts/generate_prover_toml.sh index 7e90ea5ca9..72c1d5b263 100755 --- a/circuits/benchmarks/scripts/generate_prover_toml.sh +++ b/circuits/benchmarks/scripts/generate_prover_toml.sh @@ -32,9 +32,14 @@ OUTPUT_DIR="${REPO_ROOT}/circuits/bin/${CIRCUIT_PATH}" # Map circuit path to zk_cli --circuit and optional --inputs # DKG circuits that need --inputs: share-computation, share-encryption, share-decryption +# config has no witness inputs (verifies constants only), so skip zk_cli get_zk_args() { local path="$1" case "$path" in + config) + echo "_no_zk_cli" + return + ;; dkg/pk) echo "pk" return @@ -88,6 +93,14 @@ ZK_INPUTS="${ZK_ARGS[1]:-}" cd "$REPO_ROOT" +if [ "$ZK_CIRCUIT" = "_no_zk_cli" ]; then + echo " No Prover.toml needed (config circuit has no witness inputs)" + # Ensure empty Prover.toml so nargo execute can run + mkdir -p "$OUTPUT_DIR" + touch "$OUTPUT_DIR/Prover.toml" + exit 0 +fi + CMD=(cargo run -p e3-zk-helpers --bin zk_cli -- --circuit "$ZK_CIRCUIT" --preset "$PRESET" --output "$OUTPUT_DIR" --toml --no-configs) if [ -n "$ZK_INPUTS" ]; then CMD+=(--inputs "$ZK_INPUTS") diff --git a/circuits/benchmarks/scripts/generate_report.sh b/circuits/benchmarks/scripts/generate_report.sh index 93d5a333e1..9081129ebf 100755 --- a/circuits/benchmarks/scripts/generate_report.sh +++ b/circuits/benchmarks/scripts/generate_report.sh @@ -78,7 +78,7 @@ format_gates() { } -# Helper: return "dkg" or "threshold" from circuit_path in JSON +# Helper: return "dkg", "threshold", or "config" from circuit_path in JSON category_of() { local path path=$(jq -r '.circuit_path' "$1") @@ -86,6 +86,8 @@ category_of() { echo "dkg" elif [[ "$path" == *"/threshold/"* ]]; then echo "threshold" + elif [[ "$path" == *"config"* ]]; then + echo "config" else echo "other" fi @@ -206,15 +208,69 @@ for json_file in "$INPUT_DIR"/*.json; do echo "| $circuit | $opcodes | $gates_fmt | $circuit_size_fmt | $witness_size_fmt | $vk_size_fmt | $proof_size_fmt |" >> "$OUTPUT_FILE" done -# Detailed metrics by circuit, grouped by DKG / Threshold +# Config (validate_secure_configs) - only present in secure mode +cat >> "$OUTPUT_FILE" << EOF + +### Config + +#### Timing Metrics + +| Circuit | Compile | Execute | Prove | Verify | +|---------|---------|---------|-------|--------| +EOF + +for json_file in "$INPUT_DIR"/*.json; do + [ -f "$json_file" ] || continue + [ "$(category_of "$json_file")" = "config" ] || continue + circuit=$(jq -r '.circuit_name' "$json_file") + compile_time=$(jq -r '.compilation.time_seconds' "$json_file") + execute_time=$(jq -r '.execution.time_seconds' "$json_file") + prove_time=$(jq -r '.proof_generation.time_seconds' "$json_file") + verify_time=$(jq -r '.verification.time_seconds' "$json_file") + compile_fmt=$(format_time "$compile_time") + execute_fmt=$(format_time "$execute_time") + prove_fmt=$(format_time "$prove_time") + verify_fmt=$(format_time "$verify_time") + echo "| $circuit | $compile_fmt | $execute_fmt | $prove_fmt | $verify_fmt |" >> "$OUTPUT_FILE" +done + +cat >> "$OUTPUT_FILE" << EOF + +#### Size & Circuit Metrics + +| Circuit | Opcodes | Gates | Circuit Size | Witness | VK Size | Proof Size | +|---------|---------|-------|--------------|---------|---------|------------| +EOF + +for json_file in "$INPUT_DIR"/*.json; do + [ -f "$json_file" ] || continue + [ "$(category_of "$json_file")" = "config" ] || continue + circuit=$(jq -r '.circuit_name' "$json_file") + opcodes=$(jq -r '.gates.acir_opcodes // 0' "$json_file") + gates=$(jq -r '.gates.total_gates' "$json_file") + circuit_size=$(jq -r '.compilation.circuit_size_bytes' "$json_file") + witness_size=$(jq -r '.execution.witness_size_bytes' "$json_file") + vk_size=$(jq -r '.vk_generation.vk_size_bytes' "$json_file") + proof_size=$(jq -r '.proof_generation.proof_size_bytes' "$json_file") + gates_fmt=$(format_gates "$gates") + circuit_size_fmt=$(format_bytes "$circuit_size") + witness_size_fmt=$(format_bytes "$witness_size") + vk_size_fmt=$(format_bytes "$vk_size") + proof_size_fmt=$(format_bytes "$proof_size") + echo "| $circuit | $opcodes | $gates_fmt | $circuit_size_fmt | $witness_size_fmt | $vk_size_fmt | $proof_size_fmt |" >> "$OUTPUT_FILE" +done + +# Detailed metrics by circuit, grouped by DKG / Threshold / Config cat >> "$OUTPUT_FILE" << EOF ## Circuit Details EOF -for category in dkg threshold; do - title="DKG"; [ "$category" = "threshold" ] && title="Threshold" +for category in dkg threshold config; do + title="DKG" + [ "$category" = "threshold" ] && title="Threshold" + [ "$category" = "config" ] && title="Config" echo "### $title" >> "$OUTPUT_FILE" echo "" >> "$OUTPUT_FILE" circuits=$(for json_file in "$INPUT_DIR"/*.json; do diff --git a/circuits/benchmarks/scripts/run_benchmarks.sh b/circuits/benchmarks/scripts/run_benchmarks.sh index cd53bcc2ad..ad212175bd 100755 --- a/circuits/benchmarks/scripts/run_benchmarks.sh +++ b/circuits/benchmarks/scripts/run_benchmarks.sh @@ -130,6 +130,7 @@ echo " Output Directory: ${OUTPUT_DIR}" echo "" # decrypted_shares_aggregation_mod is for insecure only (Q < 128bit); _bn is for secure (large Q) +# config validates secure configs only, so run only in secure mode RUN_CIRCUITS="" for c in $CIRCUITS; do if [ "$MODE" = "secure" ] && [ "$c" = "threshold/decrypted_shares_aggregation_mod" ]; then @@ -140,6 +141,10 @@ for c in $CIRCUITS; do echo " Skipping $c (BigNum variant is for secure/large Q only)" continue fi + if [ "$MODE" = "insecure" ] && [ "$c" = "config" ]; then + echo " Skipping $c (config circuit validates secure configs only)" + continue + fi RUN_CIRCUITS="${RUN_CIRCUITS} ${c}" done RUN_CIRCUITS=$(echo "$RUN_CIRCUITS" | xargs) diff --git a/circuits/bin/config/src/main.nr b/circuits/bin/config/src/main.nr index 537dd8c928..dc8af3eac8 100644 --- a/circuits/bin/config/src/main.nr +++ b/circuits/bin/config/src/main.nr @@ -14,6 +14,7 @@ use lib::configs::secure::dkg::{ QIS as DKG_QIS, // DKG derived values PLAINTEXT_MODULUS as DKG_PLAINTEXT_MODULUS, Q_MOD_T as DKG_Q_MOD_T, + Q_MOD_T_CENTERED as DKG_Q_MOD_T_CENTERED, // Threshold moduli reference L_THRESHOLD, QIS_THRESHOLD, // Share encryption bounds @@ -31,7 +32,7 @@ use lib::configs::secure::threshold::{ QIS as THRESHOLD_QIS, // Derived values PLAINTEXT_MODULUS as THRESHOLD_PLAINTEXT_MODULUS, Q_MOD_T as THRESHOLD_Q_MOD_T, - Q_INVERSE_MOD_T as THRESHOLD_Q_INVERSE_MOD_T, + Q_MOD_T_CENTERED as THRESHOLD_Q_MOD_T_CENTERED, Q_INVERSE_MOD_T as THRESHOLD_Q_INVERSE_MOD_T, // pk_generation bounds PK_GENERATION_EEK_BOUND, PK_GENERATION_SK_BOUND, PK_GENERATION_E_SM_BOUND, PK_GENERATION_R1_BOUNDS, PK_GENERATION_R2_BOUNDS, PK_GENERATION_B_ENC, @@ -47,6 +48,24 @@ use lib::configs::secure::threshold::{ use lib::math::modulo::U128::ModU128; +/// Center a value from [0, modulus) to [-modulus/2, modulus/2). +/// For odd modulus use (raw > t/2). +/// For even use (raw >= t/2). +/// Precondition: raw is in [0, modulus). +pub fn center(raw: Field, modulus: Field) -> Field { + let needs_centering = if (modulus as u128 % 2) == 1 { + raw as u128 > modulus as u128 / 2 + } else { + raw as u128 >= modulus as u128 / 2 + }; + + if needs_centering { + raw - modulus + } else { + raw + } +} + fn main() { // DKG (BFV) Verification verify_dkg_derived_values(); @@ -67,6 +86,7 @@ fn main() { fn verify_dkg_derived_values() { verify_dkg_q_mod_t(); + verify_dkg_q_mod_t_centered(); } // Verifies DKG: Q_MOD_T = (product of QIS) mod t @@ -82,6 +102,14 @@ fn verify_dkg_q_mod_t() { assert(product == DKG_Q_MOD_T, "DKG Q_MOD_T verification failed"); } +// Verifies DKG: Q_MOD_T_CENTERED = center(Q_MOD_T) +fn verify_dkg_q_mod_t_centered() { + assert( + center(DKG_Q_MOD_T, DKG_PLAINTEXT_MODULUS) == DKG_Q_MOD_T_CENTERED, + "DKG Q_MOD_T_CENTERED verification failed", + ); +} + //DKG Bounds // Verifies share_encryption bounds (Circuit 3) @@ -174,6 +202,7 @@ fn verify_dkg_bounds() { fn verify_threshold_derived_values() { verify_threshold_q_mod_t(); + verify_threshold_q_mod_t_centered(); verify_threshold_q_inverse_mod_t(); } @@ -190,6 +219,14 @@ fn verify_threshold_q_mod_t() { assert(product == THRESHOLD_Q_MOD_T, "Threshold Q_MOD_T verification failed"); } +// Verifies Threshold: Q_MOD_T_CENTERED = center(Q_MOD_T) +fn verify_threshold_q_mod_t_centered() { + assert( + center(THRESHOLD_Q_MOD_T, THRESHOLD_PLAINTEXT_MODULUS) == THRESHOLD_Q_MOD_T_CENTERED, + "Threshold Q_MOD_T_CENTERED verification failed", + ); +} + // Verifies Threshold: Q * Q_INVERSE_MOD_T = 1 mod t fn verify_threshold_q_inverse_mod_t() { let t = THRESHOLD_PLAINTEXT_MODULUS; diff --git a/circuits/lib/src/configs/insecure/dkg.nr b/circuits/lib/src/configs/insecure/dkg.nr index 774ff59618..d4a5cf8d7e 100644 --- a/circuits/lib/src/configs/insecure/dkg.nr +++ b/circuits/lib/src/configs/insecure/dkg.nr @@ -15,6 +15,7 @@ pub global L: u32 = 1; pub global QIS: [Field; L] = [2251799813554177]; pub global PLAINTEXT_MODULUS: Field = 68719403009; pub global Q_MOD_T: Field = 2415755265; +pub global Q_MOD_T_CENTERED: Field = 2415755265; /************************************ ------------------------------------- diff --git a/circuits/lib/src/configs/insecure/threshold.nr b/circuits/lib/src/configs/insecure/threshold.nr index 55e488edcf..b6f5f222d9 100644 --- a/circuits/lib/src/configs/insecure/threshold.nr +++ b/circuits/lib/src/configs/insecure/threshold.nr @@ -14,8 +14,9 @@ use crate::core::threshold::user_data_encryption::Configs as UserDataEncryptionC pub global N: u32 = 512; pub global L: u32 = 2; pub global QIS: [Field; L] = [68719403009, 68719230977]; -pub global Q_MOD_T: Field = -7; pub global PLAINTEXT_MODULUS: Field = 100; +pub global Q_MOD_T: Field = 93; +pub global Q_MOD_T_CENTERED: Field = -7; pub global Q_INVERSE_MOD_T: Field = 57; /************************************ diff --git a/circuits/lib/src/configs/secure/dkg.nr b/circuits/lib/src/configs/secure/dkg.nr index ac7bb418e6..84182954ef 100644 --- a/circuits/lib/src/configs/secure/dkg.nr +++ b/circuits/lib/src/configs/secure/dkg.nr @@ -15,6 +15,7 @@ pub global L: u32 = 2; pub global QIS: [Field; L] = [72057594082099201, 72057594062438401]; pub global PLAINTEXT_MODULUS: Field = 18014398509481984; pub global Q_MOD_T: Field = 1082658244788225; +pub global Q_MOD_T_CENTERED: Field = 1082658244788225; /************************************ ------------------------------------- diff --git a/circuits/lib/src/configs/secure/threshold.nr b/circuits/lib/src/configs/secure/threshold.nr index 6c6e153b4e..8e24089fd8 100644 --- a/circuits/lib/src/configs/secure/threshold.nr +++ b/circuits/lib/src/configs/secure/threshold.nr @@ -21,8 +21,9 @@ pub global N: u32 = 8192; pub global L: u32 = 4; pub global QIS: [Field; L] = [2251799822204929, 4503599627763713, 4503599631433729, 4503599634579457]; -pub global Q_MOD_T: Field = -19; pub global PLAINTEXT_MODULUS: Field = 100; +pub global Q_MOD_T: Field = 81; +pub global Q_MOD_T_CENTERED: Field = -19; pub global Q_INVERSE_MOD_T: Field = 21; /************************************ diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs index fab0083097..c9bce098e8 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs @@ -67,6 +67,7 @@ pub global L: u32 = {}; pub global QIS: [Field; L] = [{}]; pub global PLAINTEXT_MODULUS: Field = {}; pub global Q_MOD_T: Field = {}; +pub global Q_MOD_T_CENTERED: Field = {}; /************************************ ------------------------------------- @@ -120,6 +121,7 @@ pub global {}_CONFIGS: ShareEncryptionConfigs = ShareEncryptionConfigs::new( qis_str, configs.t, configs.q_mod_t, + configs.q_mod_t_centered, prefix, configs.bits.pk_bit, prefix, diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs index b49ad195ba..84ba0a0678 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs @@ -13,6 +13,7 @@ use crate::circuits::commitments::{ compute_dkg_pk_commitment, compute_share_encryption_commitment_from_message, }; +use crate::{compute_q_mod_t, compute_q_product}; use std::ops::Deref; use crate::dkg::share_encryption::ShareEncryptionCircuit; @@ -74,8 +75,10 @@ impl CircuitComputation for ShareEncryptionCircuit { pub struct Configs { /// Plaintext modulus (as usize). pub t: usize, + /// [q]_t reduced to ZKP field modulus. + pub q_mod_t: BigUint, /// centered [q]_t reduced to ZKP field modulus. - pub q_mod_t: BigInt, + pub q_mod_t_centered: BigInt, /// CRT moduli (one per limb). pub moduli: Vec, /// k0_i = [1/q_i]_t per modulus, for scaling in the circuit. @@ -150,8 +153,9 @@ impl Computation for Configs { let moduli = dkg_params.moduli().to_vec(); let t = dkg_params.plaintext(); - let q_mod_t = compute_q_mod_t_centered(&moduli, t); - + let q = compute_q_product(&moduli); + let q_mod_t = compute_q_mod_t(&q, t); + let q_mod_t_centered = compute_q_mod_t_centered(&moduli, t); let k0is = compute_k0is(&moduli, t)?; let bounds = Bounds::compute(preset, data)?; @@ -160,6 +164,7 @@ impl Computation for Configs { Ok(Configs { t: t as usize, q_mod_t, + q_mod_t_centered, moduli, k0is, bits, diff --git a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/codegen.rs b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/codegen.rs index b886f418b8..dc390eb0a6 100644 --- a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/codegen.rs +++ b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/codegen.rs @@ -62,6 +62,7 @@ pub global L: u32 = {}; pub global QIS: [Field; L] = [{}]; pub global PLAINTEXT_MODULUS: Field = {}; pub global Q_MOD_T: Field = {}; +pub global Q_MOD_T_CENTERED: Field = {}; pub global Q_INVERSE_MOD_T: Field = {}; /************************************ @@ -79,6 +80,7 @@ pub global {}_CONFIGS: DecryptedSharesAggregationConfigs = qis_str, configs.plaintext_modulus, configs.q_mod_t, + configs.q_mod_t_centered, configs.q_inverse_mod_t, prefix, configs.bits.noise_bit, diff --git a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs index 35dc1d1dfa..b35a634449 100644 --- a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs @@ -12,6 +12,7 @@ //! [0, zkp_modulus) with [`e3_polynomial::reduce`] inside [`Inputs::compute`]. use crate::calculate_bit_width; +use crate::compute_q_mod_t; use crate::compute_q_mod_t_centered; use crate::get_zkp_modulus; use crate::threshold::decrypted_shares_aggregation::circuit::DecryptedSharesAggregationCircuit; @@ -74,7 +75,8 @@ pub struct Configs { pub threshold: usize, pub moduli: Vec, pub plaintext_modulus: u64, - pub q_mod_t: BigInt, + pub q_mod_t: BigUint, + pub q_mod_t_centered: BigInt, pub q_inverse_mod_t: u64, pub bits: Bits, pub bounds: Bounds, @@ -137,8 +139,9 @@ impl Computation for Configs { build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Other(e.to_string()))?; let moduli = threshold_params.moduli().to_vec(); let t = threshold_params.plaintext(); - let q_mod_t = compute_q_mod_t_centered(&moduli, t); let q = utils::compute_q_product(&moduli); + let q_mod_t = compute_q_mod_t(&q, t); + let q_mod_t_centered = compute_q_mod_t_centered(&moduli, t); let q_inverse_mod_t = utils::compute_q_inverse_mod_t(&q, t)?; let bounds = Bounds::compute(preset, &())?; let bits = Bits::compute(preset, &bounds)?; @@ -148,6 +151,7 @@ impl Computation for Configs { moduli, plaintext_modulus: t, q_mod_t, + q_mod_t_centered, q_inverse_mod_t, bits, bounds, diff --git a/examples/CRISP/circuits/src/main.nr b/examples/CRISP/circuits/src/main.nr index 8d33893006..d6d6c804b0 100644 --- a/examples/CRISP/circuits/src/main.nr +++ b/examples/CRISP/circuits/src/main.nr @@ -5,7 +5,7 @@ // or FITNESS FOR A PARTICULAR PURPOSE. use lib::configs::default::threshold::{ - L, N, Q_MOD_T, USER_DATA_ENCRYPTION_BIT_CT, USER_DATA_ENCRYPTION_BIT_E0, + L, N, Q_MOD_T_CENTERED, USER_DATA_ENCRYPTION_BIT_CT, USER_DATA_ENCRYPTION_BIT_E0, USER_DATA_ENCRYPTION_BIT_E1, USER_DATA_ENCRYPTION_BIT_K, USER_DATA_ENCRYPTION_BIT_P1, USER_DATA_ENCRYPTION_BIT_P2, USER_DATA_ENCRYPTION_BIT_PK, USER_DATA_ENCRYPTION_BIT_R1, USER_DATA_ENCRYPTION_BIT_R2, USER_DATA_ENCRYPTION_BIT_U, USER_DATA_ENCRYPTION_CONFIGS, @@ -177,7 +177,7 @@ fn main( let ct_commitment = compute_ciphertext_commitment::<512, 2, 36>(ct0is, ct1is); if is_mask_vote == false { - check_coefficient_values_with_balance(k1, Q_MOD_T, balance, num_options); + check_coefficient_values_with_balance(k1, Q_MOD_T_CENTERED, balance, num_options); validate_signature(hashed_message, public_key_x, public_key_y, signature); let voter_address = address_to_field(derive_address(public_key_x, public_key_y)); diff --git a/examples/CRISP/circuits/src/utils.nr b/examples/CRISP/circuits/src/utils.nr index 3d7368a3db..eb83d5bd70 100644 --- a/examples/CRISP/circuits/src/utils.nr +++ b/examples/CRISP/circuits/src/utils.nr @@ -16,12 +16,12 @@ use crate::constants::{MAX_OPTIONS, MAX_VOTE_BITS}; /// /// # Arguments /// * `k1` - The plaintext polynomial encoding the vote -/// * `q_mod_t` - The field representation of 1 +/// * `q_mod_t_centered` - The field representation of 1 in centered form /// * `balance` - The voter's balance/voting power /// * `num_options` - Number of voting options pub fn check_coefficient_values_with_balance( k1: Polynomial, - q_mod_t: Field, + q_mod_t_centered: Field, balance: Field, num_options: u32, ) { @@ -50,7 +50,7 @@ pub fn check_coefficient_values_with_balance( let coeff = k1.coefficients[idx]; // Binary constraint: coeff must be 0 or q_mod_t - assert(0 == coeff * (q_mod_t - coeff)); + assert(0 == coeff * (q_mod_t_centered - coeff)); let bit: u64 = if coeff == 0 { 0 } else { 1 }; sum += bit * (1 << (bit_pos as u64)); @@ -70,7 +70,7 @@ pub fn check_coefficient_values_with_balance( if num_options == 2 { // Binary vote: mutually exclusive, only one option can be non-zero assert(non_zero_count <= 1); - + // Single vote must not exceed balance for opt_idx in 0..MAX_OPTIONS { if ((opt_idx as u64) < (num_options as u64)) & (sums[opt_idx] != 0) { @@ -92,10 +92,7 @@ pub fn check_coefficient_values_with_balance( /// # Arguments /// * `k1` - The plaintext polynomial encoding the vote /// -pub fn check_coefficient_zero( - k1: Polynomial, - num_options: u32, -) { +pub fn check_coefficient_zero(k1: Polynomial, num_options: u32) { assert(num_options > 1); assert(num_options as u64 <= MAX_OPTIONS as u64); @@ -142,7 +139,7 @@ fn test_2_options_vote_second() { fn test_2_options_both_voted_fails() { let mut coeffs = [0; 100]; coeffs[50] = 1; // Option 0 - coeffs[0] = 1; // Option 1 + coeffs[0] = 1; // Option 1 let pol = Polynomial { coefficients: coeffs }; check_coefficient_values_with_balance(pol, 1, 100, 2); @@ -166,12 +163,12 @@ fn test_3_options_split_vote() { // D = 99, segment_size = 33 // Vote 40 on option 0, 60 on option 2, total = 100, balance = 100 let mut coeffs = [0; 99]; - + // Option 0 at circuit segment 2 [66..98], value 40 = 101000, LSB first coeffs[69] = 1; // 2^3 = 8 coeffs[71] = 1; // 2^5 = 32 // Total: 40 - + // Option 2 at circuit segment 0 [0..32], value 60 = 111100, LSB first coeffs[2] = 1; // 2^2 = 4 coeffs[3] = 1; // 2^3 = 8 @@ -187,21 +184,21 @@ fn test_3_options_split_vote() { fn test_3_options_vote_all_three() { // Vote 30 on each, total = 90, balance = 100 let mut coeffs = [0; 99]; - + // Option 0 at circuit segment 2 [66..98], value 30 = 11110, LSB first coeffs[67] = 1; // 2^1 = 2 coeffs[68] = 1; // 2^2 = 4 coeffs[69] = 1; // 2^3 = 8 coeffs[70] = 1; // 2^4 = 16 // Total: 30 - + // Option 1 at circuit segment 1 [33..65], value 30 coeffs[34] = 1; // 2^1 = 2 coeffs[35] = 1; // 2^2 = 4 coeffs[36] = 1; // 2^3 = 8 coeffs[37] = 1; // 2^4 = 16 // Total: 30 - + // Option 2 at circuit segment 0 [0..32], value 30 coeffs[1] = 1; // 2^1 = 2 coeffs[2] = 1; // 2^2 = 4 @@ -217,13 +214,13 @@ fn test_3_options_vote_all_three() { fn test_3_options_total_exceeds_balance_fails() { // Vote 60 + 60 = 120, balance = 100 let mut coeffs = [0; 99]; - + // Option 0, value 60 coeffs[68] = 1; // 2^2 = 4 coeffs[69] = 1; // 2^3 = 8 coeffs[70] = 1; // 2^4 = 16 coeffs[71] = 1; // 2^5 = 32 - + // Option 1, value 60 coeffs[35] = 1; // 2^2 = 4 coeffs[36] = 1; // 2^3 = 8 @@ -239,7 +236,7 @@ fn test_5_options_single_vote() { // D = 100, segment_size = 20 // Vote only on option 2 with value 50 let mut coeffs = [0; 100]; - + // Option 2 at circuit segment 2 [40..59], value 50 = 110010, LSB first coeffs[41] = 1; // 2^1 = 2 coeffs[44] = 1; // 2^4 = 16 @@ -279,4 +276,3 @@ fn test_check_coefficient_zero_fails() { let pol = Polynomial { coefficients: coeffs }; check_coefficient_zero(pol, 3); } - diff --git a/examples/CRISP/packages/crisp-contracts/contracts/CRISPVerifier.sol b/examples/CRISP/packages/crisp-contracts/contracts/CRISPVerifier.sol index 51a8dac58d..04a372f081 100644 --- a/examples/CRISP/packages/crisp-contracts/contracts/CRISPVerifier.sol +++ b/examples/CRISP/packages/crisp-contracts/contracts/CRISPVerifier.sol @@ -10,143 +10,143 @@ uint256 constant LOG_N = 19; uint256 constant NUMBER_OF_PUBLIC_INPUTS = 23; uint256 constant VK_HASH = 0x2349c3d182cb0307ecfa17d2cb91dcf9e7742a8daa67aa106831c761dd22030d; library HonkVerificationKey { - function loadVerificationKey() internal pure returns (Honk.VerificationKey memory) { - Honk.VerificationKey memory vk = Honk.VerificationKey({ - circuitSize: uint256(524288), - logCircuitSize: uint256(19), - publicInputsSize: uint256(23), - ql: Honk.G1Point({ - x: uint256(0x253c5186ca1b682f7d302cd73b85ea3447a5a2c64fc82b70652f7fc44b203128), - y: uint256(0x14f602413e183c57d3d3124290ab48b6c8b0cbace773766a573ead30bf5c2f4f) - }), - qr: Honk.G1Point({ - x: uint256(0x2d20633d112c7c7efd2821f2860e9d1287f8f68ddd4b0216bb179078497a571c), - y: uint256(0x11928235f13ba529b1ee2f75fe26dda5ed5c79ec36028c09ad2bd8b957546344) - }), - qo: Honk.G1Point({ - x: uint256(0x2b570f8e2eccc265f39b9e73778cf6e282554452adb19d2a2406952447d4aa6e), - y: uint256(0x14afd678c64e0dacd64724504e36bca6eccb5a477a5e2987b6543246b8135a90) - }), - q4: Honk.G1Point({ - x: uint256(0x2d7f53a245e9c855325eeb73c8fea788f5b457293cacfd7e3e332210a27a7d2f), - y: uint256(0x182d5ef8840426caa829856d7e800ee98509383d9ee637d1facbc11f7a8fba0d) - }), - qm: Honk.G1Point({ - x: uint256(0x19d90118c106191d2f2f0350a56547c943c1ed5910d47dd067761099b54d11f3), - y: uint256(0x0f8f03c2def6108448f72f5177b6a2eed01250670efb63ea6f5ab65abc2a6885) - }), - qc: Honk.G1Point({ - x: uint256(0x2ead500c79e717f5cdbba88ffef46477c4f30926bf59547a9c141c4158f7c843), - y: uint256(0x027d6b571b395cf61aae9626f172ce4e41e598456f7b8e535c027e9129b2c98e) - }), - qLookup: Honk.G1Point({ - x: uint256(0x136236e1bfc2af648ac078e134c1b4b9114b11937ebafcdd87f8ca7660715ebb), - y: uint256(0x02293c705250462935a653b7b993e13e2e8bc6480c45c84976d526cbdbd071df) - }), - qArith: Honk.G1Point({ - x: uint256(0x074e7ead679c76d2c2d6d3f158cb522e71d14a169b2b1d0792cdfdad726f17a4), - y: uint256(0x21f9acd78d4150858f29c259c07e4552f6ec4bffac6c1e174cfb90e2e26bd68a) - }), - qDeltaRange: Honk.G1Point({ - x: uint256(0x0cebfba75751a4eff3d7855bf0f314e523e2c709a75992eb5674a546627248e3), - y: uint256(0x16b715150f4de399940c535932174f5c634c9fd9137a94008bb38f89f1350b4c) - }), - qElliptic: Honk.G1Point({ - x: uint256(0x174dba7fa4e38a55a78e43dda6c3fb81a1293285cf33eeb333ee186bb4331563), - y: uint256(0x0a2ff722359e35596e5abeec834c6cd73f4b06f5f2e837b31364f199f449ca77) - }), - qMemory: Honk.G1Point({ - x: uint256(0x29f116092db8eb3773b017110ca5d3a7b35fa1b064cde91c9e280e20399d7ec2), - y: uint256(0x1f0e39e18ded75167a270d6e31b9a6ad9a9d1b0d67c365eeb172a3fea4d1371e) - }), - qNnf: Honk.G1Point({ - x: uint256(0x19da50fca27dd36a006892b7cb6b36d7cfdac9d65fec6ae7a1a2e092e0988de5), - y: uint256(0x08f96f1d8e37be6dc83c9fbb3b912493841e4e81ab28a60c0fc85337cc4ed977) - }), - qPoseidon2External: Honk.G1Point({ - x: uint256(0x2d20446a333063e66c1a552fecdfe12d0fdddeef0034569ac350f3299b09955a), - y: uint256(0x175e6e8776625134217ce9516a11cbbeab8c4c1c8b41cfb75f43de1f500b07d2) - }), - qPoseidon2Internal: Honk.G1Point({ - x: uint256(0x090a76cca3c754a2ba40abdc2572fff64645060cfa5c48239a1df4dbb8360828), - y: uint256(0x0499d4c6aed1f78b33a6701459cd069e9bf7262754d2f15074a6c2401950761f) - }), - s1: Honk.G1Point({ - x: uint256(0x012609fcd0fa67214d78f2d5f5e3e2b459a3c4b21531646de7d51d9cd5383aa9), - y: uint256(0x21394cebbe5f66f3aecf43d318b3cb9fc7640825baba0c5a5c190a20ceeb5edb) - }), - s2: Honk.G1Point({ - x: uint256(0x080c7f024d9c813c60c84fc3e9bcad553c51f276d55e8d7a03c044b41cedef36), - y: uint256(0x2109db10b2da84ec2c4a1b62689d0952ababe94915293dfee09f1083010e5cfb) - }), - s3: Honk.G1Point({ - x: uint256(0x2f7d6b77cd5c3ee56c255be61d0e90fd7da5898a7ce4850aaf1060513cb7cd9d), - y: uint256(0x2ce5fdacbf91fc3358f4eba637d60f109f70fa929a6a1bc1f9d86e7856b87dcd) - }), - s4: Honk.G1Point({ - x: uint256(0x1db1540beb4dc13e8519b82205e9b4cb48833040c8b322d626df57e078afecc4), - y: uint256(0x28eb12f9f02ac092326277365f9eeaff536b8dbf771f9ace22d0e122f922196a) - }), - t1: Honk.G1Point({ - x: uint256(0x1f16b037f0b4c96ea2a30a118a44e139881c0db8a4d6c9fde7db5c1c1738e61f), - y: uint256(0x00c7781bda34afc32dedfb0d0f6b16c987c83375da000e180b44ad25685fc2ae) - }), - t2: Honk.G1Point({ - x: uint256(0x29345f914a28707887bee191c3a928191de584827a6d1a78ccce1d7629ca9dc0), - y: uint256(0x1920cebd0b33ac9713424e3bc03d53d79dc72f6afc24c90e56593094a213444c) - }), - t3: Honk.G1Point({ - x: uint256(0x261c990958bc2ef77a45467d9639ab2c68cf787ff7bce55ce3074dfdaedc8f8f), - y: uint256(0x23c1c05424a40360a61e46f4deab04988a6f5b71dda351e0da608cff1f332ee0) - }), - t4: Honk.G1Point({ - x: uint256(0x2b651d2fd644b2972d72ec439dc69d3339d0b052a296bfc48c6a08396aaca078), - y: uint256(0x2d7e8c1ecb92e2490049b50efc811df63f1ca97e58d5e82852dbec0c29715d71) - }), - id1: Honk.G1Point({ - x: uint256(0x0a10b2e79989b15e3a69bd491cdae007b0bf9c82d9be3d7867b33e2287b91dac), - y: uint256(0x257794eaba7a0e7aed16e03d4d8c4cf7d878b029f4ea45c559bbc19b5ec4d1de) - }), - id2: Honk.G1Point({ - x: uint256(0x25791b725ea7c712316ac4ffe10fdfcf37bd2b8f9d730ba2e26fa709bc7c3ae0), - y: uint256(0x15fba3e7928d36dc4dd6d6ff198b928c7246310974d4f74161cfd2781b9d3686) - }), - id3: Honk.G1Point({ - x: uint256(0x270be32427e6404801b9b014f983d80acf18b828c6cf049f430d98fbe34e85e0), - y: uint256(0x2e7d27a99c4b574557ea6117c390c65072cdfa51a08621141ae26d7e143d0669) - }), - id4: Honk.G1Point({ - x: uint256(0x18e2902febdb3e45358fe65981f338a7ffd1b16edd917de670fabe5d15307e20), - y: uint256(0x2f6f36f307a0f7012dc3918795312e71a1d4752966c2bc3151e5f5b3151fc236) - }), - lagrangeFirst: Honk.G1Point({ - x: uint256(0x0000000000000000000000000000000000000000000000000000000000000001), - y: uint256(0x0000000000000000000000000000000000000000000000000000000000000002) - }), - lagrangeLast: Honk.G1Point({ - x: uint256(0x12b14f226e24a52e0bc85bc5478c806023107f257430aae49136064dd3315c60), - y: uint256(0x0dfe490435cf839caf81df5c8a164afc5e3c8646f36bf27c19850b3f913edce4) - }) - }); - return vk; - } + function loadVerificationKey() internal pure returns (Honk.VerificationKey memory) { + Honk.VerificationKey memory vk = Honk.VerificationKey({ + circuitSize: uint256(524288), + logCircuitSize: uint256(19), + publicInputsSize: uint256(23), + ql: Honk.G1Point({ + x: uint256(0x253c5186ca1b682f7d302cd73b85ea3447a5a2c64fc82b70652f7fc44b203128), + y: uint256(0x14f602413e183c57d3d3124290ab48b6c8b0cbace773766a573ead30bf5c2f4f) + }), + qr: Honk.G1Point({ + x: uint256(0x2d20633d112c7c7efd2821f2860e9d1287f8f68ddd4b0216bb179078497a571c), + y: uint256(0x11928235f13ba529b1ee2f75fe26dda5ed5c79ec36028c09ad2bd8b957546344) + }), + qo: Honk.G1Point({ + x: uint256(0x2b570f8e2eccc265f39b9e73778cf6e282554452adb19d2a2406952447d4aa6e), + y: uint256(0x14afd678c64e0dacd64724504e36bca6eccb5a477a5e2987b6543246b8135a90) + }), + q4: Honk.G1Point({ + x: uint256(0x2d7f53a245e9c855325eeb73c8fea788f5b457293cacfd7e3e332210a27a7d2f), + y: uint256(0x182d5ef8840426caa829856d7e800ee98509383d9ee637d1facbc11f7a8fba0d) + }), + qm: Honk.G1Point({ + x: uint256(0x19d90118c106191d2f2f0350a56547c943c1ed5910d47dd067761099b54d11f3), + y: uint256(0x0f8f03c2def6108448f72f5177b6a2eed01250670efb63ea6f5ab65abc2a6885) + }), + qc: Honk.G1Point({ + x: uint256(0x2ead500c79e717f5cdbba88ffef46477c4f30926bf59547a9c141c4158f7c843), + y: uint256(0x027d6b571b395cf61aae9626f172ce4e41e598456f7b8e535c027e9129b2c98e) + }), + qLookup: Honk.G1Point({ + x: uint256(0x136236e1bfc2af648ac078e134c1b4b9114b11937ebafcdd87f8ca7660715ebb), + y: uint256(0x02293c705250462935a653b7b993e13e2e8bc6480c45c84976d526cbdbd071df) + }), + qArith: Honk.G1Point({ + x: uint256(0x074e7ead679c76d2c2d6d3f158cb522e71d14a169b2b1d0792cdfdad726f17a4), + y: uint256(0x21f9acd78d4150858f29c259c07e4552f6ec4bffac6c1e174cfb90e2e26bd68a) + }), + qDeltaRange: Honk.G1Point({ + x: uint256(0x0cebfba75751a4eff3d7855bf0f314e523e2c709a75992eb5674a546627248e3), + y: uint256(0x16b715150f4de399940c535932174f5c634c9fd9137a94008bb38f89f1350b4c) + }), + qElliptic: Honk.G1Point({ + x: uint256(0x174dba7fa4e38a55a78e43dda6c3fb81a1293285cf33eeb333ee186bb4331563), + y: uint256(0x0a2ff722359e35596e5abeec834c6cd73f4b06f5f2e837b31364f199f449ca77) + }), + qMemory: Honk.G1Point({ + x: uint256(0x29f116092db8eb3773b017110ca5d3a7b35fa1b064cde91c9e280e20399d7ec2), + y: uint256(0x1f0e39e18ded75167a270d6e31b9a6ad9a9d1b0d67c365eeb172a3fea4d1371e) + }), + qNnf: Honk.G1Point({ + x: uint256(0x19da50fca27dd36a006892b7cb6b36d7cfdac9d65fec6ae7a1a2e092e0988de5), + y: uint256(0x08f96f1d8e37be6dc83c9fbb3b912493841e4e81ab28a60c0fc85337cc4ed977) + }), + qPoseidon2External: Honk.G1Point({ + x: uint256(0x2d20446a333063e66c1a552fecdfe12d0fdddeef0034569ac350f3299b09955a), + y: uint256(0x175e6e8776625134217ce9516a11cbbeab8c4c1c8b41cfb75f43de1f500b07d2) + }), + qPoseidon2Internal: Honk.G1Point({ + x: uint256(0x090a76cca3c754a2ba40abdc2572fff64645060cfa5c48239a1df4dbb8360828), + y: uint256(0x0499d4c6aed1f78b33a6701459cd069e9bf7262754d2f15074a6c2401950761f) + }), + s1: Honk.G1Point({ + x: uint256(0x012609fcd0fa67214d78f2d5f5e3e2b459a3c4b21531646de7d51d9cd5383aa9), + y: uint256(0x21394cebbe5f66f3aecf43d318b3cb9fc7640825baba0c5a5c190a20ceeb5edb) + }), + s2: Honk.G1Point({ + x: uint256(0x080c7f024d9c813c60c84fc3e9bcad553c51f276d55e8d7a03c044b41cedef36), + y: uint256(0x2109db10b2da84ec2c4a1b62689d0952ababe94915293dfee09f1083010e5cfb) + }), + s3: Honk.G1Point({ + x: uint256(0x2f7d6b77cd5c3ee56c255be61d0e90fd7da5898a7ce4850aaf1060513cb7cd9d), + y: uint256(0x2ce5fdacbf91fc3358f4eba637d60f109f70fa929a6a1bc1f9d86e7856b87dcd) + }), + s4: Honk.G1Point({ + x: uint256(0x1db1540beb4dc13e8519b82205e9b4cb48833040c8b322d626df57e078afecc4), + y: uint256(0x28eb12f9f02ac092326277365f9eeaff536b8dbf771f9ace22d0e122f922196a) + }), + t1: Honk.G1Point({ + x: uint256(0x1f16b037f0b4c96ea2a30a118a44e139881c0db8a4d6c9fde7db5c1c1738e61f), + y: uint256(0x00c7781bda34afc32dedfb0d0f6b16c987c83375da000e180b44ad25685fc2ae) + }), + t2: Honk.G1Point({ + x: uint256(0x29345f914a28707887bee191c3a928191de584827a6d1a78ccce1d7629ca9dc0), + y: uint256(0x1920cebd0b33ac9713424e3bc03d53d79dc72f6afc24c90e56593094a213444c) + }), + t3: Honk.G1Point({ + x: uint256(0x261c990958bc2ef77a45467d9639ab2c68cf787ff7bce55ce3074dfdaedc8f8f), + y: uint256(0x23c1c05424a40360a61e46f4deab04988a6f5b71dda351e0da608cff1f332ee0) + }), + t4: Honk.G1Point({ + x: uint256(0x2b651d2fd644b2972d72ec439dc69d3339d0b052a296bfc48c6a08396aaca078), + y: uint256(0x2d7e8c1ecb92e2490049b50efc811df63f1ca97e58d5e82852dbec0c29715d71) + }), + id1: Honk.G1Point({ + x: uint256(0x0a10b2e79989b15e3a69bd491cdae007b0bf9c82d9be3d7867b33e2287b91dac), + y: uint256(0x257794eaba7a0e7aed16e03d4d8c4cf7d878b029f4ea45c559bbc19b5ec4d1de) + }), + id2: Honk.G1Point({ + x: uint256(0x25791b725ea7c712316ac4ffe10fdfcf37bd2b8f9d730ba2e26fa709bc7c3ae0), + y: uint256(0x15fba3e7928d36dc4dd6d6ff198b928c7246310974d4f74161cfd2781b9d3686) + }), + id3: Honk.G1Point({ + x: uint256(0x270be32427e6404801b9b014f983d80acf18b828c6cf049f430d98fbe34e85e0), + y: uint256(0x2e7d27a99c4b574557ea6117c390c65072cdfa51a08621141ae26d7e143d0669) + }), + id4: Honk.G1Point({ + x: uint256(0x18e2902febdb3e45358fe65981f338a7ffd1b16edd917de670fabe5d15307e20), + y: uint256(0x2f6f36f307a0f7012dc3918795312e71a1d4752966c2bc3151e5f5b3151fc236) + }), + lagrangeFirst: Honk.G1Point({ + x: uint256(0x0000000000000000000000000000000000000000000000000000000000000001), + y: uint256(0x0000000000000000000000000000000000000000000000000000000000000002) + }), + lagrangeLast: Honk.G1Point({ + x: uint256(0x12b14f226e24a52e0bc85bc5478c806023107f257430aae49136064dd3315c60), + y: uint256(0x0dfe490435cf839caf81df5c8a164afc5e3c8646f36bf27c19850b3f913edce4) + }) + }); + return vk; + } } pragma solidity ^0.8.27; interface IVerifier { - function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external returns (bool); + function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external returns (bool); } type Fr is uint256; -using {add as +} for Fr global; -using {sub as -} for Fr global; -using {mul as *} for Fr global; +using { add as + } for Fr global; +using { sub as - } for Fr global; +using { mul as * } for Fr global; -using {exp as ^} for Fr global; -using {notEqual as !=} for Fr global; -using {equal as ==} for Fr global; +using { exp as ^ } for Fr global; +using { notEqual as != } for Fr global; +using { equal as == } for Fr global; uint256 constant SUBGROUP_SIZE = 256; uint256 constant MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617; // Prime field order @@ -159,135 +159,135 @@ Fr constant ZERO = Fr.wrap(0); // Instantiation library FrLib { - function from(uint256 value) internal pure returns (Fr) { - unchecked { - return Fr.wrap(value % MODULUS); - } - } - - function fromBytes32(bytes32 value) internal pure returns (Fr) { - unchecked { - return Fr.wrap(uint256(value) % MODULUS); - } - } - - function toBytes32(Fr value) internal pure returns (bytes32) { - unchecked { - return bytes32(Fr.unwrap(value)); - } - } - - function invert(Fr value) internal view returns (Fr) { - uint256 v = Fr.unwrap(value); - uint256 result; - - // Call the modexp precompile to invert in the field - assembly { - let free := mload(0x40) - mstore(free, 0x20) - mstore(add(free, 0x20), 0x20) - mstore(add(free, 0x40), 0x20) - mstore(add(free, 0x60), v) - mstore(add(free, 0x80), sub(MODULUS, 2)) - mstore(add(free, 0xa0), MODULUS) - let success := staticcall(gas(), 0x05, free, 0xc0, 0x00, 0x20) - if iszero(success) { - revert(0, 0) - } - result := mload(0x00) - mstore(0x40, add(free, 0x80)) - } - - return Fr.wrap(result); - } - - function pow(Fr base, uint256 v) internal view returns (Fr) { - uint256 b = Fr.unwrap(base); - uint256 result; - - // Call the modexp precompile to invert in the field - assembly { - let free := mload(0x40) - mstore(free, 0x20) - mstore(add(free, 0x20), 0x20) - mstore(add(free, 0x40), 0x20) - mstore(add(free, 0x60), b) - mstore(add(free, 0x80), v) - mstore(add(free, 0xa0), MODULUS) - let success := staticcall(gas(), 0x05, free, 0xc0, 0x00, 0x20) - if iszero(success) { - revert(0, 0) - } - result := mload(0x00) - mstore(0x40, add(free, 0x80)) - } - - return Fr.wrap(result); - } - - function div(Fr numerator, Fr denominator) internal view returns (Fr) { - unchecked { - return numerator * invert(denominator); - } - } - - function sqr(Fr value) internal pure returns (Fr) { - unchecked { - return value * value; - } - } - - function unwrap(Fr value) internal pure returns (uint256) { - unchecked { - return Fr.unwrap(value); - } - } - - function neg(Fr value) internal pure returns (Fr) { - unchecked { - return Fr.wrap(MODULUS - Fr.unwrap(value)); - } + function from(uint256 value) internal pure returns (Fr) { + unchecked { + return Fr.wrap(value % MODULUS); + } + } + + function fromBytes32(bytes32 value) internal pure returns (Fr) { + unchecked { + return Fr.wrap(uint256(value) % MODULUS); + } + } + + function toBytes32(Fr value) internal pure returns (bytes32) { + unchecked { + return bytes32(Fr.unwrap(value)); + } + } + + function invert(Fr value) internal view returns (Fr) { + uint256 v = Fr.unwrap(value); + uint256 result; + + // Call the modexp precompile to invert in the field + assembly { + let free := mload(0x40) + mstore(free, 0x20) + mstore(add(free, 0x20), 0x20) + mstore(add(free, 0x40), 0x20) + mstore(add(free, 0x60), v) + mstore(add(free, 0x80), sub(MODULUS, 2)) + mstore(add(free, 0xa0), MODULUS) + let success := staticcall(gas(), 0x05, free, 0xc0, 0x00, 0x20) + if iszero(success) { + revert(0, 0) + } + result := mload(0x00) + mstore(0x40, add(free, 0x80)) + } + + return Fr.wrap(result); + } + + function pow(Fr base, uint256 v) internal view returns (Fr) { + uint256 b = Fr.unwrap(base); + uint256 result; + + // Call the modexp precompile to invert in the field + assembly { + let free := mload(0x40) + mstore(free, 0x20) + mstore(add(free, 0x20), 0x20) + mstore(add(free, 0x40), 0x20) + mstore(add(free, 0x60), b) + mstore(add(free, 0x80), v) + mstore(add(free, 0xa0), MODULUS) + let success := staticcall(gas(), 0x05, free, 0xc0, 0x00, 0x20) + if iszero(success) { + revert(0, 0) + } + result := mload(0x00) + mstore(0x40, add(free, 0x80)) + } + + return Fr.wrap(result); + } + + function div(Fr numerator, Fr denominator) internal view returns (Fr) { + unchecked { + return numerator * invert(denominator); } + } + + function sqr(Fr value) internal pure returns (Fr) { + unchecked { + return value * value; + } + } + + function unwrap(Fr value) internal pure returns (uint256) { + unchecked { + return Fr.unwrap(value); + } + } + + function neg(Fr value) internal pure returns (Fr) { + unchecked { + return Fr.wrap(MODULUS - Fr.unwrap(value)); + } + } } // Free functions function add(Fr a, Fr b) pure returns (Fr) { - unchecked { - return Fr.wrap(addmod(Fr.unwrap(a), Fr.unwrap(b), MODULUS)); - } + unchecked { + return Fr.wrap(addmod(Fr.unwrap(a), Fr.unwrap(b), MODULUS)); + } } function mul(Fr a, Fr b) pure returns (Fr) { - unchecked { - return Fr.wrap(mulmod(Fr.unwrap(a), Fr.unwrap(b), MODULUS)); - } + unchecked { + return Fr.wrap(mulmod(Fr.unwrap(a), Fr.unwrap(b), MODULUS)); + } } function sub(Fr a, Fr b) pure returns (Fr) { - unchecked { - return Fr.wrap(addmod(Fr.unwrap(a), MODULUS - Fr.unwrap(b), MODULUS)); - } + unchecked { + return Fr.wrap(addmod(Fr.unwrap(a), MODULUS - Fr.unwrap(b), MODULUS)); + } } function exp(Fr base, Fr exponent) pure returns (Fr) { - if (Fr.unwrap(exponent) == 0) return Fr.wrap(1); - // Implement exponent with a loop as we will overflow otherwise - for (uint256 i = 1; i < Fr.unwrap(exponent); i += i) { - base = base * base; - } - return base; + if (Fr.unwrap(exponent) == 0) return Fr.wrap(1); + // Implement exponent with a loop as we will overflow otherwise + for (uint256 i = 1; i < Fr.unwrap(exponent); i += i) { + base = base * base; + } + return base; } function notEqual(Fr a, Fr b) pure returns (bool) { - unchecked { - return Fr.unwrap(a) != Fr.unwrap(b); - } + unchecked { + return Fr.unwrap(a) != Fr.unwrap(b); + } } function equal(Fr a, Fr b) pure returns (bool) { - unchecked { - return Fr.unwrap(a) == Fr.unwrap(b); - } + unchecked { + return Fr.unwrap(a) == Fr.unwrap(b); + } } uint256 constant CONST_PROOF_SIZE_LOG_N = 28; @@ -308,1332 +308,1325 @@ uint256 constant NUMBER_OF_ALPHAS = NUMBER_OF_SUBRELATIONS - 1; // ENUM FOR WIRES enum WIRE { - Q_M, - Q_C, - Q_L, - Q_R, - Q_O, - Q_4, - Q_LOOKUP, - Q_ARITH, - Q_RANGE, - Q_ELLIPTIC, - Q_MEMORY, - Q_NNF, - Q_POSEIDON2_EXTERNAL, - Q_POSEIDON2_INTERNAL, - SIGMA_1, - SIGMA_2, - SIGMA_3, - SIGMA_4, - ID_1, - ID_2, - ID_3, - ID_4, - TABLE_1, - TABLE_2, - TABLE_3, - TABLE_4, - LAGRANGE_FIRST, - LAGRANGE_LAST, - W_L, - W_R, - W_O, - W_4, - Z_PERM, - LOOKUP_INVERSES, - LOOKUP_READ_COUNTS, - LOOKUP_READ_TAGS, - W_L_SHIFT, - W_R_SHIFT, - W_O_SHIFT, - W_4_SHIFT, - Z_PERM_SHIFT + Q_M, + Q_C, + Q_L, + Q_R, + Q_O, + Q_4, + Q_LOOKUP, + Q_ARITH, + Q_RANGE, + Q_ELLIPTIC, + Q_MEMORY, + Q_NNF, + Q_POSEIDON2_EXTERNAL, + Q_POSEIDON2_INTERNAL, + SIGMA_1, + SIGMA_2, + SIGMA_3, + SIGMA_4, + ID_1, + ID_2, + ID_3, + ID_4, + TABLE_1, + TABLE_2, + TABLE_3, + TABLE_4, + LAGRANGE_FIRST, + LAGRANGE_LAST, + W_L, + W_R, + W_O, + W_4, + Z_PERM, + LOOKUP_INVERSES, + LOOKUP_READ_COUNTS, + LOOKUP_READ_TAGS, + W_L_SHIFT, + W_R_SHIFT, + W_O_SHIFT, + W_4_SHIFT, + Z_PERM_SHIFT } library Honk { - struct G1Point { - uint256 x; - uint256 y; - } - - struct VerificationKey { - // Misc Params - uint256 circuitSize; - uint256 logCircuitSize; - uint256 publicInputsSize; - // Selectors - G1Point qm; - G1Point qc; - G1Point ql; - G1Point qr; - G1Point qo; - G1Point q4; - G1Point qLookup; // Lookup - G1Point qArith; // Arithmetic widget - G1Point qDeltaRange; // Delta Range sort - G1Point qMemory; // Memory - G1Point qNnf; // Non-native Field - G1Point qElliptic; // Auxillary - G1Point qPoseidon2External; - G1Point qPoseidon2Internal; - // Copy cnstraints - G1Point s1; - G1Point s2; - G1Point s3; - G1Point s4; - // Copy identity - G1Point id1; - G1Point id2; - G1Point id3; - G1Point id4; - // Precomputed lookup table - G1Point t1; - G1Point t2; - G1Point t3; - G1Point t4; - // Fixed first and last - G1Point lagrangeFirst; - G1Point lagrangeLast; - } - - struct RelationParameters { - // challenges - Fr eta; - Fr etaTwo; - Fr etaThree; - Fr beta; - Fr gamma; - // derived - Fr publicInputsDelta; - } - - struct Proof { - // Pairing point object - Fr[PAIRING_POINTS_SIZE] pairingPointObject; - // Free wires - G1Point w1; - G1Point w2; - G1Point w3; - G1Point w4; - // Lookup helpers - Permutations - G1Point zPerm; - // Lookup helpers - logup - G1Point lookupReadCounts; - G1Point lookupReadTags; - G1Point lookupInverses; - // Sumcheck - Fr[BATCHED_RELATION_PARTIAL_LENGTH][CONST_PROOF_SIZE_LOG_N] sumcheckUnivariates; - Fr[NUMBER_OF_ENTITIES] sumcheckEvaluations; - // Shplemini - G1Point[CONST_PROOF_SIZE_LOG_N - 1] geminiFoldComms; - Fr[CONST_PROOF_SIZE_LOG_N] geminiAEvaluations; - G1Point shplonkQ; - G1Point kzgQuotient; - } - - struct ZKProof { - // Pairing point object - Fr[PAIRING_POINTS_SIZE] pairingPointObject; - // Commitments to wire polynomials - G1Point w1; - G1Point w2; - G1Point w3; - G1Point w4; - // Commitments to logup witness polynomials - G1Point lookupReadCounts; - G1Point lookupReadTags; - G1Point lookupInverses; - // Commitment to grand permutation polynomial - G1Point zPerm; - G1Point[3] libraCommitments; - // Sumcheck - Fr libraSum; - Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH][CONST_PROOF_SIZE_LOG_N] sumcheckUnivariates; - Fr[NUMBER_OF_ENTITIES] sumcheckEvaluations; - Fr libraEvaluation; - // ZK - G1Point geminiMaskingPoly; - Fr geminiMaskingEval; - // Shplemini - G1Point[CONST_PROOF_SIZE_LOG_N - 1] geminiFoldComms; - Fr[CONST_PROOF_SIZE_LOG_N] geminiAEvaluations; - Fr[4] libraPolyEvals; - G1Point shplonkQ; - G1Point kzgQuotient; - } + struct G1Point { + uint256 x; + uint256 y; + } + + struct VerificationKey { + // Misc Params + uint256 circuitSize; + uint256 logCircuitSize; + uint256 publicInputsSize; + // Selectors + G1Point qm; + G1Point qc; + G1Point ql; + G1Point qr; + G1Point qo; + G1Point q4; + G1Point qLookup; // Lookup + G1Point qArith; // Arithmetic widget + G1Point qDeltaRange; // Delta Range sort + G1Point qMemory; // Memory + G1Point qNnf; // Non-native Field + G1Point qElliptic; // Auxillary + G1Point qPoseidon2External; + G1Point qPoseidon2Internal; + // Copy cnstraints + G1Point s1; + G1Point s2; + G1Point s3; + G1Point s4; + // Copy identity + G1Point id1; + G1Point id2; + G1Point id3; + G1Point id4; + // Precomputed lookup table + G1Point t1; + G1Point t2; + G1Point t3; + G1Point t4; + // Fixed first and last + G1Point lagrangeFirst; + G1Point lagrangeLast; + } + + struct RelationParameters { + // challenges + Fr eta; + Fr etaTwo; + Fr etaThree; + Fr beta; + Fr gamma; + // derived + Fr publicInputsDelta; + } + + struct Proof { + // Pairing point object + Fr[PAIRING_POINTS_SIZE] pairingPointObject; + // Free wires + G1Point w1; + G1Point w2; + G1Point w3; + G1Point w4; + // Lookup helpers - Permutations + G1Point zPerm; + // Lookup helpers - logup + G1Point lookupReadCounts; + G1Point lookupReadTags; + G1Point lookupInverses; + // Sumcheck + Fr[BATCHED_RELATION_PARTIAL_LENGTH][CONST_PROOF_SIZE_LOG_N] sumcheckUnivariates; + Fr[NUMBER_OF_ENTITIES] sumcheckEvaluations; + // Shplemini + G1Point[CONST_PROOF_SIZE_LOG_N - 1] geminiFoldComms; + Fr[CONST_PROOF_SIZE_LOG_N] geminiAEvaluations; + G1Point shplonkQ; + G1Point kzgQuotient; + } + + struct ZKProof { + // Pairing point object + Fr[PAIRING_POINTS_SIZE] pairingPointObject; + // Commitments to wire polynomials + G1Point w1; + G1Point w2; + G1Point w3; + G1Point w4; + // Commitments to logup witness polynomials + G1Point lookupReadCounts; + G1Point lookupReadTags; + G1Point lookupInverses; + // Commitment to grand permutation polynomial + G1Point zPerm; + G1Point[3] libraCommitments; + // Sumcheck + Fr libraSum; + Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH][CONST_PROOF_SIZE_LOG_N] sumcheckUnivariates; + Fr[NUMBER_OF_ENTITIES] sumcheckEvaluations; + Fr libraEvaluation; + // ZK + G1Point geminiMaskingPoly; + Fr geminiMaskingEval; + // Shplemini + G1Point[CONST_PROOF_SIZE_LOG_N - 1] geminiFoldComms; + Fr[CONST_PROOF_SIZE_LOG_N] geminiAEvaluations; + Fr[4] libraPolyEvals; + G1Point shplonkQ; + G1Point kzgQuotient; + } } // ZKTranscript library to generate fiat shamir challenges, the ZK transcript only differest struct ZKTranscript { - // Oink - Honk.RelationParameters relationParameters; - Fr[NUMBER_OF_ALPHAS] alphas; - Fr[CONST_PROOF_SIZE_LOG_N] gateChallenges; - // Sumcheck - Fr libraChallenge; - Fr[CONST_PROOF_SIZE_LOG_N] sumCheckUChallenges; - // Shplemini - Fr rho; - Fr geminiR; - Fr shplonkNu; - Fr shplonkZ; - // Derived - Fr publicInputsDelta; + // Oink + Honk.RelationParameters relationParameters; + Fr[NUMBER_OF_ALPHAS] alphas; + Fr[CONST_PROOF_SIZE_LOG_N] gateChallenges; + // Sumcheck + Fr libraChallenge; + Fr[CONST_PROOF_SIZE_LOG_N] sumCheckUChallenges; + // Shplemini + Fr rho; + Fr geminiR; + Fr shplonkNu; + Fr shplonkZ; + // Derived + Fr publicInputsDelta; } library ZKTranscriptLib { - function generateTranscript( - Honk.ZKProof memory proof, - bytes32[] calldata publicInputs, - uint256 vkHash, - uint256 publicInputsSize, - uint256 logN - ) external pure returns (ZKTranscript memory t) { - Fr previousChallenge; - (t.relationParameters, previousChallenge) = - generateRelationParametersChallenges(proof, publicInputs, vkHash, publicInputsSize, previousChallenge); - - (t.alphas, previousChallenge) = generateAlphaChallenges(previousChallenge, proof); - - (t.gateChallenges, previousChallenge) = generateGateChallenges(previousChallenge, logN); - (t.libraChallenge, previousChallenge) = generateLibraChallenge(previousChallenge, proof); - (t.sumCheckUChallenges, previousChallenge) = generateSumcheckChallenges(proof, previousChallenge, logN); - - (t.rho, previousChallenge) = generateRhoChallenge(proof, previousChallenge); - - (t.geminiR, previousChallenge) = generateGeminiRChallenge(proof, previousChallenge, logN); - - (t.shplonkNu, previousChallenge) = generateShplonkNuChallenge(proof, previousChallenge, logN); - - (t.shplonkZ, previousChallenge) = generateShplonkZChallenge(proof, previousChallenge); - return t; - } - - function splitChallenge(Fr challenge) internal pure returns (Fr first, Fr second) { - uint256 challengeU256 = uint256(Fr.unwrap(challenge)); - uint256 lo = challengeU256 & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - uint256 hi = challengeU256 >> 128; - first = FrLib.fromBytes32(bytes32(lo)); - second = FrLib.fromBytes32(bytes32(hi)); - } - - function generateRelationParametersChallenges( - Honk.ZKProof memory proof, - bytes32[] calldata publicInputs, - uint256 vkHash, - uint256 publicInputsSize, - Fr previousChallenge - ) internal pure returns (Honk.RelationParameters memory rp, Fr nextPreviousChallenge) { - (rp.eta, rp.etaTwo, rp.etaThree, previousChallenge) = - generateEtaChallenge(proof, publicInputs, vkHash, publicInputsSize); - - (rp.beta, rp.gamma, nextPreviousChallenge) = generateBetaAndGammaChallenges(previousChallenge, proof); - } - - function generateEtaChallenge( - Honk.ZKProof memory proof, - bytes32[] calldata publicInputs, - uint256 vkHash, - uint256 publicInputsSize - ) internal pure returns (Fr eta, Fr etaTwo, Fr etaThree, Fr previousChallenge) { - bytes32[] memory round0 = new bytes32[](1 + publicInputsSize + 6); - round0[0] = bytes32(vkHash); - - for (uint256 i = 0; i < publicInputsSize - PAIRING_POINTS_SIZE; i++) { - round0[1 + i] = bytes32(publicInputs[i]); - } - for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) { - round0[1 + publicInputsSize - PAIRING_POINTS_SIZE + i] = FrLib.toBytes32(proof.pairingPointObject[i]); - } - - // Create the first challenge - // Note: w4 is added to the challenge later on - round0[1 + publicInputsSize] = bytes32(proof.w1.x); - round0[1 + publicInputsSize + 1] = bytes32(proof.w1.y); - round0[1 + publicInputsSize + 2] = bytes32(proof.w2.x); - round0[1 + publicInputsSize + 3] = bytes32(proof.w2.y); - round0[1 + publicInputsSize + 4] = bytes32(proof.w3.x); - round0[1 + publicInputsSize + 5] = bytes32(proof.w3.y); - - previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(round0))); - (eta, etaTwo) = splitChallenge(previousChallenge); - previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(Fr.unwrap(previousChallenge)))); - - (etaThree,) = splitChallenge(previousChallenge); - } - - function generateBetaAndGammaChallenges(Fr previousChallenge, Honk.ZKProof memory proof) - internal - pure - returns (Fr beta, Fr gamma, Fr nextPreviousChallenge) + function generateTranscript( + Honk.ZKProof memory proof, + bytes32[] calldata publicInputs, + uint256 vkHash, + uint256 publicInputsSize, + uint256 logN + ) external pure returns (ZKTranscript memory t) { + Fr previousChallenge; + (t.relationParameters, previousChallenge) = generateRelationParametersChallenges( + proof, + publicInputs, + vkHash, + publicInputsSize, + previousChallenge + ); + + (t.alphas, previousChallenge) = generateAlphaChallenges(previousChallenge, proof); + + (t.gateChallenges, previousChallenge) = generateGateChallenges(previousChallenge, logN); + (t.libraChallenge, previousChallenge) = generateLibraChallenge(previousChallenge, proof); + (t.sumCheckUChallenges, previousChallenge) = generateSumcheckChallenges(proof, previousChallenge, logN); + + (t.rho, previousChallenge) = generateRhoChallenge(proof, previousChallenge); + + (t.geminiR, previousChallenge) = generateGeminiRChallenge(proof, previousChallenge, logN); + + (t.shplonkNu, previousChallenge) = generateShplonkNuChallenge(proof, previousChallenge, logN); + + (t.shplonkZ, previousChallenge) = generateShplonkZChallenge(proof, previousChallenge); + return t; + } + + function splitChallenge(Fr challenge) internal pure returns (Fr first, Fr second) { + uint256 challengeU256 = uint256(Fr.unwrap(challenge)); + uint256 lo = challengeU256 & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + uint256 hi = challengeU256 >> 128; + first = FrLib.fromBytes32(bytes32(lo)); + second = FrLib.fromBytes32(bytes32(hi)); + } + + function generateRelationParametersChallenges( + Honk.ZKProof memory proof, + bytes32[] calldata publicInputs, + uint256 vkHash, + uint256 publicInputsSize, + Fr previousChallenge + ) internal pure returns (Honk.RelationParameters memory rp, Fr nextPreviousChallenge) { + (rp.eta, rp.etaTwo, rp.etaThree, previousChallenge) = generateEtaChallenge(proof, publicInputs, vkHash, publicInputsSize); + + (rp.beta, rp.gamma, nextPreviousChallenge) = generateBetaAndGammaChallenges(previousChallenge, proof); + } + + function generateEtaChallenge( + Honk.ZKProof memory proof, + bytes32[] calldata publicInputs, + uint256 vkHash, + uint256 publicInputsSize + ) internal pure returns (Fr eta, Fr etaTwo, Fr etaThree, Fr previousChallenge) { + bytes32[] memory round0 = new bytes32[](1 + publicInputsSize + 6); + round0[0] = bytes32(vkHash); + + for (uint256 i = 0; i < publicInputsSize - PAIRING_POINTS_SIZE; i++) { + round0[1 + i] = bytes32(publicInputs[i]); + } + for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) { + round0[1 + publicInputsSize - PAIRING_POINTS_SIZE + i] = FrLib.toBytes32(proof.pairingPointObject[i]); + } + + // Create the first challenge + // Note: w4 is added to the challenge later on + round0[1 + publicInputsSize] = bytes32(proof.w1.x); + round0[1 + publicInputsSize + 1] = bytes32(proof.w1.y); + round0[1 + publicInputsSize + 2] = bytes32(proof.w2.x); + round0[1 + publicInputsSize + 3] = bytes32(proof.w2.y); + round0[1 + publicInputsSize + 4] = bytes32(proof.w3.x); + round0[1 + publicInputsSize + 5] = bytes32(proof.w3.y); + + previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(round0))); + (eta, etaTwo) = splitChallenge(previousChallenge); + previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(Fr.unwrap(previousChallenge)))); + + (etaThree, ) = splitChallenge(previousChallenge); + } + + function generateBetaAndGammaChallenges( + Fr previousChallenge, + Honk.ZKProof memory proof + ) internal pure returns (Fr beta, Fr gamma, Fr nextPreviousChallenge) { + bytes32[7] memory round1; + round1[0] = FrLib.toBytes32(previousChallenge); + round1[1] = bytes32(proof.lookupReadCounts.x); + round1[2] = bytes32(proof.lookupReadCounts.y); + round1[3] = bytes32(proof.lookupReadTags.x); + round1[4] = bytes32(proof.lookupReadTags.y); + round1[5] = bytes32(proof.w4.x); + round1[6] = bytes32(proof.w4.y); + + nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(round1))); + (beta, gamma) = splitChallenge(nextPreviousChallenge); + } + + // Alpha challenges non-linearise the gate contributions + function generateAlphaChallenges( + Fr previousChallenge, + Honk.ZKProof memory proof + ) internal pure returns (Fr[NUMBER_OF_ALPHAS] memory alphas, Fr nextPreviousChallenge) { + // Generate the original sumcheck alpha 0 by hashing zPerm and zLookup + uint256[5] memory alpha0; + alpha0[0] = Fr.unwrap(previousChallenge); + alpha0[1] = proof.lookupInverses.x; + alpha0[2] = proof.lookupInverses.y; + alpha0[3] = proof.zPerm.x; + alpha0[4] = proof.zPerm.y; + + nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(alpha0))); + Fr alpha; + (alpha, ) = splitChallenge(nextPreviousChallenge); + + // Compute powers of alpha for batching subrelations + alphas[0] = alpha; + for (uint256 i = 1; i < NUMBER_OF_ALPHAS; i++) { + alphas[i] = alphas[i - 1] * alpha; + } + } + + function generateGateChallenges( + Fr previousChallenge, + uint256 logN + ) internal pure returns (Fr[CONST_PROOF_SIZE_LOG_N] memory gateChallenges, Fr nextPreviousChallenge) { + previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(Fr.unwrap(previousChallenge)))); + (gateChallenges[0], ) = splitChallenge(previousChallenge); + for (uint256 i = 1; i < logN; i++) { + gateChallenges[i] = gateChallenges[i - 1] * gateChallenges[i - 1]; + } + nextPreviousChallenge = previousChallenge; + } + + function generateLibraChallenge( + Fr previousChallenge, + Honk.ZKProof memory proof + ) internal pure returns (Fr libraChallenge, Fr nextPreviousChallenge) { + // 2 comm, 1 sum, 1 challenge + uint256[4] memory challengeData; + challengeData[0] = Fr.unwrap(previousChallenge); + challengeData[1] = proof.libraCommitments[0].x; + challengeData[2] = proof.libraCommitments[0].y; + challengeData[3] = Fr.unwrap(proof.libraSum); + nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(challengeData))); + (libraChallenge, ) = splitChallenge(nextPreviousChallenge); + } + + function generateSumcheckChallenges( + Honk.ZKProof memory proof, + Fr prevChallenge, + uint256 logN + ) internal pure returns (Fr[CONST_PROOF_SIZE_LOG_N] memory sumcheckChallenges, Fr nextPreviousChallenge) { + for (uint256 i = 0; i < logN; i++) { + Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH + 1] memory univariateChal; + univariateChal[0] = prevChallenge; + + for (uint256 j = 0; j < ZK_BATCHED_RELATION_PARTIAL_LENGTH; j++) { + univariateChal[j + 1] = proof.sumcheckUnivariates[i][j]; + } + prevChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(univariateChal))); + + (sumcheckChallenges[i], ) = splitChallenge(prevChallenge); + } + nextPreviousChallenge = prevChallenge; + } + + // We add Libra claimed eval + 3 comm + 1 more eval + function generateRhoChallenge(Honk.ZKProof memory proof, Fr prevChallenge) internal pure returns (Fr rho, Fr nextPreviousChallenge) { + uint256[NUMBER_OF_ENTITIES + 9] memory rhoChallengeElements; + rhoChallengeElements[0] = Fr.unwrap(prevChallenge); + uint256 i; + for (i = 1; i <= NUMBER_OF_ENTITIES; i++) { + rhoChallengeElements[i] = Fr.unwrap(proof.sumcheckEvaluations[i - 1]); + } + rhoChallengeElements[i] = Fr.unwrap(proof.libraEvaluation); + + i += 1; + rhoChallengeElements[i] = proof.libraCommitments[1].x; + rhoChallengeElements[i + 1] = proof.libraCommitments[1].y; + i += 2; + rhoChallengeElements[i] = proof.libraCommitments[2].x; + rhoChallengeElements[i + 1] = proof.libraCommitments[2].y; + i += 2; + rhoChallengeElements[i] = proof.geminiMaskingPoly.x; + rhoChallengeElements[i + 1] = proof.geminiMaskingPoly.y; + + i += 2; + rhoChallengeElements[i] = Fr.unwrap(proof.geminiMaskingEval); + + nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(rhoChallengeElements))); + (rho, ) = splitChallenge(nextPreviousChallenge); + } + + function generateGeminiRChallenge( + Honk.ZKProof memory proof, + Fr prevChallenge, + uint256 logN + ) internal pure returns (Fr geminiR, Fr nextPreviousChallenge) { + uint256[] memory gR = new uint256[]((logN - 1) * 2 + 1); + gR[0] = Fr.unwrap(prevChallenge); + + for (uint256 i = 0; i < logN - 1; i++) { + gR[1 + i * 2] = proof.geminiFoldComms[i].x; + gR[2 + i * 2] = proof.geminiFoldComms[i].y; + } + + nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(gR))); + + (geminiR, ) = splitChallenge(nextPreviousChallenge); + } + + function generateShplonkNuChallenge( + Honk.ZKProof memory proof, + Fr prevChallenge, + uint256 logN + ) internal pure returns (Fr shplonkNu, Fr nextPreviousChallenge) { + uint256[] memory shplonkNuChallengeElements = new uint256[](logN + 1 + 4); + shplonkNuChallengeElements[0] = Fr.unwrap(prevChallenge); + + for (uint256 i = 1; i <= logN; i++) { + shplonkNuChallengeElements[i] = Fr.unwrap(proof.geminiAEvaluations[i - 1]); + } + + uint256 libraIdx = 0; + for (uint256 i = logN + 1; i <= logN + 4; i++) { + shplonkNuChallengeElements[i] = Fr.unwrap(proof.libraPolyEvals[libraIdx]); + libraIdx++; + } + + nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(shplonkNuChallengeElements))); + (shplonkNu, ) = splitChallenge(nextPreviousChallenge); + } + + function generateShplonkZChallenge( + Honk.ZKProof memory proof, + Fr prevChallenge + ) internal pure returns (Fr shplonkZ, Fr nextPreviousChallenge) { + uint256[3] memory shplonkZChallengeElements; + shplonkZChallengeElements[0] = Fr.unwrap(prevChallenge); + + shplonkZChallengeElements[1] = proof.shplonkQ.x; + shplonkZChallengeElements[2] = proof.shplonkQ.y; + + nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(shplonkZChallengeElements))); + (shplonkZ, ) = splitChallenge(nextPreviousChallenge); + } + + function loadProof(bytes calldata proof, uint256 logN) internal pure returns (Honk.ZKProof memory p) { + uint256 boundary = 0x0; + + // Pairing point object + for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) { + p.pairingPointObject[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + boundary += FIELD_ELEMENT_SIZE; + } + // Commitments + p.w1 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.w2 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.w3 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + + // Lookup / Permutation Helper Commitments + p.lookupReadCounts = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.lookupReadTags = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.w4 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.lookupInverses = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.zPerm = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.libraCommitments[0] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + + p.libraSum = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + boundary += FIELD_ELEMENT_SIZE; + // Sumcheck univariates + for (uint256 i = 0; i < logN; i++) { + for (uint256 j = 0; j < ZK_BATCHED_RELATION_PARTIAL_LENGTH; j++) { + p.sumcheckUnivariates[i][j] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + boundary += FIELD_ELEMENT_SIZE; + } + } + + // Sumcheck evaluations + for (uint256 i = 0; i < NUMBER_OF_ENTITIES; i++) { + p.sumcheckEvaluations[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + boundary += FIELD_ELEMENT_SIZE; + } + + p.libraEvaluation = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + boundary += FIELD_ELEMENT_SIZE; + + p.libraCommitments[1] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.libraCommitments[2] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.geminiMaskingPoly = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.geminiMaskingEval = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + boundary += FIELD_ELEMENT_SIZE; + + // Gemini + // Read gemini fold univariates + for (uint256 i = 0; i < logN - 1; i++) { + p.geminiFoldComms[i] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + } + + // Read gemini a evaluations + for (uint256 i = 0; i < logN; i++) { + p.geminiAEvaluations[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + boundary += FIELD_ELEMENT_SIZE; + } + + for (uint256 i = 0; i < 4; i++) { + p.libraPolyEvals[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + boundary += FIELD_ELEMENT_SIZE; + } + + // Shplonk + p.shplonkQ = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + // KZG + p.kzgQuotient = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + } +} + +// Field arithmetic libraries + +library RelationsLib { + Fr internal constant GRUMPKIN_CURVE_B_PARAMETER_NEGATED = Fr.wrap(17); // -(-17) + + function accumulateRelationEvaluations( + Fr[NUMBER_OF_ENTITIES] memory purportedEvaluations, + Honk.RelationParameters memory rp, + Fr[NUMBER_OF_ALPHAS] memory alphas, + Fr powPartialEval + ) internal pure returns (Fr accumulator) { + Fr[NUMBER_OF_SUBRELATIONS] memory evaluations; + + // Accumulate all relations in Ultra Honk - each with varying number of subrelations + accumulateArithmeticRelation(purportedEvaluations, evaluations, powPartialEval); + accumulatePermutationRelation(purportedEvaluations, rp, evaluations, powPartialEval); + accumulateLogDerivativeLookupRelation(purportedEvaluations, rp, evaluations, powPartialEval); + accumulateDeltaRangeRelation(purportedEvaluations, evaluations, powPartialEval); + accumulateEllipticRelation(purportedEvaluations, evaluations, powPartialEval); + accumulateMemoryRelation(purportedEvaluations, rp, evaluations, powPartialEval); + accumulateNnfRelation(purportedEvaluations, evaluations, powPartialEval); + accumulatePoseidonExternalRelation(purportedEvaluations, evaluations, powPartialEval); + accumulatePoseidonInternalRelation(purportedEvaluations, evaluations, powPartialEval); + + // batch the subrelations with the alpha challenges to obtain the full honk relation + accumulator = scaleAndBatchSubrelations(evaluations, alphas); + } + + /** + * Aesthetic helper function that is used to index by enum into proof.sumcheckEvaluations, it avoids + * the relation checking code being cluttered with uint256 type casting, which is often a different colour in code + * editors, and thus is noisy. + */ + function wire(Fr[NUMBER_OF_ENTITIES] memory p, WIRE _wire) internal pure returns (Fr) { + return p[uint256(_wire)]; + } + + uint256 internal constant NEG_HALF_MODULO_P = 0x183227397098d014dc2822db40c0ac2e9419f4243cdcb848a1f0fac9f8000000; + /** + * Ultra Arithmetic Relation + * + */ + + function accumulateArithmeticRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + // Relation 0 + Fr q_arith = wire(p, WIRE.Q_ARITH); { - bytes32[7] memory round1; - round1[0] = FrLib.toBytes32(previousChallenge); - round1[1] = bytes32(proof.lookupReadCounts.x); - round1[2] = bytes32(proof.lookupReadCounts.y); - round1[3] = bytes32(proof.lookupReadTags.x); - round1[4] = bytes32(proof.lookupReadTags.y); - round1[5] = bytes32(proof.w4.x); - round1[6] = bytes32(proof.w4.y); - - nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(round1))); - (beta, gamma) = splitChallenge(nextPreviousChallenge); - } - - // Alpha challenges non-linearise the gate contributions - function generateAlphaChallenges(Fr previousChallenge, Honk.ZKProof memory proof) - internal - pure - returns (Fr[NUMBER_OF_ALPHAS] memory alphas, Fr nextPreviousChallenge) + Fr neg_half = Fr.wrap(NEG_HALF_MODULO_P); + + Fr accum = (q_arith - Fr.wrap(3)) * (wire(p, WIRE.Q_M) * wire(p, WIRE.W_R) * wire(p, WIRE.W_L)) * neg_half; + accum = + accum + + (wire(p, WIRE.Q_L) * wire(p, WIRE.W_L)) + + (wire(p, WIRE.Q_R) * wire(p, WIRE.W_R)) + + (wire(p, WIRE.Q_O) * wire(p, WIRE.W_O)) + + (wire(p, WIRE.Q_4) * wire(p, WIRE.W_4)) + + wire(p, WIRE.Q_C); + accum = accum + (q_arith - ONE) * wire(p, WIRE.W_4_SHIFT); + accum = accum * q_arith; + accum = accum * domainSep; + evals[0] = accum; + } + + // Relation 1 { - // Generate the original sumcheck alpha 0 by hashing zPerm and zLookup - uint256[5] memory alpha0; - alpha0[0] = Fr.unwrap(previousChallenge); - alpha0[1] = proof.lookupInverses.x; - alpha0[2] = proof.lookupInverses.y; - alpha0[3] = proof.zPerm.x; - alpha0[4] = proof.zPerm.y; - - nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(alpha0))); - Fr alpha; - (alpha,) = splitChallenge(nextPreviousChallenge); - - // Compute powers of alpha for batching subrelations - alphas[0] = alpha; - for (uint256 i = 1; i < NUMBER_OF_ALPHAS; i++) { - alphas[i] = alphas[i - 1] * alpha; - } - } - - function generateGateChallenges(Fr previousChallenge, uint256 logN) - internal - pure - returns (Fr[CONST_PROOF_SIZE_LOG_N] memory gateChallenges, Fr nextPreviousChallenge) + Fr accum = wire(p, WIRE.W_L) + wire(p, WIRE.W_4) - wire(p, WIRE.W_L_SHIFT) + wire(p, WIRE.Q_M); + accum = accum * (q_arith - Fr.wrap(2)); + accum = accum * (q_arith - ONE); + accum = accum * q_arith; + accum = accum * domainSep; + evals[1] = accum; + } + } + + function accumulatePermutationRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Honk.RelationParameters memory rp, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + Fr grand_product_numerator; + Fr grand_product_denominator; + { - previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(Fr.unwrap(previousChallenge)))); - (gateChallenges[0],) = splitChallenge(previousChallenge); - for (uint256 i = 1; i < logN; i++) { - gateChallenges[i] = gateChallenges[i - 1] * gateChallenges[i - 1]; - } - nextPreviousChallenge = previousChallenge; - } - - function generateLibraChallenge(Fr previousChallenge, Honk.ZKProof memory proof) - internal - pure - returns (Fr libraChallenge, Fr nextPreviousChallenge) + Fr num = wire(p, WIRE.W_L) + wire(p, WIRE.ID_1) * rp.beta + rp.gamma; + num = num * (wire(p, WIRE.W_R) + wire(p, WIRE.ID_2) * rp.beta + rp.gamma); + num = num * (wire(p, WIRE.W_O) + wire(p, WIRE.ID_3) * rp.beta + rp.gamma); + num = num * (wire(p, WIRE.W_4) + wire(p, WIRE.ID_4) * rp.beta + rp.gamma); + + grand_product_numerator = num; + } { - // 2 comm, 1 sum, 1 challenge - uint256[4] memory challengeData; - challengeData[0] = Fr.unwrap(previousChallenge); - challengeData[1] = proof.libraCommitments[0].x; - challengeData[2] = proof.libraCommitments[0].y; - challengeData[3] = Fr.unwrap(proof.libraSum); - nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(challengeData))); - (libraChallenge,) = splitChallenge(nextPreviousChallenge); - } - - function generateSumcheckChallenges(Honk.ZKProof memory proof, Fr prevChallenge, uint256 logN) - internal - pure - returns (Fr[CONST_PROOF_SIZE_LOG_N] memory sumcheckChallenges, Fr nextPreviousChallenge) + Fr den = wire(p, WIRE.W_L) + wire(p, WIRE.SIGMA_1) * rp.beta + rp.gamma; + den = den * (wire(p, WIRE.W_R) + wire(p, WIRE.SIGMA_2) * rp.beta + rp.gamma); + den = den * (wire(p, WIRE.W_O) + wire(p, WIRE.SIGMA_3) * rp.beta + rp.gamma); + den = den * (wire(p, WIRE.W_4) + wire(p, WIRE.SIGMA_4) * rp.beta + rp.gamma); + + grand_product_denominator = den; + } + + // Contribution 2 { - for (uint256 i = 0; i < logN; i++) { - Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH + 1] memory univariateChal; - univariateChal[0] = prevChallenge; + Fr acc = (wire(p, WIRE.Z_PERM) + wire(p, WIRE.LAGRANGE_FIRST)) * grand_product_numerator; - for (uint256 j = 0; j < ZK_BATCHED_RELATION_PARTIAL_LENGTH; j++) { - univariateChal[j + 1] = proof.sumcheckUnivariates[i][j]; - } - prevChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(univariateChal))); + acc = acc - ((wire(p, WIRE.Z_PERM_SHIFT) + (wire(p, WIRE.LAGRANGE_LAST) * rp.publicInputsDelta)) * grand_product_denominator); + acc = acc * domainSep; + evals[2] = acc; + } - (sumcheckChallenges[i],) = splitChallenge(prevChallenge); - } - nextPreviousChallenge = prevChallenge; + // Contribution 3 + { + Fr acc = (wire(p, WIRE.LAGRANGE_LAST) * wire(p, WIRE.Z_PERM_SHIFT)) * domainSep; + evals[3] = acc; } + } + + function accumulateLogDerivativeLookupRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Honk.RelationParameters memory rp, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + Fr write_term; + Fr read_term; - // We add Libra claimed eval + 3 comm + 1 more eval - function generateRhoChallenge(Honk.ZKProof memory proof, Fr prevChallenge) - internal - pure - returns (Fr rho, Fr nextPreviousChallenge) + // Calculate the write term (the table accumulation) { - uint256[NUMBER_OF_ENTITIES + 9] memory rhoChallengeElements; - rhoChallengeElements[0] = Fr.unwrap(prevChallenge); - uint256 i; - for (i = 1; i <= NUMBER_OF_ENTITIES; i++) { - rhoChallengeElements[i] = Fr.unwrap(proof.sumcheckEvaluations[i - 1]); - } - rhoChallengeElements[i] = Fr.unwrap(proof.libraEvaluation); - - i += 1; - rhoChallengeElements[i] = proof.libraCommitments[1].x; - rhoChallengeElements[i + 1] = proof.libraCommitments[1].y; - i += 2; - rhoChallengeElements[i] = proof.libraCommitments[2].x; - rhoChallengeElements[i + 1] = proof.libraCommitments[2].y; - i += 2; - rhoChallengeElements[i] = proof.geminiMaskingPoly.x; - rhoChallengeElements[i + 1] = proof.geminiMaskingPoly.y; - - i += 2; - rhoChallengeElements[i] = Fr.unwrap(proof.geminiMaskingEval); - - nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(rhoChallengeElements))); - (rho,) = splitChallenge(nextPreviousChallenge); - } - - function generateGeminiRChallenge(Honk.ZKProof memory proof, Fr prevChallenge, uint256 logN) - internal - pure - returns (Fr geminiR, Fr nextPreviousChallenge) + write_term = + wire(p, WIRE.TABLE_1) + + rp.gamma + + (wire(p, WIRE.TABLE_2) * rp.eta) + + (wire(p, WIRE.TABLE_3) * rp.etaTwo) + + (wire(p, WIRE.TABLE_4) * rp.etaThree); + } + + // Calculate the write term { - uint256[] memory gR = new uint256[]((logN - 1) * 2 + 1); - gR[0] = Fr.unwrap(prevChallenge); + Fr derived_entry_1 = wire(p, WIRE.W_L) + rp.gamma + (wire(p, WIRE.Q_R) * wire(p, WIRE.W_L_SHIFT)); + Fr derived_entry_2 = wire(p, WIRE.W_R) + wire(p, WIRE.Q_M) * wire(p, WIRE.W_R_SHIFT); + Fr derived_entry_3 = wire(p, WIRE.W_O) + wire(p, WIRE.Q_C) * wire(p, WIRE.W_O_SHIFT); + + read_term = derived_entry_1 + (derived_entry_2 * rp.eta) + (derived_entry_3 * rp.etaTwo) + (wire(p, WIRE.Q_O) * rp.etaThree); + } + + Fr read_inverse = wire(p, WIRE.LOOKUP_INVERSES) * write_term; + Fr write_inverse = wire(p, WIRE.LOOKUP_INVERSES) * read_term; + + Fr inverse_exists_xor = wire(p, WIRE.LOOKUP_READ_TAGS) + + wire(p, WIRE.Q_LOOKUP) - + (wire(p, WIRE.LOOKUP_READ_TAGS) * wire(p, WIRE.Q_LOOKUP)); + + // Inverse calculated correctly relation + Fr accumulatorNone = read_term * write_term * wire(p, WIRE.LOOKUP_INVERSES) - inverse_exists_xor; + accumulatorNone = accumulatorNone * domainSep; + + // Inverse + Fr accumulatorOne = wire(p, WIRE.Q_LOOKUP) * read_inverse - wire(p, WIRE.LOOKUP_READ_COUNTS) * write_inverse; + + Fr read_tag = wire(p, WIRE.LOOKUP_READ_TAGS); + + Fr read_tag_boolean_relation = read_tag * read_tag - read_tag; - for (uint256 i = 0; i < logN - 1; i++) { - gR[1 + i * 2] = proof.geminiFoldComms[i].x; - gR[2 + i * 2] = proof.geminiFoldComms[i].y; - } + evals[4] = accumulatorNone; + evals[5] = accumulatorOne; + evals[6] = read_tag_boolean_relation * domainSep; + } - nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(gR))); + function accumulateDeltaRangeRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + Fr minus_one = ZERO - ONE; + Fr minus_two = ZERO - Fr.wrap(2); + Fr minus_three = ZERO - Fr.wrap(3); - (geminiR,) = splitChallenge(nextPreviousChallenge); + // Compute wire differences + Fr delta_1 = wire(p, WIRE.W_R) - wire(p, WIRE.W_L); + Fr delta_2 = wire(p, WIRE.W_O) - wire(p, WIRE.W_R); + Fr delta_3 = wire(p, WIRE.W_4) - wire(p, WIRE.W_O); + Fr delta_4 = wire(p, WIRE.W_L_SHIFT) - wire(p, WIRE.W_4); + + // Contribution 6 + { + Fr acc = delta_1; + acc = acc * (delta_1 + minus_one); + acc = acc * (delta_1 + minus_two); + acc = acc * (delta_1 + minus_three); + acc = acc * wire(p, WIRE.Q_RANGE); + acc = acc * domainSep; + evals[7] = acc; } - function generateShplonkNuChallenge(Honk.ZKProof memory proof, Fr prevChallenge, uint256 logN) - internal - pure - returns (Fr shplonkNu, Fr nextPreviousChallenge) + // Contribution 7 { - uint256[] memory shplonkNuChallengeElements = new uint256[](logN + 1 + 4); - shplonkNuChallengeElements[0] = Fr.unwrap(prevChallenge); + Fr acc = delta_2; + acc = acc * (delta_2 + minus_one); + acc = acc * (delta_2 + minus_two); + acc = acc * (delta_2 + minus_three); + acc = acc * wire(p, WIRE.Q_RANGE); + acc = acc * domainSep; + evals[8] = acc; + } - for (uint256 i = 1; i <= logN; i++) { - shplonkNuChallengeElements[i] = Fr.unwrap(proof.geminiAEvaluations[i - 1]); - } + // Contribution 8 + { + Fr acc = delta_3; + acc = acc * (delta_3 + minus_one); + acc = acc * (delta_3 + minus_two); + acc = acc * (delta_3 + minus_three); + acc = acc * wire(p, WIRE.Q_RANGE); + acc = acc * domainSep; + evals[9] = acc; + } - uint256 libraIdx = 0; - for (uint256 i = logN + 1; i <= logN + 4; i++) { - shplonkNuChallengeElements[i] = Fr.unwrap(proof.libraPolyEvals[libraIdx]); - libraIdx++; - } + // Contribution 9 + { + Fr acc = delta_4; + acc = acc * (delta_4 + minus_one); + acc = acc * (delta_4 + minus_two); + acc = acc * (delta_4 + minus_three); + acc = acc * wire(p, WIRE.Q_RANGE); + acc = acc * domainSep; + evals[10] = acc; + } + } + + struct EllipticParams { + // Points + Fr x_1; + Fr y_1; + Fr x_2; + Fr y_2; + Fr y_3; + Fr x_3; + // push accumulators into memory + Fr x_double_identity; + } + + function accumulateEllipticRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + EllipticParams memory ep; + ep.x_1 = wire(p, WIRE.W_R); + ep.y_1 = wire(p, WIRE.W_O); + + ep.x_2 = wire(p, WIRE.W_L_SHIFT); + ep.y_2 = wire(p, WIRE.W_4_SHIFT); + ep.y_3 = wire(p, WIRE.W_O_SHIFT); + ep.x_3 = wire(p, WIRE.W_R_SHIFT); + + Fr q_sign = wire(p, WIRE.Q_L); + Fr q_is_double = wire(p, WIRE.Q_M); + + // Contribution 10 point addition, x-coordinate check + // q_elliptic * (x3 + x2 + x1)(x2 - x1)(x2 - x1) - y2^2 - y1^2 + 2(y2y1)*q_sign = 0 + Fr x_diff = (ep.x_2 - ep.x_1); + Fr y1_sqr = (ep.y_1 * ep.y_1); + { + // Move to top + Fr partialEval = domainSep; + + Fr y2_sqr = (ep.y_2 * ep.y_2); + Fr y1y2 = ep.y_1 * ep.y_2 * q_sign; + Fr x_add_identity = (ep.x_3 + ep.x_2 + ep.x_1); + x_add_identity = x_add_identity * x_diff * x_diff; + x_add_identity = x_add_identity - y2_sqr - y1_sqr + y1y2 + y1y2; - nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(shplonkNuChallengeElements))); - (shplonkNu,) = splitChallenge(nextPreviousChallenge); + evals[11] = x_add_identity * partialEval * wire(p, WIRE.Q_ELLIPTIC) * (ONE - q_is_double); } - function generateShplonkZChallenge(Honk.ZKProof memory proof, Fr prevChallenge) - internal - pure - returns (Fr shplonkZ, Fr nextPreviousChallenge) + // Contribution 11 point addition, x-coordinate check + // q_elliptic * (q_sign * y1 + y3)(x2 - x1) + (x3 - x1)(y2 - q_sign * y1) = 0 { - uint256[3] memory shplonkZChallengeElements; - shplonkZChallengeElements[0] = Fr.unwrap(prevChallenge); - - shplonkZChallengeElements[1] = proof.shplonkQ.x; - shplonkZChallengeElements[2] = proof.shplonkQ.y; - - nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(shplonkZChallengeElements))); - (shplonkZ,) = splitChallenge(nextPreviousChallenge); - } - - function loadProof(bytes calldata proof, uint256 logN) internal pure returns (Honk.ZKProof memory p) { - uint256 boundary = 0x0; - - // Pairing point object - for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) { - p.pairingPointObject[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); - boundary += FIELD_ELEMENT_SIZE; - } - // Commitments - p.w1 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - p.w2 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - p.w3 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - - // Lookup / Permutation Helper Commitments - p.lookupReadCounts = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - p.lookupReadTags = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - p.w4 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - p.lookupInverses = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - p.zPerm = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - p.libraCommitments[0] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - - p.libraSum = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); - boundary += FIELD_ELEMENT_SIZE; - // Sumcheck univariates - for (uint256 i = 0; i < logN; i++) { - for (uint256 j = 0; j < ZK_BATCHED_RELATION_PARTIAL_LENGTH; j++) { - p.sumcheckUnivariates[i][j] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); - boundary += FIELD_ELEMENT_SIZE; - } - } - - // Sumcheck evaluations - for (uint256 i = 0; i < NUMBER_OF_ENTITIES; i++) { - p.sumcheckEvaluations[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); - boundary += FIELD_ELEMENT_SIZE; - } - - p.libraEvaluation = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); - boundary += FIELD_ELEMENT_SIZE; + Fr y1_plus_y3 = ep.y_1 + ep.y_3; + Fr y_diff = ep.y_2 * q_sign - ep.y_1; + Fr y_add_identity = y1_plus_y3 * x_diff + (ep.x_3 - ep.x_1) * y_diff; + evals[12] = y_add_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * (ONE - q_is_double); + } - p.libraCommitments[1] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - p.libraCommitments[2] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - p.geminiMaskingPoly = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - p.geminiMaskingEval = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); - boundary += FIELD_ELEMENT_SIZE; + // Contribution 10 point doubling, x-coordinate check + // (x3 + x1 + x1) (4y1*y1) - 9 * x1 * x1 * x1 * x1 = 0 + // N.B. we're using the equivalence x1*x1*x1 === y1*y1 - curve_b to reduce degree by 1 + { + Fr x_pow_4 = (y1_sqr + GRUMPKIN_CURVE_B_PARAMETER_NEGATED) * ep.x_1; + Fr y1_sqr_mul_4 = y1_sqr + y1_sqr; + y1_sqr_mul_4 = y1_sqr_mul_4 + y1_sqr_mul_4; + Fr x1_pow_4_mul_9 = x_pow_4 * Fr.wrap(9); + + // NOTE: pushed into memory (stack >:'( ) + ep.x_double_identity = (ep.x_3 + ep.x_1 + ep.x_1) * y1_sqr_mul_4 - x1_pow_4_mul_9; - // Gemini - // Read gemini fold univariates - for (uint256 i = 0; i < logN - 1; i++) { - p.geminiFoldComms[i] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - } - - // Read gemini a evaluations - for (uint256 i = 0; i < logN; i++) { - p.geminiAEvaluations[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); - boundary += FIELD_ELEMENT_SIZE; - } - - for (uint256 i = 0; i < 4; i++) { - p.libraPolyEvals[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); - boundary += FIELD_ELEMENT_SIZE; - } - - // Shplonk - p.shplonkQ = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); - boundary += GROUP_ELEMENT_SIZE; - // KZG - p.kzgQuotient = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + Fr acc = ep.x_double_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * q_is_double; + evals[11] = evals[11] + acc; } -} -// Field arithmetic libraries + // Contribution 11 point doubling, y-coordinate check + // (y1 + y1) (2y1) - (3 * x1 * x1)(x1 - x3) = 0 + { + Fr x1_sqr_mul_3 = (ep.x_1 + ep.x_1 + ep.x_1) * ep.x_1; + Fr y_double_identity = x1_sqr_mul_3 * (ep.x_1 - ep.x_3) - (ep.y_1 + ep.y_1) * (ep.y_1 + ep.y_3); + evals[12] = evals[12] + y_double_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * q_is_double; + } + } + + // Parameters used within the Memory Relation + // A struct is used to work around stack too deep. This relation has alot of variables + struct MemParams { + Fr memory_record_check; + Fr partial_record_check; + Fr next_gate_access_type; + Fr record_delta; + Fr index_delta; + Fr adjacent_values_match_if_adjacent_indices_match; + Fr adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation; + Fr access_check; + Fr next_gate_access_type_is_boolean; + Fr ROM_consistency_check_identity; + Fr RAM_consistency_check_identity; + Fr timestamp_delta; + Fr RAM_timestamp_check_identity; + Fr memory_identity; + Fr index_is_monotonically_increasing; + } + + function accumulateMemoryRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Honk.RelationParameters memory rp, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + MemParams memory ap; -library RelationsLib { - Fr internal constant GRUMPKIN_CURVE_B_PARAMETER_NEGATED = Fr.wrap(17); // -(-17) - - function accumulateRelationEvaluations( - Fr[NUMBER_OF_ENTITIES] memory purportedEvaluations, - Honk.RelationParameters memory rp, - Fr[NUMBER_OF_ALPHAS] memory alphas, - Fr powPartialEval - ) internal pure returns (Fr accumulator) { - Fr[NUMBER_OF_SUBRELATIONS] memory evaluations; - - // Accumulate all relations in Ultra Honk - each with varying number of subrelations - accumulateArithmeticRelation(purportedEvaluations, evaluations, powPartialEval); - accumulatePermutationRelation(purportedEvaluations, rp, evaluations, powPartialEval); - accumulateLogDerivativeLookupRelation(purportedEvaluations, rp, evaluations, powPartialEval); - accumulateDeltaRangeRelation(purportedEvaluations, evaluations, powPartialEval); - accumulateEllipticRelation(purportedEvaluations, evaluations, powPartialEval); - accumulateMemoryRelation(purportedEvaluations, rp, evaluations, powPartialEval); - accumulateNnfRelation(purportedEvaluations, evaluations, powPartialEval); - accumulatePoseidonExternalRelation(purportedEvaluations, evaluations, powPartialEval); - accumulatePoseidonInternalRelation(purportedEvaluations, evaluations, powPartialEval); - - // batch the subrelations with the alpha challenges to obtain the full honk relation - accumulator = scaleAndBatchSubrelations(evaluations, alphas); - } + /** + * MEMORY + * + * A RAM memory record contains a tuple of the following fields: + * * i: `index` of memory cell being accessed + * * t: `timestamp` of memory cell being accessed (used for RAM, set to 0 for ROM) + * * v: `value` of memory cell being accessed + * * a: `access` type of record. read: 0 = read, 1 = write + * * r: `record` of memory cell. record = access + index * eta + timestamp * eta_two + value * eta_three + * + * A ROM memory record contains a tuple of the following fields: + * * i: `index` of memory cell being accessed + * * v: `value1` of memory cell being accessed (ROM tables can store up to 2 values per index) + * * v2:`value2` of memory cell being accessed (ROM tables can store up to 2 values per index) + * * r: `record` of memory cell. record = index * eta + value2 * eta_two + value1 * eta_three + * + * When performing a read/write access, the values of i, t, v, v2, a, r are stored in the following wires + + * selectors, depending on whether the gate is a RAM read/write or a ROM read + * + * | gate type | i | v2/t | v | a | r | + * | --------- | -- | ----- | -- | -- | -- | + * | ROM | w1 | w2 | w3 | -- | w4 | + * | RAM | w1 | w2 | w3 | qc | w4 | + * + * (for accesses where `index` is a circuit constant, it is assumed the circuit will apply a copy constraint on + * `w2` to fix its value) + * + * + */ /** - * Aesthetic helper function that is used to index by enum into proof.sumcheckEvaluations, it avoids - * the relation checking code being cluttered with uint256 type casting, which is often a different colour in code - * editors, and thus is noisy. + * Memory Record Check + * Partial degree: 1 + * Total degree: 4 + * + * A ROM/ROM access gate can be evaluated with the identity: + * + * qc + w1 \eta + w2 \eta_two + w3 \eta_three - w4 = 0 + * + * For ROM gates, qc = 0 */ - function wire(Fr[NUMBER_OF_ENTITIES] memory p, WIRE _wire) internal pure returns (Fr) { - return p[uint256(_wire)]; - } + ap.memory_record_check = wire(p, WIRE.W_O) * rp.etaThree; + ap.memory_record_check = ap.memory_record_check + (wire(p, WIRE.W_R) * rp.etaTwo); + ap.memory_record_check = ap.memory_record_check + (wire(p, WIRE.W_L) * rp.eta); + ap.memory_record_check = ap.memory_record_check + wire(p, WIRE.Q_C); + ap.partial_record_check = ap.memory_record_check; // used in RAM consistency check; deg 1 or 4 + ap.memory_record_check = ap.memory_record_check - wire(p, WIRE.W_4); - uint256 internal constant NEG_HALF_MODULO_P = 0x183227397098d014dc2822db40c0ac2e9419f4243cdcb848a1f0fac9f8000000; /** - * Ultra Arithmetic Relation + * Contribution 13 & 14 + * ROM Consistency Check + * Partial degree: 1 + * Total degree: 4 + * + * For every ROM read, a set equivalence check is applied between the record witnesses, and a second set of + * records that are sorted. + * + * We apply the following checks for the sorted records: + * + * 1. w1, w2, w3 correctly map to 'index', 'v1, 'v2' for a given record value at w4 + * 2. index values for adjacent records are monotonically increasing + * 3. if, at gate i, index_i == index_{i + 1}, then value1_i == value1_{i + 1} and value2_i == value2_{i + 1} * */ + ap.index_delta = wire(p, WIRE.W_L_SHIFT) - wire(p, WIRE.W_L); + ap.record_delta = wire(p, WIRE.W_4_SHIFT) - wire(p, WIRE.W_4); - function accumulateArithmeticRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { - // Relation 0 - Fr q_arith = wire(p, WIRE.Q_ARITH); - { - Fr neg_half = Fr.wrap(NEG_HALF_MODULO_P); - - Fr accum = (q_arith - Fr.wrap(3)) * (wire(p, WIRE.Q_M) * wire(p, WIRE.W_R) * wire(p, WIRE.W_L)) * neg_half; - accum = accum + (wire(p, WIRE.Q_L) * wire(p, WIRE.W_L)) + (wire(p, WIRE.Q_R) * wire(p, WIRE.W_R)) - + (wire(p, WIRE.Q_O) * wire(p, WIRE.W_O)) + (wire(p, WIRE.Q_4) * wire(p, WIRE.W_4)) + wire(p, WIRE.Q_C); - accum = accum + (q_arith - ONE) * wire(p, WIRE.W_4_SHIFT); - accum = accum * q_arith; - accum = accum * domainSep; - evals[0] = accum; - } - - // Relation 1 - { - Fr accum = wire(p, WIRE.W_L) + wire(p, WIRE.W_4) - wire(p, WIRE.W_L_SHIFT) + wire(p, WIRE.Q_M); - accum = accum * (q_arith - Fr.wrap(2)); - accum = accum * (q_arith - ONE); - accum = accum * q_arith; - accum = accum * domainSep; - evals[1] = accum; - } - } - - function accumulatePermutationRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Honk.RelationParameters memory rp, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { - Fr grand_product_numerator; - Fr grand_product_denominator; - - { - Fr num = wire(p, WIRE.W_L) + wire(p, WIRE.ID_1) * rp.beta + rp.gamma; - num = num * (wire(p, WIRE.W_R) + wire(p, WIRE.ID_2) * rp.beta + rp.gamma); - num = num * (wire(p, WIRE.W_O) + wire(p, WIRE.ID_3) * rp.beta + rp.gamma); - num = num * (wire(p, WIRE.W_4) + wire(p, WIRE.ID_4) * rp.beta + rp.gamma); - - grand_product_numerator = num; - } - { - Fr den = wire(p, WIRE.W_L) + wire(p, WIRE.SIGMA_1) * rp.beta + rp.gamma; - den = den * (wire(p, WIRE.W_R) + wire(p, WIRE.SIGMA_2) * rp.beta + rp.gamma); - den = den * (wire(p, WIRE.W_O) + wire(p, WIRE.SIGMA_3) * rp.beta + rp.gamma); - den = den * (wire(p, WIRE.W_4) + wire(p, WIRE.SIGMA_4) * rp.beta + rp.gamma); - - grand_product_denominator = den; - } - - // Contribution 2 - { - Fr acc = (wire(p, WIRE.Z_PERM) + wire(p, WIRE.LAGRANGE_FIRST)) * grand_product_numerator; - - acc = acc - - ( - (wire(p, WIRE.Z_PERM_SHIFT) + (wire(p, WIRE.LAGRANGE_LAST) * rp.publicInputsDelta)) - * grand_product_denominator - ); - acc = acc * domainSep; - evals[2] = acc; - } - - // Contribution 3 - { - Fr acc = (wire(p, WIRE.LAGRANGE_LAST) * wire(p, WIRE.Z_PERM_SHIFT)) * domainSep; - evals[3] = acc; - } - } - - function accumulateLogDerivativeLookupRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Honk.RelationParameters memory rp, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { - Fr write_term; - Fr read_term; - - // Calculate the write term (the table accumulation) - { - write_term = wire(p, WIRE.TABLE_1) + rp.gamma + (wire(p, WIRE.TABLE_2) * rp.eta) - + (wire(p, WIRE.TABLE_3) * rp.etaTwo) + (wire(p, WIRE.TABLE_4) * rp.etaThree); - } - - // Calculate the write term - { - Fr derived_entry_1 = wire(p, WIRE.W_L) + rp.gamma + (wire(p, WIRE.Q_R) * wire(p, WIRE.W_L_SHIFT)); - Fr derived_entry_2 = wire(p, WIRE.W_R) + wire(p, WIRE.Q_M) * wire(p, WIRE.W_R_SHIFT); - Fr derived_entry_3 = wire(p, WIRE.W_O) + wire(p, WIRE.Q_C) * wire(p, WIRE.W_O_SHIFT); - - read_term = derived_entry_1 + (derived_entry_2 * rp.eta) + (derived_entry_3 * rp.etaTwo) - + (wire(p, WIRE.Q_O) * rp.etaThree); - } - - Fr read_inverse = wire(p, WIRE.LOOKUP_INVERSES) * write_term; - Fr write_inverse = wire(p, WIRE.LOOKUP_INVERSES) * read_term; - - Fr inverse_exists_xor = wire(p, WIRE.LOOKUP_READ_TAGS) + wire(p, WIRE.Q_LOOKUP) - - (wire(p, WIRE.LOOKUP_READ_TAGS) * wire(p, WIRE.Q_LOOKUP)); - - // Inverse calculated correctly relation - Fr accumulatorNone = read_term * write_term * wire(p, WIRE.LOOKUP_INVERSES) - inverse_exists_xor; - accumulatorNone = accumulatorNone * domainSep; - - // Inverse - Fr accumulatorOne = wire(p, WIRE.Q_LOOKUP) * read_inverse - wire(p, WIRE.LOOKUP_READ_COUNTS) * write_inverse; - - Fr read_tag = wire(p, WIRE.LOOKUP_READ_TAGS); - - Fr read_tag_boolean_relation = read_tag * read_tag - read_tag; - - evals[4] = accumulatorNone; - evals[5] = accumulatorOne; - evals[6] = read_tag_boolean_relation * domainSep; - } - - function accumulateDeltaRangeRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { - Fr minus_one = ZERO - ONE; - Fr minus_two = ZERO - Fr.wrap(2); - Fr minus_three = ZERO - Fr.wrap(3); - - // Compute wire differences - Fr delta_1 = wire(p, WIRE.W_R) - wire(p, WIRE.W_L); - Fr delta_2 = wire(p, WIRE.W_O) - wire(p, WIRE.W_R); - Fr delta_3 = wire(p, WIRE.W_4) - wire(p, WIRE.W_O); - Fr delta_4 = wire(p, WIRE.W_L_SHIFT) - wire(p, WIRE.W_4); - - // Contribution 6 - { - Fr acc = delta_1; - acc = acc * (delta_1 + minus_one); - acc = acc * (delta_1 + minus_two); - acc = acc * (delta_1 + minus_three); - acc = acc * wire(p, WIRE.Q_RANGE); - acc = acc * domainSep; - evals[7] = acc; - } - - // Contribution 7 - { - Fr acc = delta_2; - acc = acc * (delta_2 + minus_one); - acc = acc * (delta_2 + minus_two); - acc = acc * (delta_2 + minus_three); - acc = acc * wire(p, WIRE.Q_RANGE); - acc = acc * domainSep; - evals[8] = acc; - } - - // Contribution 8 - { - Fr acc = delta_3; - acc = acc * (delta_3 + minus_one); - acc = acc * (delta_3 + minus_two); - acc = acc * (delta_3 + minus_three); - acc = acc * wire(p, WIRE.Q_RANGE); - acc = acc * domainSep; - evals[9] = acc; - } - - // Contribution 9 - { - Fr acc = delta_4; - acc = acc * (delta_4 + minus_one); - acc = acc * (delta_4 + minus_two); - acc = acc * (delta_4 + minus_three); - acc = acc * wire(p, WIRE.Q_RANGE); - acc = acc * domainSep; - evals[10] = acc; - } - } - - struct EllipticParams { - // Points - Fr x_1; - Fr y_1; - Fr x_2; - Fr y_2; - Fr y_3; - Fr x_3; - // push accumulators into memory - Fr x_double_identity; - } - - function accumulateEllipticRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { - EllipticParams memory ep; - ep.x_1 = wire(p, WIRE.W_R); - ep.y_1 = wire(p, WIRE.W_O); - - ep.x_2 = wire(p, WIRE.W_L_SHIFT); - ep.y_2 = wire(p, WIRE.W_4_SHIFT); - ep.y_3 = wire(p, WIRE.W_O_SHIFT); - ep.x_3 = wire(p, WIRE.W_R_SHIFT); - - Fr q_sign = wire(p, WIRE.Q_L); - Fr q_is_double = wire(p, WIRE.Q_M); - - // Contribution 10 point addition, x-coordinate check - // q_elliptic * (x3 + x2 + x1)(x2 - x1)(x2 - x1) - y2^2 - y1^2 + 2(y2y1)*q_sign = 0 - Fr x_diff = (ep.x_2 - ep.x_1); - Fr y1_sqr = (ep.y_1 * ep.y_1); - { - // Move to top - Fr partialEval = domainSep; - - Fr y2_sqr = (ep.y_2 * ep.y_2); - Fr y1y2 = ep.y_1 * ep.y_2 * q_sign; - Fr x_add_identity = (ep.x_3 + ep.x_2 + ep.x_1); - x_add_identity = x_add_identity * x_diff * x_diff; - x_add_identity = x_add_identity - y2_sqr - y1_sqr + y1y2 + y1y2; - - evals[11] = x_add_identity * partialEval * wire(p, WIRE.Q_ELLIPTIC) * (ONE - q_is_double); - } - - // Contribution 11 point addition, x-coordinate check - // q_elliptic * (q_sign * y1 + y3)(x2 - x1) + (x3 - x1)(y2 - q_sign * y1) = 0 - { - Fr y1_plus_y3 = ep.y_1 + ep.y_3; - Fr y_diff = ep.y_2 * q_sign - ep.y_1; - Fr y_add_identity = y1_plus_y3 * x_diff + (ep.x_3 - ep.x_1) * y_diff; - evals[12] = y_add_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * (ONE - q_is_double); - } - - // Contribution 10 point doubling, x-coordinate check - // (x3 + x1 + x1) (4y1*y1) - 9 * x1 * x1 * x1 * x1 = 0 - // N.B. we're using the equivalence x1*x1*x1 === y1*y1 - curve_b to reduce degree by 1 - { - Fr x_pow_4 = (y1_sqr + GRUMPKIN_CURVE_B_PARAMETER_NEGATED) * ep.x_1; - Fr y1_sqr_mul_4 = y1_sqr + y1_sqr; - y1_sqr_mul_4 = y1_sqr_mul_4 + y1_sqr_mul_4; - Fr x1_pow_4_mul_9 = x_pow_4 * Fr.wrap(9); - - // NOTE: pushed into memory (stack >:'( ) - ep.x_double_identity = (ep.x_3 + ep.x_1 + ep.x_1) * y1_sqr_mul_4 - x1_pow_4_mul_9; - - Fr acc = ep.x_double_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * q_is_double; - evals[11] = evals[11] + acc; - } - - // Contribution 11 point doubling, y-coordinate check - // (y1 + y1) (2y1) - (3 * x1 * x1)(x1 - x3) = 0 - { - Fr x1_sqr_mul_3 = (ep.x_1 + ep.x_1 + ep.x_1) * ep.x_1; - Fr y_double_identity = x1_sqr_mul_3 * (ep.x_1 - ep.x_3) - (ep.y_1 + ep.y_1) * (ep.y_1 + ep.y_3); - evals[12] = evals[12] + y_double_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * q_is_double; - } - } - - // Parameters used within the Memory Relation - // A struct is used to work around stack too deep. This relation has alot of variables - struct MemParams { - Fr memory_record_check; - Fr partial_record_check; - Fr next_gate_access_type; - Fr record_delta; - Fr index_delta; - Fr adjacent_values_match_if_adjacent_indices_match; - Fr adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation; - Fr access_check; - Fr next_gate_access_type_is_boolean; - Fr ROM_consistency_check_identity; - Fr RAM_consistency_check_identity; - Fr timestamp_delta; - Fr RAM_timestamp_check_identity; - Fr memory_identity; - Fr index_is_monotonically_increasing; - } - - function accumulateMemoryRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Honk.RelationParameters memory rp, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { - MemParams memory ap; - - /** - * MEMORY - * - * A RAM memory record contains a tuple of the following fields: - * * i: `index` of memory cell being accessed - * * t: `timestamp` of memory cell being accessed (used for RAM, set to 0 for ROM) - * * v: `value` of memory cell being accessed - * * a: `access` type of record. read: 0 = read, 1 = write - * * r: `record` of memory cell. record = access + index * eta + timestamp * eta_two + value * eta_three - * - * A ROM memory record contains a tuple of the following fields: - * * i: `index` of memory cell being accessed - * * v: `value1` of memory cell being accessed (ROM tables can store up to 2 values per index) - * * v2:`value2` of memory cell being accessed (ROM tables can store up to 2 values per index) - * * r: `record` of memory cell. record = index * eta + value2 * eta_two + value1 * eta_three - * - * When performing a read/write access, the values of i, t, v, v2, a, r are stored in the following wires + - * selectors, depending on whether the gate is a RAM read/write or a ROM read - * - * | gate type | i | v2/t | v | a | r | - * | --------- | -- | ----- | -- | -- | -- | - * | ROM | w1 | w2 | w3 | -- | w4 | - * | RAM | w1 | w2 | w3 | qc | w4 | - * - * (for accesses where `index` is a circuit constant, it is assumed the circuit will apply a copy constraint on - * `w2` to fix its value) - * - * - */ - - /** - * Memory Record Check - * Partial degree: 1 - * Total degree: 4 - * - * A ROM/ROM access gate can be evaluated with the identity: - * - * qc + w1 \eta + w2 \eta_two + w3 \eta_three - w4 = 0 - * - * For ROM gates, qc = 0 - */ - ap.memory_record_check = wire(p, WIRE.W_O) * rp.etaThree; - ap.memory_record_check = ap.memory_record_check + (wire(p, WIRE.W_R) * rp.etaTwo); - ap.memory_record_check = ap.memory_record_check + (wire(p, WIRE.W_L) * rp.eta); - ap.memory_record_check = ap.memory_record_check + wire(p, WIRE.Q_C); - ap.partial_record_check = ap.memory_record_check; // used in RAM consistency check; deg 1 or 4 - ap.memory_record_check = ap.memory_record_check - wire(p, WIRE.W_4); - - /** - * Contribution 13 & 14 - * ROM Consistency Check - * Partial degree: 1 - * Total degree: 4 - * - * For every ROM read, a set equivalence check is applied between the record witnesses, and a second set of - * records that are sorted. - * - * We apply the following checks for the sorted records: - * - * 1. w1, w2, w3 correctly map to 'index', 'v1, 'v2' for a given record value at w4 - * 2. index values for adjacent records are monotonically increasing - * 3. if, at gate i, index_i == index_{i + 1}, then value1_i == value1_{i + 1} and value2_i == value2_{i + 1} - * - */ - ap.index_delta = wire(p, WIRE.W_L_SHIFT) - wire(p, WIRE.W_L); - ap.record_delta = wire(p, WIRE.W_4_SHIFT) - wire(p, WIRE.W_4); - - ap.index_is_monotonically_increasing = ap.index_delta * (ap.index_delta - Fr.wrap(1)); // deg 2 - - ap.adjacent_values_match_if_adjacent_indices_match = (ap.index_delta * MINUS_ONE + ONE) * ap.record_delta; // deg 2 - - evals[14] = ap.adjacent_values_match_if_adjacent_indices_match * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)) - * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5 - evals[15] = ap.index_is_monotonically_increasing * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)) - * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5 - - ap.ROM_consistency_check_identity = ap.memory_record_check * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)); // deg 3 or 7 - - /** - * Contributions 15,16,17 - * RAM Consistency Check - * - * The 'access' type of the record is extracted with the expression `w_4 - ap.partial_record_check` - * (i.e. for an honest Prover `w1 * eta + w2 * eta^2 + w3 * eta^3 - w4 = access`. - * This is validated by requiring `access` to be boolean - * - * For two adjacent entries in the sorted list if _both_ - * A) index values match - * B) adjacent access value is 0 (i.e. next gate is a READ) - * then - * C) both values must match. - * The gate boolean check is - * (A && B) => C === !(A && B) || C === !A || !B || C - * - * N.B. it is the responsibility of the circuit writer to ensure that every RAM cell is initialized - * with a WRITE operation. - */ - Fr access_type = (wire(p, WIRE.W_4) - ap.partial_record_check); // will be 0 or 1 for honest Prover; deg 1 or 4 - ap.access_check = access_type * (access_type - Fr.wrap(1)); // check value is 0 or 1; deg 2 or 8 - - // reverse order we could re-use `ap.partial_record_check` 1 - ((w3' * eta + w2') * eta + w1') * eta - // deg 1 or 4 - ap.next_gate_access_type = wire(p, WIRE.W_O_SHIFT) * rp.etaThree; - ap.next_gate_access_type = ap.next_gate_access_type + (wire(p, WIRE.W_R_SHIFT) * rp.etaTwo); - ap.next_gate_access_type = ap.next_gate_access_type + (wire(p, WIRE.W_L_SHIFT) * rp.eta); - ap.next_gate_access_type = wire(p, WIRE.W_4_SHIFT) - ap.next_gate_access_type; - - Fr value_delta = wire(p, WIRE.W_O_SHIFT) - wire(p, WIRE.W_O); - ap.adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation = - (ap.index_delta * MINUS_ONE + ONE) * value_delta * (ap.next_gate_access_type * MINUS_ONE + ONE); // deg 3 or 6 - - // We can't apply the RAM consistency check identity on the final entry in the sorted list (the wires in the - // next gate would make the identity fail). We need to validate that its 'access type' bool is correct. Can't - // do with an arithmetic gate because of the `eta` factors. We need to check that the *next* gate's access - // type is correct, to cover this edge case - // deg 2 or 4 - ap.next_gate_access_type_is_boolean = - ap.next_gate_access_type * ap.next_gate_access_type - ap.next_gate_access_type; - - // Putting it all together... - evals[16] = ap.adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation - * (wire(p, WIRE.Q_O)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5 or 8 - evals[17] = ap.index_is_monotonically_increasing * (wire(p, WIRE.Q_O)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 - evals[18] = ap.next_gate_access_type_is_boolean * (wire(p, WIRE.Q_O)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 or 6 - - ap.RAM_consistency_check_identity = ap.access_check * (wire(p, WIRE.Q_O)); // deg 3 or 9 - - /** - * RAM Timestamp Consistency Check - * - * | w1 | w2 | w3 | w4 | - * | index | timestamp | timestamp_check | -- | - * - * Let delta_index = index_{i + 1} - index_{i} - * - * Iff delta_index == 0, timestamp_check = timestamp_{i + 1} - timestamp_i - * Else timestamp_check = 0 - */ - ap.timestamp_delta = wire(p, WIRE.W_R_SHIFT) - wire(p, WIRE.W_R); - ap.RAM_timestamp_check_identity = (ap.index_delta * MINUS_ONE + ONE) * ap.timestamp_delta - wire(p, WIRE.W_O); // deg 3 - - /** - * Complete Contribution 12 - * The complete RAM/ROM memory identity - * Partial degree: - */ - ap.memory_identity = ap.ROM_consistency_check_identity; // deg 3 or 6 - ap.memory_identity = - ap.memory_identity + ap.RAM_timestamp_check_identity * (wire(p, WIRE.Q_4) * wire(p, WIRE.Q_L)); // deg 4 - ap.memory_identity = ap.memory_identity + ap.memory_record_check * (wire(p, WIRE.Q_M) * wire(p, WIRE.Q_L)); // deg 3 or 6 - ap.memory_identity = ap.memory_identity + ap.RAM_consistency_check_identity; // deg 3 or 9 - - // (deg 3 or 9) + (deg 4) + (deg 3) - ap.memory_identity = ap.memory_identity * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 or 10 - evals[13] = ap.memory_identity; - } - - // Constants for the Non-native Field relation - Fr constant LIMB_SIZE = Fr.wrap(uint256(1) << 68); - Fr constant SUBLIMB_SHIFT = Fr.wrap(uint256(1) << 14); - - // Parameters used within the Non-Native Field Relation - // A struct is used to work around stack too deep. This relation has alot of variables - struct NnfParams { - Fr limb_subproduct; - Fr non_native_field_gate_1; - Fr non_native_field_gate_2; - Fr non_native_field_gate_3; - Fr limb_accumulator_1; - Fr limb_accumulator_2; - Fr nnf_identity; - } - - function accumulateNnfRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { - NnfParams memory ap; - - /** - * Contribution 12 - * Non native field arithmetic gate 2 - * deg 4 - * - * _ _ - * / _ _ _ 14 \ - * q_2 . q_4 | (w_1 . w_2) + (w_1 . w_2) + (w_1 . w_4 + w_2 . w_3 - w_3) . 2 - w_3 - w_4 | - * \_ _/ - * - * - */ - ap.limb_subproduct = wire(p, WIRE.W_L) * wire(p, WIRE.W_R_SHIFT) + wire(p, WIRE.W_L_SHIFT) * wire(p, WIRE.W_R); - ap.non_native_field_gate_2 = - (wire(p, WIRE.W_L) * wire(p, WIRE.W_4) + wire(p, WIRE.W_R) * wire(p, WIRE.W_O) - wire(p, WIRE.W_O_SHIFT)); - ap.non_native_field_gate_2 = ap.non_native_field_gate_2 * LIMB_SIZE; - ap.non_native_field_gate_2 = ap.non_native_field_gate_2 - wire(p, WIRE.W_4_SHIFT); - ap.non_native_field_gate_2 = ap.non_native_field_gate_2 + ap.limb_subproduct; - ap.non_native_field_gate_2 = ap.non_native_field_gate_2 * wire(p, WIRE.Q_4); - - ap.limb_subproduct = ap.limb_subproduct * LIMB_SIZE; - ap.limb_subproduct = ap.limb_subproduct + (wire(p, WIRE.W_L_SHIFT) * wire(p, WIRE.W_R_SHIFT)); - ap.non_native_field_gate_1 = ap.limb_subproduct; - ap.non_native_field_gate_1 = ap.non_native_field_gate_1 - (wire(p, WIRE.W_O) + wire(p, WIRE.W_4)); - ap.non_native_field_gate_1 = ap.non_native_field_gate_1 * wire(p, WIRE.Q_O); - - ap.non_native_field_gate_3 = ap.limb_subproduct; - ap.non_native_field_gate_3 = ap.non_native_field_gate_3 + wire(p, WIRE.W_4); - ap.non_native_field_gate_3 = ap.non_native_field_gate_3 - (wire(p, WIRE.W_O_SHIFT) + wire(p, WIRE.W_4_SHIFT)); - ap.non_native_field_gate_3 = ap.non_native_field_gate_3 * wire(p, WIRE.Q_M); - - Fr non_native_field_identity = - ap.non_native_field_gate_1 + ap.non_native_field_gate_2 + ap.non_native_field_gate_3; - non_native_field_identity = non_native_field_identity * wire(p, WIRE.Q_R); - - // ((((w2' * 2^14 + w1') * 2^14 + w3) * 2^14 + w2) * 2^14 + w1 - w4) * qm - // deg 2 - ap.limb_accumulator_1 = wire(p, WIRE.W_R_SHIFT) * SUBLIMB_SHIFT; - ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_L_SHIFT); - ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT; - ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_O); - ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT; - ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_R); - ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT; - ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_L); - ap.limb_accumulator_1 = ap.limb_accumulator_1 - wire(p, WIRE.W_4); - ap.limb_accumulator_1 = ap.limb_accumulator_1 * wire(p, WIRE.Q_4); - - // ((((w3' * 2^14 + w2') * 2^14 + w1') * 2^14 + w4) * 2^14 + w3 - w4') * qm - // deg 2 - ap.limb_accumulator_2 = wire(p, WIRE.W_O_SHIFT) * SUBLIMB_SHIFT; - ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_R_SHIFT); - ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT; - ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_L_SHIFT); - ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT; - ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_4); - ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT; - ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_O); - ap.limb_accumulator_2 = ap.limb_accumulator_2 - wire(p, WIRE.W_4_SHIFT); - ap.limb_accumulator_2 = ap.limb_accumulator_2 * wire(p, WIRE.Q_M); - - Fr limb_accumulator_identity = ap.limb_accumulator_1 + ap.limb_accumulator_2; - limb_accumulator_identity = limb_accumulator_identity * wire(p, WIRE.Q_O); // deg 3 - - ap.nnf_identity = non_native_field_identity + limb_accumulator_identity; - ap.nnf_identity = ap.nnf_identity * (wire(p, WIRE.Q_NNF) * domainSep); - evals[19] = ap.nnf_identity; - } - - struct PoseidonExternalParams { - Fr s1; - Fr s2; - Fr s3; - Fr s4; - Fr u1; - Fr u2; - Fr u3; - Fr u4; - Fr t0; - Fr t1; - Fr t2; - Fr t3; - Fr v1; - Fr v2; - Fr v3; - Fr v4; - Fr q_pos_by_scaling; - } - - function accumulatePoseidonExternalRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { - PoseidonExternalParams memory ep; - - ep.s1 = wire(p, WIRE.W_L) + wire(p, WIRE.Q_L); - ep.s2 = wire(p, WIRE.W_R) + wire(p, WIRE.Q_R); - ep.s3 = wire(p, WIRE.W_O) + wire(p, WIRE.Q_O); - ep.s4 = wire(p, WIRE.W_4) + wire(p, WIRE.Q_4); - - ep.u1 = ep.s1 * ep.s1 * ep.s1 * ep.s1 * ep.s1; - ep.u2 = ep.s2 * ep.s2 * ep.s2 * ep.s2 * ep.s2; - ep.u3 = ep.s3 * ep.s3 * ep.s3 * ep.s3 * ep.s3; - ep.u4 = ep.s4 * ep.s4 * ep.s4 * ep.s4 * ep.s4; - // matrix mul v = M_E * u with 14 additions - ep.t0 = ep.u1 + ep.u2; // u_1 + u_2 - ep.t1 = ep.u3 + ep.u4; // u_3 + u_4 - ep.t2 = ep.u2 + ep.u2 + ep.t1; // 2u_2 - // ep.t2 += ep.t1; // 2u_2 + u_3 + u_4 - ep.t3 = ep.u4 + ep.u4 + ep.t0; // 2u_4 - // ep.t3 += ep.t0; // u_1 + u_2 + 2u_4 - ep.v4 = ep.t1 + ep.t1; - ep.v4 = ep.v4 + ep.v4 + ep.t3; - // ep.v4 += ep.t3; // u_1 + u_2 + 4u_3 + 6u_4 - ep.v2 = ep.t0 + ep.t0; - ep.v2 = ep.v2 + ep.v2 + ep.t2; - // ep.v2 += ep.t2; // 4u_1 + 6u_2 + u_3 + u_4 - ep.v1 = ep.t3 + ep.v2; // 5u_1 + 7u_2 + u_3 + 3u_4 - ep.v3 = ep.t2 + ep.v4; // u_1 + 3u_2 + 5u_3 + 7u_4 - - ep.q_pos_by_scaling = wire(p, WIRE.Q_POSEIDON2_EXTERNAL) * domainSep; - evals[20] = evals[20] + ep.q_pos_by_scaling * (ep.v1 - wire(p, WIRE.W_L_SHIFT)); - - evals[21] = evals[21] + ep.q_pos_by_scaling * (ep.v2 - wire(p, WIRE.W_R_SHIFT)); - - evals[22] = evals[22] + ep.q_pos_by_scaling * (ep.v3 - wire(p, WIRE.W_O_SHIFT)); - - evals[23] = evals[23] + ep.q_pos_by_scaling * (ep.v4 - wire(p, WIRE.W_4_SHIFT)); - } - - struct PoseidonInternalParams { - Fr u1; - Fr u2; - Fr u3; - Fr u4; - Fr u_sum; - Fr v1; - Fr v2; - Fr v3; - Fr v4; - Fr s1; - Fr q_pos_by_scaling; - } - - function accumulatePoseidonInternalRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { - PoseidonInternalParams memory ip; - - Fr[4] memory INTERNAL_MATRIX_DIAGONAL = [ - FrLib.from(0x10dc6e9c006ea38b04b1e03b4bd9490c0d03f98929ca1d7fb56821fd19d3b6e7), - FrLib.from(0x0c28145b6a44df3e0149b3d0a30b3bb599df9756d4dd9b84a86b38cfb45a740b), - FrLib.from(0x00544b8338791518b2c7645a50392798b21f75bb60e3596170067d00141cac15), - FrLib.from(0x222c01175718386f2e2e82eb122789e352e105a3b8fa852613bc534433ee428b) - ]; - - // add round constants - ip.s1 = wire(p, WIRE.W_L) + wire(p, WIRE.Q_L); - - // apply s-box round - ip.u1 = ip.s1 * ip.s1 * ip.s1 * ip.s1 * ip.s1; - ip.u2 = wire(p, WIRE.W_R); - ip.u3 = wire(p, WIRE.W_O); - ip.u4 = wire(p, WIRE.W_4); - - // matrix mul with v = M_I * u 4 muls and 7 additions - ip.u_sum = ip.u1 + ip.u2 + ip.u3 + ip.u4; - - ip.q_pos_by_scaling = wire(p, WIRE.Q_POSEIDON2_INTERNAL) * domainSep; - - ip.v1 = ip.u1 * INTERNAL_MATRIX_DIAGONAL[0] + ip.u_sum; - evals[24] = evals[24] + ip.q_pos_by_scaling * (ip.v1 - wire(p, WIRE.W_L_SHIFT)); - - ip.v2 = ip.u2 * INTERNAL_MATRIX_DIAGONAL[1] + ip.u_sum; - evals[25] = evals[25] + ip.q_pos_by_scaling * (ip.v2 - wire(p, WIRE.W_R_SHIFT)); - - ip.v3 = ip.u3 * INTERNAL_MATRIX_DIAGONAL[2] + ip.u_sum; - evals[26] = evals[26] + ip.q_pos_by_scaling * (ip.v3 - wire(p, WIRE.W_O_SHIFT)); - - ip.v4 = ip.u4 * INTERNAL_MATRIX_DIAGONAL[3] + ip.u_sum; - evals[27] = evals[27] + ip.q_pos_by_scaling * (ip.v4 - wire(p, WIRE.W_4_SHIFT)); - } - - function scaleAndBatchSubrelations( - Fr[NUMBER_OF_SUBRELATIONS] memory evaluations, - Fr[NUMBER_OF_ALPHAS] memory subrelationChallenges - ) internal pure returns (Fr accumulator) { - accumulator = evaluations[0]; - - for (uint256 i = 1; i < NUMBER_OF_SUBRELATIONS; ++i) { - accumulator = accumulator + evaluations[i] * subrelationChallenges[i - 1]; - } - } + ap.index_is_monotonically_increasing = ap.index_delta * (ap.index_delta - Fr.wrap(1)); // deg 2 + + ap.adjacent_values_match_if_adjacent_indices_match = (ap.index_delta * MINUS_ONE + ONE) * ap.record_delta; // deg 2 + + evals[14] = + ap.adjacent_values_match_if_adjacent_indices_match * + (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)) * + (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5 + evals[15] = ap.index_is_monotonically_increasing * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5 + + ap.ROM_consistency_check_identity = ap.memory_record_check * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)); // deg 3 or 7 + + /** + * Contributions 15,16,17 + * RAM Consistency Check + * + * The 'access' type of the record is extracted with the expression `w_4 - ap.partial_record_check` + * (i.e. for an honest Prover `w1 * eta + w2 * eta^2 + w3 * eta^3 - w4 = access`. + * This is validated by requiring `access` to be boolean + * + * For two adjacent entries in the sorted list if _both_ + * A) index values match + * B) adjacent access value is 0 (i.e. next gate is a READ) + * then + * C) both values must match. + * The gate boolean check is + * (A && B) => C === !(A && B) || C === !A || !B || C + * + * N.B. it is the responsibility of the circuit writer to ensure that every RAM cell is initialized + * with a WRITE operation. + */ + Fr access_type = (wire(p, WIRE.W_4) - ap.partial_record_check); // will be 0 or 1 for honest Prover; deg 1 or 4 + ap.access_check = access_type * (access_type - Fr.wrap(1)); // check value is 0 or 1; deg 2 or 8 + + // reverse order we could re-use `ap.partial_record_check` 1 - ((w3' * eta + w2') * eta + w1') * eta + // deg 1 or 4 + ap.next_gate_access_type = wire(p, WIRE.W_O_SHIFT) * rp.etaThree; + ap.next_gate_access_type = ap.next_gate_access_type + (wire(p, WIRE.W_R_SHIFT) * rp.etaTwo); + ap.next_gate_access_type = ap.next_gate_access_type + (wire(p, WIRE.W_L_SHIFT) * rp.eta); + ap.next_gate_access_type = wire(p, WIRE.W_4_SHIFT) - ap.next_gate_access_type; + + Fr value_delta = wire(p, WIRE.W_O_SHIFT) - wire(p, WIRE.W_O); + ap.adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation = + (ap.index_delta * MINUS_ONE + ONE) * + value_delta * + (ap.next_gate_access_type * MINUS_ONE + ONE); // deg 3 or 6 + + // We can't apply the RAM consistency check identity on the final entry in the sorted list (the wires in the + // next gate would make the identity fail). We need to validate that its 'access type' bool is correct. Can't + // do with an arithmetic gate because of the `eta` factors. We need to check that the *next* gate's access + // type is correct, to cover this edge case + // deg 2 or 4 + ap.next_gate_access_type_is_boolean = ap.next_gate_access_type * ap.next_gate_access_type - ap.next_gate_access_type; + + // Putting it all together... + evals[16] = + ap.adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation * + (wire(p, WIRE.Q_O)) * + (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5 or 8 + evals[17] = ap.index_is_monotonically_increasing * (wire(p, WIRE.Q_O)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 + evals[18] = ap.next_gate_access_type_is_boolean * (wire(p, WIRE.Q_O)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 or 6 + + ap.RAM_consistency_check_identity = ap.access_check * (wire(p, WIRE.Q_O)); // deg 3 or 9 + + /** + * RAM Timestamp Consistency Check + * + * | w1 | w2 | w3 | w4 | + * | index | timestamp | timestamp_check | -- | + * + * Let delta_index = index_{i + 1} - index_{i} + * + * Iff delta_index == 0, timestamp_check = timestamp_{i + 1} - timestamp_i + * Else timestamp_check = 0 + */ + ap.timestamp_delta = wire(p, WIRE.W_R_SHIFT) - wire(p, WIRE.W_R); + ap.RAM_timestamp_check_identity = (ap.index_delta * MINUS_ONE + ONE) * ap.timestamp_delta - wire(p, WIRE.W_O); // deg 3 + + /** + * Complete Contribution 12 + * The complete RAM/ROM memory identity + * Partial degree: + */ + ap.memory_identity = ap.ROM_consistency_check_identity; // deg 3 or 6 + ap.memory_identity = ap.memory_identity + ap.RAM_timestamp_check_identity * (wire(p, WIRE.Q_4) * wire(p, WIRE.Q_L)); // deg 4 + ap.memory_identity = ap.memory_identity + ap.memory_record_check * (wire(p, WIRE.Q_M) * wire(p, WIRE.Q_L)); // deg 3 or 6 + ap.memory_identity = ap.memory_identity + ap.RAM_consistency_check_identity; // deg 3 or 9 + + // (deg 3 or 9) + (deg 4) + (deg 3) + ap.memory_identity = ap.memory_identity * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 or 10 + evals[13] = ap.memory_identity; + } + + // Constants for the Non-native Field relation + Fr constant LIMB_SIZE = Fr.wrap(uint256(1) << 68); + Fr constant SUBLIMB_SHIFT = Fr.wrap(uint256(1) << 14); + + // Parameters used within the Non-Native Field Relation + // A struct is used to work around stack too deep. This relation has alot of variables + struct NnfParams { + Fr limb_subproduct; + Fr non_native_field_gate_1; + Fr non_native_field_gate_2; + Fr non_native_field_gate_3; + Fr limb_accumulator_1; + Fr limb_accumulator_2; + Fr nnf_identity; + } + + function accumulateNnfRelation(Fr[NUMBER_OF_ENTITIES] memory p, Fr[NUMBER_OF_SUBRELATIONS] memory evals, Fr domainSep) internal pure { + NnfParams memory ap; + + /** + * Contribution 12 + * Non native field arithmetic gate 2 + * deg 4 + * + * _ _ + * / _ _ _ 14 \ + * q_2 . q_4 | (w_1 . w_2) + (w_1 . w_2) + (w_1 . w_4 + w_2 . w_3 - w_3) . 2 - w_3 - w_4 | + * \_ _/ + * + * + */ + ap.limb_subproduct = wire(p, WIRE.W_L) * wire(p, WIRE.W_R_SHIFT) + wire(p, WIRE.W_L_SHIFT) * wire(p, WIRE.W_R); + ap.non_native_field_gate_2 = (wire(p, WIRE.W_L) * wire(p, WIRE.W_4) + wire(p, WIRE.W_R) * wire(p, WIRE.W_O) - wire(p, WIRE.W_O_SHIFT)); + ap.non_native_field_gate_2 = ap.non_native_field_gate_2 * LIMB_SIZE; + ap.non_native_field_gate_2 = ap.non_native_field_gate_2 - wire(p, WIRE.W_4_SHIFT); + ap.non_native_field_gate_2 = ap.non_native_field_gate_2 + ap.limb_subproduct; + ap.non_native_field_gate_2 = ap.non_native_field_gate_2 * wire(p, WIRE.Q_4); + + ap.limb_subproduct = ap.limb_subproduct * LIMB_SIZE; + ap.limb_subproduct = ap.limb_subproduct + (wire(p, WIRE.W_L_SHIFT) * wire(p, WIRE.W_R_SHIFT)); + ap.non_native_field_gate_1 = ap.limb_subproduct; + ap.non_native_field_gate_1 = ap.non_native_field_gate_1 - (wire(p, WIRE.W_O) + wire(p, WIRE.W_4)); + ap.non_native_field_gate_1 = ap.non_native_field_gate_1 * wire(p, WIRE.Q_O); + + ap.non_native_field_gate_3 = ap.limb_subproduct; + ap.non_native_field_gate_3 = ap.non_native_field_gate_3 + wire(p, WIRE.W_4); + ap.non_native_field_gate_3 = ap.non_native_field_gate_3 - (wire(p, WIRE.W_O_SHIFT) + wire(p, WIRE.W_4_SHIFT)); + ap.non_native_field_gate_3 = ap.non_native_field_gate_3 * wire(p, WIRE.Q_M); + + Fr non_native_field_identity = ap.non_native_field_gate_1 + ap.non_native_field_gate_2 + ap.non_native_field_gate_3; + non_native_field_identity = non_native_field_identity * wire(p, WIRE.Q_R); + + // ((((w2' * 2^14 + w1') * 2^14 + w3) * 2^14 + w2) * 2^14 + w1 - w4) * qm + // deg 2 + ap.limb_accumulator_1 = wire(p, WIRE.W_R_SHIFT) * SUBLIMB_SHIFT; + ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_L_SHIFT); + ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT; + ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_O); + ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT; + ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_R); + ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT; + ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_L); + ap.limb_accumulator_1 = ap.limb_accumulator_1 - wire(p, WIRE.W_4); + ap.limb_accumulator_1 = ap.limb_accumulator_1 * wire(p, WIRE.Q_4); + + // ((((w3' * 2^14 + w2') * 2^14 + w1') * 2^14 + w4) * 2^14 + w3 - w4') * qm + // deg 2 + ap.limb_accumulator_2 = wire(p, WIRE.W_O_SHIFT) * SUBLIMB_SHIFT; + ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_R_SHIFT); + ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT; + ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_L_SHIFT); + ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT; + ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_4); + ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT; + ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_O); + ap.limb_accumulator_2 = ap.limb_accumulator_2 - wire(p, WIRE.W_4_SHIFT); + ap.limb_accumulator_2 = ap.limb_accumulator_2 * wire(p, WIRE.Q_M); + + Fr limb_accumulator_identity = ap.limb_accumulator_1 + ap.limb_accumulator_2; + limb_accumulator_identity = limb_accumulator_identity * wire(p, WIRE.Q_O); // deg 3 + + ap.nnf_identity = non_native_field_identity + limb_accumulator_identity; + ap.nnf_identity = ap.nnf_identity * (wire(p, WIRE.Q_NNF) * domainSep); + evals[19] = ap.nnf_identity; + } + + struct PoseidonExternalParams { + Fr s1; + Fr s2; + Fr s3; + Fr s4; + Fr u1; + Fr u2; + Fr u3; + Fr u4; + Fr t0; + Fr t1; + Fr t2; + Fr t3; + Fr v1; + Fr v2; + Fr v3; + Fr v4; + Fr q_pos_by_scaling; + } + + function accumulatePoseidonExternalRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + PoseidonExternalParams memory ep; + + ep.s1 = wire(p, WIRE.W_L) + wire(p, WIRE.Q_L); + ep.s2 = wire(p, WIRE.W_R) + wire(p, WIRE.Q_R); + ep.s3 = wire(p, WIRE.W_O) + wire(p, WIRE.Q_O); + ep.s4 = wire(p, WIRE.W_4) + wire(p, WIRE.Q_4); + + ep.u1 = ep.s1 * ep.s1 * ep.s1 * ep.s1 * ep.s1; + ep.u2 = ep.s2 * ep.s2 * ep.s2 * ep.s2 * ep.s2; + ep.u3 = ep.s3 * ep.s3 * ep.s3 * ep.s3 * ep.s3; + ep.u4 = ep.s4 * ep.s4 * ep.s4 * ep.s4 * ep.s4; + // matrix mul v = M_E * u with 14 additions + ep.t0 = ep.u1 + ep.u2; // u_1 + u_2 + ep.t1 = ep.u3 + ep.u4; // u_3 + u_4 + ep.t2 = ep.u2 + ep.u2 + ep.t1; // 2u_2 + // ep.t2 += ep.t1; // 2u_2 + u_3 + u_4 + ep.t3 = ep.u4 + ep.u4 + ep.t0; // 2u_4 + // ep.t3 += ep.t0; // u_1 + u_2 + 2u_4 + ep.v4 = ep.t1 + ep.t1; + ep.v4 = ep.v4 + ep.v4 + ep.t3; + // ep.v4 += ep.t3; // u_1 + u_2 + 4u_3 + 6u_4 + ep.v2 = ep.t0 + ep.t0; + ep.v2 = ep.v2 + ep.v2 + ep.t2; + // ep.v2 += ep.t2; // 4u_1 + 6u_2 + u_3 + u_4 + ep.v1 = ep.t3 + ep.v2; // 5u_1 + 7u_2 + u_3 + 3u_4 + ep.v3 = ep.t2 + ep.v4; // u_1 + 3u_2 + 5u_3 + 7u_4 + + ep.q_pos_by_scaling = wire(p, WIRE.Q_POSEIDON2_EXTERNAL) * domainSep; + evals[20] = evals[20] + ep.q_pos_by_scaling * (ep.v1 - wire(p, WIRE.W_L_SHIFT)); + + evals[21] = evals[21] + ep.q_pos_by_scaling * (ep.v2 - wire(p, WIRE.W_R_SHIFT)); + + evals[22] = evals[22] + ep.q_pos_by_scaling * (ep.v3 - wire(p, WIRE.W_O_SHIFT)); + + evals[23] = evals[23] + ep.q_pos_by_scaling * (ep.v4 - wire(p, WIRE.W_4_SHIFT)); + } + + struct PoseidonInternalParams { + Fr u1; + Fr u2; + Fr u3; + Fr u4; + Fr u_sum; + Fr v1; + Fr v2; + Fr v3; + Fr v4; + Fr s1; + Fr q_pos_by_scaling; + } + + function accumulatePoseidonInternalRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + PoseidonInternalParams memory ip; + + Fr[4] memory INTERNAL_MATRIX_DIAGONAL = [ + FrLib.from(0x10dc6e9c006ea38b04b1e03b4bd9490c0d03f98929ca1d7fb56821fd19d3b6e7), + FrLib.from(0x0c28145b6a44df3e0149b3d0a30b3bb599df9756d4dd9b84a86b38cfb45a740b), + FrLib.from(0x00544b8338791518b2c7645a50392798b21f75bb60e3596170067d00141cac15), + FrLib.from(0x222c01175718386f2e2e82eb122789e352e105a3b8fa852613bc534433ee428b) + ]; + + // add round constants + ip.s1 = wire(p, WIRE.W_L) + wire(p, WIRE.Q_L); + + // apply s-box round + ip.u1 = ip.s1 * ip.s1 * ip.s1 * ip.s1 * ip.s1; + ip.u2 = wire(p, WIRE.W_R); + ip.u3 = wire(p, WIRE.W_O); + ip.u4 = wire(p, WIRE.W_4); + + // matrix mul with v = M_I * u 4 muls and 7 additions + ip.u_sum = ip.u1 + ip.u2 + ip.u3 + ip.u4; + + ip.q_pos_by_scaling = wire(p, WIRE.Q_POSEIDON2_INTERNAL) * domainSep; + + ip.v1 = ip.u1 * INTERNAL_MATRIX_DIAGONAL[0] + ip.u_sum; + evals[24] = evals[24] + ip.q_pos_by_scaling * (ip.v1 - wire(p, WIRE.W_L_SHIFT)); + + ip.v2 = ip.u2 * INTERNAL_MATRIX_DIAGONAL[1] + ip.u_sum; + evals[25] = evals[25] + ip.q_pos_by_scaling * (ip.v2 - wire(p, WIRE.W_R_SHIFT)); + + ip.v3 = ip.u3 * INTERNAL_MATRIX_DIAGONAL[2] + ip.u_sum; + evals[26] = evals[26] + ip.q_pos_by_scaling * (ip.v3 - wire(p, WIRE.W_O_SHIFT)); + + ip.v4 = ip.u4 * INTERNAL_MATRIX_DIAGONAL[3] + ip.u_sum; + evals[27] = evals[27] + ip.q_pos_by_scaling * (ip.v4 - wire(p, WIRE.W_4_SHIFT)); + } + + function scaleAndBatchSubrelations( + Fr[NUMBER_OF_SUBRELATIONS] memory evaluations, + Fr[NUMBER_OF_ALPHAS] memory subrelationChallenges + ) internal pure returns (Fr accumulator) { + accumulator = evaluations[0]; + + for (uint256 i = 1; i < NUMBER_OF_SUBRELATIONS; ++i) { + accumulator = accumulator + evaluations[i] * subrelationChallenges[i - 1]; + } + } } // Field arithmetic libraries - prevent littering the code with modmul / addmul library CommitmentSchemeLib { - using FrLib for Fr; - - // Avoid stack too deep - struct ShpleminiIntermediates { - Fr unshiftedScalar; - Fr shiftedScalar; - Fr unshiftedScalarNeg; - Fr shiftedScalarNeg; - // Scalar to be multiplied by [1]₁ - Fr constantTermAccumulator; - // Accumulator for powers of rho - Fr batchingChallenge; - // Linear combination of multilinear (sumcheck) evaluations and powers of rho - Fr batchedEvaluation; - Fr[4] denominators; - Fr[4] batchingScalars; - // 1/(z - r^{2^i}) for i = 0, ..., logSize, dynamically updated - Fr posInvertedDenominator; - // 1/(z + r^{2^i}) for i = 0, ..., logSize, dynamically updated - Fr negInvertedDenominator; - // ν^{2i} * 1/(z - r^{2^i}) - Fr scalingFactorPos; - // ν^{2i+1} * 1/(z + r^{2^i}) - Fr scalingFactorNeg; - // Fold_i(r^{2^i}) reconstructed by Verifier - Fr[] foldPosEvaluations; - } - - function computeSquares(Fr r, uint256 logN) internal pure returns (Fr[] memory) { - Fr[] memory squares = new Fr[](logN); - squares[0] = r; - for (uint256 i = 1; i < logN; ++i) { - squares[i] = squares[i - 1].sqr(); - } - return squares; - } - // Compute the evaluations Aₗ(r^{2ˡ}) for l = 0, ..., m-1 - - function computeFoldPosEvaluations( - Fr[CONST_PROOF_SIZE_LOG_N] memory sumcheckUChallenges, - Fr batchedEvalAccumulator, - Fr[CONST_PROOF_SIZE_LOG_N] memory geminiEvaluations, - Fr[] memory geminiEvalChallengePowers, - uint256 logSize - ) internal view returns (Fr[] memory) { - Fr[] memory foldPosEvaluations = new Fr[](logSize); - for (uint256 i = logSize; i > 0; --i) { - Fr challengePower = geminiEvalChallengePowers[i - 1]; - Fr u = sumcheckUChallenges[i - 1]; - - Fr batchedEvalRoundAcc = ( - (challengePower * batchedEvalAccumulator * Fr.wrap(2)) - - geminiEvaluations[i - 1] * (challengePower * (ONE - u) - u) - ); - // Divide by the denominator - batchedEvalRoundAcc = batchedEvalRoundAcc * (challengePower * (ONE - u) + u).invert(); - - batchedEvalAccumulator = batchedEvalRoundAcc; - foldPosEvaluations[i - 1] = batchedEvalRoundAcc; - } - return foldPosEvaluations; - } + using FrLib for Fr; + + // Avoid stack too deep + struct ShpleminiIntermediates { + Fr unshiftedScalar; + Fr shiftedScalar; + Fr unshiftedScalarNeg; + Fr shiftedScalarNeg; + // Scalar to be multiplied by [1]₁ + Fr constantTermAccumulator; + // Accumulator for powers of rho + Fr batchingChallenge; + // Linear combination of multilinear (sumcheck) evaluations and powers of rho + Fr batchedEvaluation; + Fr[4] denominators; + Fr[4] batchingScalars; + // 1/(z - r^{2^i}) for i = 0, ..., logSize, dynamically updated + Fr posInvertedDenominator; + // 1/(z + r^{2^i}) for i = 0, ..., logSize, dynamically updated + Fr negInvertedDenominator; + // ν^{2i} * 1/(z - r^{2^i}) + Fr scalingFactorPos; + // ν^{2i+1} * 1/(z + r^{2^i}) + Fr scalingFactorNeg; + // Fold_i(r^{2^i}) reconstructed by Verifier + Fr[] foldPosEvaluations; + } + + function computeSquares(Fr r, uint256 logN) internal pure returns (Fr[] memory) { + Fr[] memory squares = new Fr[](logN); + squares[0] = r; + for (uint256 i = 1; i < logN; ++i) { + squares[i] = squares[i - 1].sqr(); + } + return squares; + } + // Compute the evaluations Aₗ(r^{2ˡ}) for l = 0, ..., m-1 + + function computeFoldPosEvaluations( + Fr[CONST_PROOF_SIZE_LOG_N] memory sumcheckUChallenges, + Fr batchedEvalAccumulator, + Fr[CONST_PROOF_SIZE_LOG_N] memory geminiEvaluations, + Fr[] memory geminiEvalChallengePowers, + uint256 logSize + ) internal view returns (Fr[] memory) { + Fr[] memory foldPosEvaluations = new Fr[](logSize); + for (uint256 i = logSize; i > 0; --i) { + Fr challengePower = geminiEvalChallengePowers[i - 1]; + Fr u = sumcheckUChallenges[i - 1]; + + Fr batchedEvalRoundAcc = ((challengePower * batchedEvalAccumulator * Fr.wrap(2)) - + geminiEvaluations[i - 1] * + (challengePower * (ONE - u) - u)); + // Divide by the denominator + batchedEvalRoundAcc = batchedEvalRoundAcc * (challengePower * (ONE - u) + u).invert(); + + batchedEvalAccumulator = batchedEvalRoundAcc; + foldPosEvaluations[i - 1] = batchedEvalRoundAcc; + } + return foldPosEvaluations; + } } uint256 constant Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; // EC group order. F_q function bytes32ToString(bytes32 value) pure returns (string memory result) { - bytes memory alphabet = "0123456789abcdef"; - - bytes memory str = new bytes(66); - str[0] = "0"; - str[1] = "x"; - for (uint256 i = 0; i < 32; i++) { - str[2 + i * 2] = alphabet[uint8(value[i] >> 4)]; - str[3 + i * 2] = alphabet[uint8(value[i] & 0x0f)]; - } - result = string(str); + bytes memory alphabet = "0123456789abcdef"; + + bytes memory str = new bytes(66); + str[0] = "0"; + str[1] = "x"; + for (uint256 i = 0; i < 32; i++) { + str[2 + i * 2] = alphabet[uint8(value[i] >> 4)]; + str[3 + i * 2] = alphabet[uint8(value[i] & 0x0f)]; + } + result = string(str); } // Fr utility function bytesToFr(bytes calldata proofSection) pure returns (Fr scalar) { - scalar = FrLib.fromBytes32(bytes32(proofSection)); + scalar = FrLib.fromBytes32(bytes32(proofSection)); } // EC Point utilities function bytesToG1Point(bytes calldata proofSection) pure returns (Honk.G1Point memory point) { - point = Honk.G1Point({ - x: uint256(bytes32(proofSection[0x00:0x20])) % Q, - y: uint256(bytes32(proofSection[0x20:0x40])) % Q - }); + point = Honk.G1Point({ x: uint256(bytes32(proofSection[0x00:0x20])) % Q, y: uint256(bytes32(proofSection[0x20:0x40])) % Q }); } function negateInplace(Honk.G1Point memory point) pure returns (Honk.G1Point memory) { - point.y = (Q - point.y) % Q; - return point; + point.y = (Q - point.y) % Q; + return point; } /** @@ -1648,33 +1641,32 @@ function negateInplace(Honk.G1Point memory point) pure returns (Honk.G1Point mem * @return lhs * @return rhs */ -function convertPairingPointsToG1(Fr[PAIRING_POINTS_SIZE] memory pairingPoints) - pure - returns (Honk.G1Point memory lhs, Honk.G1Point memory rhs) -{ - uint256 lhsX = Fr.unwrap(pairingPoints[0]); - lhsX |= Fr.unwrap(pairingPoints[1]) << 68; - lhsX |= Fr.unwrap(pairingPoints[2]) << 136; - lhsX |= Fr.unwrap(pairingPoints[3]) << 204; - lhs.x = lhsX; - - uint256 lhsY = Fr.unwrap(pairingPoints[4]); - lhsY |= Fr.unwrap(pairingPoints[5]) << 68; - lhsY |= Fr.unwrap(pairingPoints[6]) << 136; - lhsY |= Fr.unwrap(pairingPoints[7]) << 204; - lhs.y = lhsY; - - uint256 rhsX = Fr.unwrap(pairingPoints[8]); - rhsX |= Fr.unwrap(pairingPoints[9]) << 68; - rhsX |= Fr.unwrap(pairingPoints[10]) << 136; - rhsX |= Fr.unwrap(pairingPoints[11]) << 204; - rhs.x = rhsX; - - uint256 rhsY = Fr.unwrap(pairingPoints[12]); - rhsY |= Fr.unwrap(pairingPoints[13]) << 68; - rhsY |= Fr.unwrap(pairingPoints[14]) << 136; - rhsY |= Fr.unwrap(pairingPoints[15]) << 204; - rhs.y = rhsY; +function convertPairingPointsToG1( + Fr[PAIRING_POINTS_SIZE] memory pairingPoints +) pure returns (Honk.G1Point memory lhs, Honk.G1Point memory rhs) { + uint256 lhsX = Fr.unwrap(pairingPoints[0]); + lhsX |= Fr.unwrap(pairingPoints[1]) << 68; + lhsX |= Fr.unwrap(pairingPoints[2]) << 136; + lhsX |= Fr.unwrap(pairingPoints[3]) << 204; + lhs.x = lhsX; + + uint256 lhsY = Fr.unwrap(pairingPoints[4]); + lhsY |= Fr.unwrap(pairingPoints[5]) << 68; + lhsY |= Fr.unwrap(pairingPoints[6]) << 136; + lhsY |= Fr.unwrap(pairingPoints[7]) << 204; + lhs.y = lhsY; + + uint256 rhsX = Fr.unwrap(pairingPoints[8]); + rhsX |= Fr.unwrap(pairingPoints[9]) << 68; + rhsX |= Fr.unwrap(pairingPoints[10]) << 136; + rhsX |= Fr.unwrap(pairingPoints[11]) << 204; + rhs.x = rhsX; + + uint256 rhsY = Fr.unwrap(pairingPoints[12]); + rhsY |= Fr.unwrap(pairingPoints[13]) << 68; + rhsY |= Fr.unwrap(pairingPoints[14]) << 136; + rhsY |= Fr.unwrap(pairingPoints[15]) << 204; + rhs.y = rhsY; } /** @@ -1686,32 +1678,32 @@ function convertPairingPointsToG1(Fr[PAIRING_POINTS_SIZE] memory pairingPoints) * @return recursionSeparator The recursion separator - generated from hashing the above. */ function generateRecursionSeparator( - Fr[PAIRING_POINTS_SIZE] memory proofPairingPoints, - Honk.G1Point memory accLhs, - Honk.G1Point memory accRhs + Fr[PAIRING_POINTS_SIZE] memory proofPairingPoints, + Honk.G1Point memory accLhs, + Honk.G1Point memory accRhs ) pure returns (Fr recursionSeparator) { - // hash the proof aggregated X - // hash the proof aggregated Y - // hash the accum X - // hash the accum Y + // hash the proof aggregated X + // hash the proof aggregated Y + // hash the accum X + // hash the accum Y - (Honk.G1Point memory proofLhs, Honk.G1Point memory proofRhs) = convertPairingPointsToG1(proofPairingPoints); + (Honk.G1Point memory proofLhs, Honk.G1Point memory proofRhs) = convertPairingPointsToG1(proofPairingPoints); - uint256[8] memory recursionSeparatorElements; + uint256[8] memory recursionSeparatorElements; - // Proof points - recursionSeparatorElements[0] = proofLhs.x; - recursionSeparatorElements[1] = proofLhs.y; - recursionSeparatorElements[2] = proofRhs.x; - recursionSeparatorElements[3] = proofRhs.y; + // Proof points + recursionSeparatorElements[0] = proofLhs.x; + recursionSeparatorElements[1] = proofLhs.y; + recursionSeparatorElements[2] = proofRhs.x; + recursionSeparatorElements[3] = proofRhs.y; - // Accumulator points - recursionSeparatorElements[4] = accLhs.x; - recursionSeparatorElements[5] = accLhs.y; - recursionSeparatorElements[6] = accRhs.x; - recursionSeparatorElements[7] = accRhs.y; + // Accumulator points + recursionSeparatorElements[4] = accLhs.x; + recursionSeparatorElements[5] = accLhs.y; + recursionSeparatorElements[6] = accRhs.x; + recursionSeparatorElements[7] = accRhs.y; - recursionSeparator = FrLib.fromBytes32(keccak256(abi.encodePacked(recursionSeparatorElements))); + recursionSeparator = FrLib.fromBytes32(keccak256(abi.encodePacked(recursionSeparatorElements))); } /** @@ -1723,16 +1715,17 @@ function generateRecursionSeparator( * @param recursionSeperator The separator to use for the multiplication. * @return `(recursionSeperator * basePoint) + other`. */ -function mulWithSeperator(Honk.G1Point memory basePoint, Honk.G1Point memory other, Fr recursionSeperator) - view - returns (Honk.G1Point memory) -{ - Honk.G1Point memory result; +function mulWithSeperator( + Honk.G1Point memory basePoint, + Honk.G1Point memory other, + Fr recursionSeperator +) view returns (Honk.G1Point memory) { + Honk.G1Point memory result; - result = ecMul(recursionSeperator, basePoint); - result = ecAdd(result, other); + result = ecMul(recursionSeperator, basePoint); + result = ecAdd(result, other); - return result; + return result; } /** @@ -1744,41 +1737,41 @@ function mulWithSeperator(Honk.G1Point memory basePoint, Honk.G1Point memory oth * @return result The result of the multiplication. */ function ecMul(Fr value, Honk.G1Point memory point) view returns (Honk.G1Point memory) { - Honk.G1Point memory result; - - assembly { - let free := mload(0x40) - // Write the point into memory (two 32 byte words) - // Memory layout: - // Address | value - // free | point.x - // free + 0x20| point.y - mstore(free, mload(point)) - mstore(add(free, 0x20), mload(add(point, 0x20))) - // Write the scalar into memory (one 32 byte word) - // Memory layout: - // Address | value - // free + 0x40| value - mstore(add(free, 0x40), value) - - // Call the ecMul precompile, it takes in the following - // [point.x, point.y, scalar], and returns the result back into the free memory location. - let success := staticcall(gas(), 0x07, free, 0x60, free, 0x40) - if iszero(success) { - revert(0, 0) - } - // Copy the result of the multiplication back into the result memory location. - // Memory layout: - // Address | value - // result | result.x - // result + 0x20| result.y - mstore(result, mload(free)) - mstore(add(result, 0x20), mload(add(free, 0x20))) - - mstore(0x40, add(free, 0x60)) - } - - return result; + Honk.G1Point memory result; + + assembly { + let free := mload(0x40) + // Write the point into memory (two 32 byte words) + // Memory layout: + // Address | value + // free | point.x + // free + 0x20| point.y + mstore(free, mload(point)) + mstore(add(free, 0x20), mload(add(point, 0x20))) + // Write the scalar into memory (one 32 byte word) + // Memory layout: + // Address | value + // free + 0x40| value + mstore(add(free, 0x40), value) + + // Call the ecMul precompile, it takes in the following + // [point.x, point.y, scalar], and returns the result back into the free memory location. + let success := staticcall(gas(), 0x07, free, 0x60, free, 0x40) + if iszero(success) { + revert(0, 0) + } + // Copy the result of the multiplication back into the result memory location. + // Memory layout: + // Address | value + // result | result.x + // result + 0x20| result.y + mstore(result, mload(free)) + mstore(add(result, 0x20), mload(add(free, 0x20))) + + mstore(0x40, add(free, 0x60)) + } + + return result; } /** @@ -1790,649 +1783,637 @@ function ecMul(Fr value, Honk.G1Point memory point) view returns (Honk.G1Point m * @return result The result of the addition. */ function ecAdd(Honk.G1Point memory lhs, Honk.G1Point memory rhs) view returns (Honk.G1Point memory) { - Honk.G1Point memory result; - - assembly { - let free := mload(0x40) - // Write lhs into memory (two 32 byte words) - // Memory layout: - // Address | value - // free | lhs.x - // free + 0x20| lhs.y - mstore(free, mload(lhs)) - mstore(add(free, 0x20), mload(add(lhs, 0x20))) - - // Write rhs into memory (two 32 byte words) - // Memory layout: - // Address | value - // free + 0x40| rhs.x - // free + 0x60| rhs.y - mstore(add(free, 0x40), mload(rhs)) - mstore(add(free, 0x60), mload(add(rhs, 0x20))) - - // Call the ecAdd precompile, it takes in the following - // [lhs.x, lhs.y, rhs.x, rhs.y], and returns their addition back into the free memory location. - let success := staticcall(gas(), 0x06, free, 0x80, free, 0x40) - if iszero(success) { revert(0, 0) } - - // Copy the result of the addition back into the result memory location. - // Memory layout: - // Address | value - // result | result.x - // result + 0x20| result.y - mstore(result, mload(free)) - mstore(add(result, 0x20), mload(add(free, 0x20))) - - mstore(0x40, add(free, 0x80)) - } - - return result; + Honk.G1Point memory result; + + assembly { + let free := mload(0x40) + // Write lhs into memory (two 32 byte words) + // Memory layout: + // Address | value + // free | lhs.x + // free + 0x20| lhs.y + mstore(free, mload(lhs)) + mstore(add(free, 0x20), mload(add(lhs, 0x20))) + + // Write rhs into memory (two 32 byte words) + // Memory layout: + // Address | value + // free + 0x40| rhs.x + // free + 0x60| rhs.y + mstore(add(free, 0x40), mload(rhs)) + mstore(add(free, 0x60), mload(add(rhs, 0x20))) + + // Call the ecAdd precompile, it takes in the following + // [lhs.x, lhs.y, rhs.x, rhs.y], and returns their addition back into the free memory location. + let success := staticcall(gas(), 0x06, free, 0x80, free, 0x40) + if iszero(success) { + revert(0, 0) + } + + // Copy the result of the addition back into the result memory location. + // Memory layout: + // Address | value + // result | result.x + // result + 0x20| result.y + mstore(result, mload(free)) + mstore(add(result, 0x20), mload(add(free, 0x20))) + + mstore(0x40, add(free, 0x80)) + } + + return result; } function validateOnCurve(Honk.G1Point memory point) pure { - uint256 x = point.x; - uint256 y = point.y; + uint256 x = point.x; + uint256 y = point.y; - bool success = false; - assembly { - let xx := mulmod(x, x, Q) - success := eq(mulmod(y, y, Q), addmod(mulmod(x, xx, Q), 3, Q)) - } + bool success = false; + assembly { + let xx := mulmod(x, x, Q) + success := eq(mulmod(y, y, Q), addmod(mulmod(x, xx, Q), 3, Q)) + } - require(success, "point is not on the curve"); + require(success, "point is not on the curve"); } function pairing(Honk.G1Point memory rhs, Honk.G1Point memory lhs) view returns (bool decodedResult) { - bytes memory input = abi.encodePacked( - rhs.x, - rhs.y, - // Fixed G2 point - uint256(0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2), - uint256(0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed), - uint256(0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b), - uint256(0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa), - lhs.x, - lhs.y, - // G2 point from VK - uint256(0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1), - uint256(0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0), - uint256(0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4), - uint256(0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55) - ); - - (bool success, bytes memory result) = address(0x08).staticcall(input); - decodedResult = success && abi.decode(result, (bool)); + bytes memory input = abi.encodePacked( + rhs.x, + rhs.y, + // Fixed G2 point + uint256(0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2), + uint256(0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed), + uint256(0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b), + uint256(0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa), + lhs.x, + lhs.y, + // G2 point from VK + uint256(0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1), + uint256(0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0), + uint256(0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4), + uint256(0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55) + ); + + (bool success, bytes memory result) = address(0x08).staticcall(input); + decodedResult = success && abi.decode(result, (bool)); } // Field arithmetic libraries - prevent littering the code with modmul / addmul +abstract contract BaseZKHonkVerifier is IVerifier { + using FrLib for Fr; + + uint256 immutable $N; + uint256 immutable $LOG_N; + uint256 immutable $VK_HASH; + uint256 immutable $NUM_PUBLIC_INPUTS; + + constructor(uint256 _N, uint256 _logN, uint256 _vkHash, uint256 _numPublicInputs) { + $N = _N; + $LOG_N = _logN; + $VK_HASH = _vkHash; + $NUM_PUBLIC_INPUTS = _numPublicInputs; + } + + // Errors + error ProofLengthWrong(); + error ProofLengthWrongWithLogN(uint256 logN, uint256 actualLength, uint256 expectedLength); + error PublicInputsLengthWrong(); + error SumcheckFailed(); + error ShpleminiFailed(); + error GeminiChallengeInSubgroup(); + error ConsistencyCheckFailed(); + + // Constants for proof length calculation (matching UltraKeccakZKFlavor) + uint256 constant NUM_WITNESS_ENTITIES = 8; + uint256 constant NUM_ELEMENTS_COMM = 2; // uint256 elements for curve points + uint256 constant NUM_ELEMENTS_FR = 1; // uint256 elements for field elements + uint256 constant NUM_LIBRA_EVALUATIONS = 4; // libra evaluations + + // Calculate proof size based on log_n (matching UltraKeccakZKFlavor formula) + function calculateProofSize(uint256 logN) internal pure returns (uint256) { + // Witness and Libra commitments + uint256 proofLength = NUM_WITNESS_ENTITIES * NUM_ELEMENTS_COMM; // witness commitments + proofLength += NUM_ELEMENTS_COMM * 4; // Libra concat, grand sum, quotient comms + Gemini masking + // Sumcheck + proofLength += logN * ZK_BATCHED_RELATION_PARTIAL_LENGTH * NUM_ELEMENTS_FR; // sumcheck univariates + proofLength += NUMBER_OF_ENTITIES * NUM_ELEMENTS_FR; // sumcheck evaluations + // Libra and Gemini + proofLength += NUM_ELEMENTS_FR * 3; // Libra sum, claimed eval, Gemini masking eval + proofLength += logN * NUM_ELEMENTS_FR; // Gemini a evaluations + proofLength += NUM_LIBRA_EVALUATIONS * NUM_ELEMENTS_FR; // libra evaluations -abstract contract BaseZKHonkVerifier is IVerifier { - using FrLib for Fr; + // PCS commitments + proofLength += (logN - 1) * NUM_ELEMENTS_COMM; // Gemini Fold commitments + proofLength += NUM_ELEMENTS_COMM * 2; // Shplonk Q and KZG W commitments + + // Pairing points + proofLength += PAIRING_POINTS_SIZE; // pairing inputs carried on public inputs - uint256 immutable $N; - uint256 immutable $LOG_N; - uint256 immutable $VK_HASH; - uint256 immutable $NUM_PUBLIC_INPUTS; + return proofLength; + } - constructor(uint256 _N, uint256 _logN, uint256 _vkHash, uint256 _numPublicInputs) { - $N = _N; - $LOG_N = _logN; - $VK_HASH = _vkHash; - $NUM_PUBLIC_INPUTS = _numPublicInputs; + uint256 constant SHIFTED_COMMITMENTS_START = 30; + + function loadVerificationKey() internal pure virtual returns (Honk.VerificationKey memory); + + function verify(bytes calldata proof, bytes32[] calldata publicInputs) public view override returns (bool verified) { + // Calculate expected proof size based on $LOG_N + uint256 expectedProofSize = calculateProofSize($LOG_N); + + // Check the received proof is the expected size where each field element is 32 bytes + if (proof.length != expectedProofSize * 32) { + revert ProofLengthWrongWithLogN($LOG_N, proof.length, expectedProofSize * 32); } - // Errors - error ProofLengthWrong(); - error ProofLengthWrongWithLogN(uint256 logN, uint256 actualLength, uint256 expectedLength); - error PublicInputsLengthWrong(); - error SumcheckFailed(); - error ShpleminiFailed(); - error GeminiChallengeInSubgroup(); - error ConsistencyCheckFailed(); + Honk.VerificationKey memory vk = loadVerificationKey(); + Honk.ZKProof memory p = ZKTranscriptLib.loadProof(proof, $LOG_N); - // Constants for proof length calculation (matching UltraKeccakZKFlavor) - uint256 constant NUM_WITNESS_ENTITIES = 8; - uint256 constant NUM_ELEMENTS_COMM = 2; // uint256 elements for curve points - uint256 constant NUM_ELEMENTS_FR = 1; // uint256 elements for field elements - uint256 constant NUM_LIBRA_EVALUATIONS = 4; // libra evaluations + if (publicInputs.length != vk.publicInputsSize - PAIRING_POINTS_SIZE) { + revert PublicInputsLengthWrong(); + } - // Calculate proof size based on log_n (matching UltraKeccakZKFlavor formula) - function calculateProofSize(uint256 logN) internal pure returns (uint256) { - // Witness and Libra commitments - uint256 proofLength = NUM_WITNESS_ENTITIES * NUM_ELEMENTS_COMM; // witness commitments - proofLength += NUM_ELEMENTS_COMM * 4; // Libra concat, grand sum, quotient comms + Gemini masking + // Generate the fiat shamir challenges for the whole protocol + ZKTranscript memory t = ZKTranscriptLib.generateTranscript(p, publicInputs, $VK_HASH, $NUM_PUBLIC_INPUTS, $LOG_N); - // Sumcheck - proofLength += logN * ZK_BATCHED_RELATION_PARTIAL_LENGTH * NUM_ELEMENTS_FR; // sumcheck univariates - proofLength += NUMBER_OF_ENTITIES * NUM_ELEMENTS_FR; // sumcheck evaluations + // Derive public input delta + t.relationParameters.publicInputsDelta = computePublicInputDelta( + publicInputs, + p.pairingPointObject, + t.relationParameters.beta, + t.relationParameters.gamma /*pubInputsOffset=*/, + 1 + ); - // Libra and Gemini - proofLength += NUM_ELEMENTS_FR * 3; // Libra sum, claimed eval, Gemini masking eval - proofLength += logN * NUM_ELEMENTS_FR; // Gemini a evaluations - proofLength += NUM_LIBRA_EVALUATIONS * NUM_ELEMENTS_FR; // libra evaluations + // Sumcheck + if (!verifySumcheck(p, t)) revert SumcheckFailed(); - // PCS commitments - proofLength += (logN - 1) * NUM_ELEMENTS_COMM; // Gemini Fold commitments - proofLength += NUM_ELEMENTS_COMM * 2; // Shplonk Q and KZG W commitments + if (!verifyShplemini(p, vk, t)) revert ShpleminiFailed(); - // Pairing points - proofLength += PAIRING_POINTS_SIZE; // pairing inputs carried on public inputs + verified = true; + } - return proofLength; - } + uint256 constant PERMUTATION_ARGUMENT_VALUE_SEPARATOR = 1 << 28; - uint256 constant SHIFTED_COMMITMENTS_START = 30; + function computePublicInputDelta( + bytes32[] memory publicInputs, + Fr[PAIRING_POINTS_SIZE] memory pairingPointObject, + Fr beta, + Fr gamma, + uint256 offset + ) internal view returns (Fr publicInputDelta) { + Fr numerator = Fr.wrap(1); + Fr denominator = Fr.wrap(1); - function loadVerificationKey() internal pure virtual returns (Honk.VerificationKey memory); + Fr numeratorAcc = gamma + (beta * FrLib.from(PERMUTATION_ARGUMENT_VALUE_SEPARATOR + offset)); + Fr denominatorAcc = gamma - (beta * FrLib.from(offset + 1)); - function verify(bytes calldata proof, bytes32[] calldata publicInputs) - public - view - override - returns (bool verified) { - // Calculate expected proof size based on $LOG_N - uint256 expectedProofSize = calculateProofSize($LOG_N); + for (uint256 i = 0; i < $NUM_PUBLIC_INPUTS - PAIRING_POINTS_SIZE; i++) { + Fr pubInput = FrLib.fromBytes32(publicInputs[i]); + + numerator = numerator * (numeratorAcc + pubInput); + denominator = denominator * (denominatorAcc + pubInput); + + numeratorAcc = numeratorAcc + beta; + denominatorAcc = denominatorAcc - beta; + } - // Check the received proof is the expected size where each field element is 32 bytes - if (proof.length != expectedProofSize * 32) { - revert ProofLengthWrongWithLogN($LOG_N, proof.length, expectedProofSize * 32); - } + for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) { + Fr pubInput = pairingPointObject[i]; - Honk.VerificationKey memory vk = loadVerificationKey(); - Honk.ZKProof memory p = ZKTranscriptLib.loadProof(proof, $LOG_N); + numerator = numerator * (numeratorAcc + pubInput); + denominator = denominator * (denominatorAcc + pubInput); - if (publicInputs.length != vk.publicInputsSize - PAIRING_POINTS_SIZE) { - revert PublicInputsLengthWrong(); - } + numeratorAcc = numeratorAcc + beta; + denominatorAcc = denominatorAcc - beta; + } + } - // Generate the fiat shamir challenges for the whole protocol - ZKTranscript memory t = - ZKTranscriptLib.generateTranscript(p, publicInputs, $VK_HASH, $NUM_PUBLIC_INPUTS, $LOG_N); + // Fr delta = numerator / denominator; // TOOO: batch invert later? + publicInputDelta = FrLib.div(numerator, denominator); + } - // Derive public input delta - t.relationParameters.publicInputsDelta = computePublicInputDelta( - publicInputs, - p.pairingPointObject, - t.relationParameters.beta, - t.relationParameters.gamma, /*pubInputsOffset=*/ - 1 - ); + function verifySumcheck(Honk.ZKProof memory proof, ZKTranscript memory tp) internal view returns (bool verified) { + Fr roundTargetSum = tp.libraChallenge * proof.libraSum; // default 0 + Fr powPartialEvaluation = Fr.wrap(1); - // Sumcheck - if (!verifySumcheck(p, t)) revert SumcheckFailed(); + // We perform sumcheck reductions over log n rounds ( the multivariate degree ) + for (uint256 round; round < $LOG_N; ++round) { + Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory roundUnivariate = proof.sumcheckUnivariates[round]; + Fr totalSum = roundUnivariate[0] + roundUnivariate[1]; + if (totalSum != roundTargetSum) revert SumcheckFailed(); - if (!verifyShplemini(p, vk, t)) revert ShpleminiFailed(); + Fr roundChallenge = tp.sumCheckUChallenges[round]; - verified = true; + // Update the round target for the next rounf + roundTargetSum = computeNextTargetSum(roundUnivariate, roundChallenge); + powPartialEvaluation = powPartialEvaluation * (Fr.wrap(1) + roundChallenge * (tp.gateChallenges[round] - Fr.wrap(1))); } - uint256 constant PERMUTATION_ARGUMENT_VALUE_SEPARATOR = 1 << 28; + // Last round + Fr grandHonkRelationSum = RelationsLib.accumulateRelationEvaluations( + proof.sumcheckEvaluations, + tp.relationParameters, + tp.alphas, + powPartialEvaluation + ); + + Fr evaluation = Fr.wrap(1); + for (uint256 i = 2; i < $LOG_N; i++) { + evaluation = evaluation * tp.sumCheckUChallenges[i]; + } + + grandHonkRelationSum = grandHonkRelationSum * (Fr.wrap(1) - evaluation) + proof.libraEvaluation * tp.libraChallenge; + verified = (grandHonkRelationSum == roundTargetSum); + } + + // Return the new target sum for the next sumcheck round + function computeNextTargetSum( + Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory roundUnivariates, + Fr roundChallenge + ) internal view returns (Fr targetSum) { + Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory BARYCENTRIC_LAGRANGE_DENOMINATORS = [ + Fr.wrap(0x0000000000000000000000000000000000000000000000000000000000009d80), + Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffec51), + Fr.wrap(0x00000000000000000000000000000000000000000000000000000000000005a0), + Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593effffd31), + Fr.wrap(0x0000000000000000000000000000000000000000000000000000000000000240), + Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593effffd31), + Fr.wrap(0x00000000000000000000000000000000000000000000000000000000000005a0), + Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffec51), + Fr.wrap(0x0000000000000000000000000000000000000000000000000000000000009d80) + ]; + + // To compute the next target sum, we evaluate the given univariate at a point u (challenge). + + // Performing Barycentric evaluations + // Compute B(x) + Fr numeratorValue = Fr.wrap(1); + for (uint256 i = 0; i < ZK_BATCHED_RELATION_PARTIAL_LENGTH; ++i) { + numeratorValue = numeratorValue * (roundChallenge - Fr.wrap(i)); + } + + Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory denominatorInverses; + for (uint256 i = 0; i < ZK_BATCHED_RELATION_PARTIAL_LENGTH; ++i) { + denominatorInverses[i] = FrLib.invert(BARYCENTRIC_LAGRANGE_DENOMINATORS[i] * (roundChallenge - Fr.wrap(i))); + } + + for (uint256 i = 0; i < ZK_BATCHED_RELATION_PARTIAL_LENGTH; ++i) { + targetSum = targetSum + roundUnivariates[i] * denominatorInverses[i]; + } + + // Scale the sum by the value of B(x) + targetSum = targetSum * numeratorValue; + } + + uint256 constant LIBRA_COMMITMENTS = 3; + uint256 constant LIBRA_EVALUATIONS = 4; + uint256 constant LIBRA_UNIVARIATES_LENGTH = 9; + + struct PairingInputs { + Honk.G1Point P_0; + Honk.G1Point P_1; + } + + function verifyShplemini( + Honk.ZKProof memory proof, + Honk.VerificationKey memory vk, + ZKTranscript memory tp + ) internal view returns (bool verified) { + CommitmentSchemeLib.ShpleminiIntermediates memory mem; // stack + + // - Compute vector (r, r², ... , r²⁽ⁿ⁻¹⁾), where n = log_circuit_size + Fr[] memory powers_of_evaluation_challenge = CommitmentSchemeLib.computeSquares(tp.geminiR, $LOG_N); + // Arrays hold values that will be linearly combined for the gemini and shplonk batch openings + Fr[] memory scalars = new Fr[](NUMBER_UNSHIFTED + $LOG_N + LIBRA_COMMITMENTS + 3); + Honk.G1Point[] memory commitments = new Honk.G1Point[](NUMBER_UNSHIFTED + $LOG_N + LIBRA_COMMITMENTS + 3); + + mem.posInvertedDenominator = (tp.shplonkZ - powers_of_evaluation_challenge[0]).invert(); + mem.negInvertedDenominator = (tp.shplonkZ + powers_of_evaluation_challenge[0]).invert(); + + mem.unshiftedScalar = mem.posInvertedDenominator + (tp.shplonkNu * mem.negInvertedDenominator); + mem.shiftedScalar = tp.geminiR.invert() * (mem.posInvertedDenominator - (tp.shplonkNu * mem.negInvertedDenominator)); + + scalars[0] = Fr.wrap(1); + commitments[0] = proof.shplonkQ; + + /* Batch multivariate opening claims, shifted and unshifted + * The vector of scalars is populated as follows: + * \f[ + * \left( + * - \left(\frac{1}{z-r} + \nu \times \frac{1}{z+r}\right), + * \ldots, + * - \rho^{i+k-1} \times \left(\frac{1}{z-r} + \nu \times \frac{1}{z+r}\right), + * - \rho^{i+k} \times \frac{1}{r} \times \left(\frac{1}{z-r} - \nu \times \frac{1}{z+r}\right), + * \ldots, + * - \rho^{k+m-1} \times \frac{1}{r} \times \left(\frac{1}{z-r} - \nu \times \frac{1}{z+r}\right) + * \right) + * \f] + * + * The following vector is concatenated to the vector of commitments: + * \f[ + * f_0, \ldots, f_{m-1}, f_{\text{shift}, 0}, \ldots, f_{\text{shift}, k-1} + * \f] + * + * Simultaneously, the evaluation of the multilinear polynomial + * \f[ + * \sum \rho^i \cdot f_i + \sum \rho^{i+k} \cdot f_{\text{shift}, i} + * \f] + * at the challenge point \f$ (u_0,\ldots, u_{n-1}) \f$ is computed. + * + * This approach minimizes the number of iterations over the commitments to multilinear polynomials + * and eliminates the need to store the powers of \f$ \rho \f$. + */ + mem.batchedEvaluation = proof.geminiMaskingEval; + mem.batchingChallenge = tp.rho; + mem.unshiftedScalarNeg = mem.unshiftedScalar.neg(); + mem.shiftedScalarNeg = mem.shiftedScalar.neg(); + + scalars[1] = mem.unshiftedScalarNeg; + for (uint256 i = 0; i < NUMBER_UNSHIFTED; ++i) { + scalars[i + 2] = mem.unshiftedScalarNeg * mem.batchingChallenge; + mem.batchedEvaluation = mem.batchedEvaluation + (proof.sumcheckEvaluations[i] * mem.batchingChallenge); + mem.batchingChallenge = mem.batchingChallenge * tp.rho; + } + // g commitments are accumulated at r + // For each of the to be shifted commitments perform the shift in place by + // adding to the unshifted value. + // We do so, as the values are to be used in batchMul later, and as + // `a * c + b * c = (a + b) * c` this will allow us to reduce memory and compute. + // Applied to w1, w2, w3, w4 and zPerm + for (uint256 i = 0; i < NUMBER_TO_BE_SHIFTED; ++i) { + uint256 scalarOff = i + SHIFTED_COMMITMENTS_START; + uint256 evaluationOff = i + NUMBER_UNSHIFTED; + + scalars[scalarOff] = scalars[scalarOff] + (mem.shiftedScalarNeg * mem.batchingChallenge); + mem.batchedEvaluation = mem.batchedEvaluation + (proof.sumcheckEvaluations[evaluationOff] * mem.batchingChallenge); + mem.batchingChallenge = mem.batchingChallenge * tp.rho; + } + + commitments[1] = proof.geminiMaskingPoly; + + commitments[2] = vk.qm; + commitments[3] = vk.qc; + commitments[4] = vk.ql; + commitments[5] = vk.qr; + commitments[6] = vk.qo; + commitments[7] = vk.q4; + commitments[8] = vk.qLookup; + commitments[9] = vk.qArith; + commitments[10] = vk.qDeltaRange; + commitments[11] = vk.qElliptic; + commitments[12] = vk.qMemory; + commitments[13] = vk.qNnf; + commitments[14] = vk.qPoseidon2External; + commitments[15] = vk.qPoseidon2Internal; + commitments[16] = vk.s1; + commitments[17] = vk.s2; + commitments[18] = vk.s3; + commitments[19] = vk.s4; + commitments[20] = vk.id1; + commitments[21] = vk.id2; + commitments[22] = vk.id3; + commitments[23] = vk.id4; + commitments[24] = vk.t1; + commitments[25] = vk.t2; + commitments[26] = vk.t3; + commitments[27] = vk.t4; + commitments[28] = vk.lagrangeFirst; + commitments[29] = vk.lagrangeLast; + + // Accumulate proof points + commitments[30] = proof.w1; + commitments[31] = proof.w2; + commitments[32] = proof.w3; + commitments[33] = proof.w4; + commitments[34] = proof.zPerm; + commitments[35] = proof.lookupInverses; + commitments[36] = proof.lookupReadCounts; + commitments[37] = proof.lookupReadTags; + + /* Batch gemini claims from the prover + * place the commitments to gemini aᵢ to the vector of commitments, compute the contributions from + * aᵢ(−r²ⁱ) for i=1, … , n−1 to the constant term accumulator, add corresponding scalars + * + * 1. Moves the vector + * \f[ + * \left( \text{com}(A_1), \text{com}(A_2), \ldots, \text{com}(A_{n-1}) \right) + * \f] + * to the 'commitments' vector. + * + * 2. Computes the scalars: + * \f[ + * \frac{\nu^{2}}{z + r^2}, \frac{\nu^3}{z + r^4}, \ldots, \frac{\nu^{n-1}}{z + r^{2^{n-1}}} + * \f] + * and places them into the 'scalars' vector. + * + * 3. Accumulates the summands of the constant term: + * \f[ + * \sum_{i=2}^{n-1} \frac{\nu^{i} \cdot A_i(-r^{2^i})}{z + r^{2^i}} + * \f] + * and adds them to the 'constant_term_accumulator'. + */ + + // Add contributions from A₀(r) and A₀(-r) to constant_term_accumulator: + // Compute the evaluations Aₗ(r^{2ˡ}) for l = 0, ..., $LOG_N - 1 + Fr[] memory foldPosEvaluations = CommitmentSchemeLib.computeFoldPosEvaluations( + tp.sumCheckUChallenges, + mem.batchedEvaluation, + proof.geminiAEvaluations, + powers_of_evaluation_challenge, + $LOG_N + ); - function computePublicInputDelta( - bytes32[] memory publicInputs, - Fr[PAIRING_POINTS_SIZE] memory pairingPointObject, - Fr beta, - Fr gamma, - uint256 offset - ) internal view returns (Fr publicInputDelta) { - Fr numerator = Fr.wrap(1); - Fr denominator = Fr.wrap(1); + mem.constantTermAccumulator = foldPosEvaluations[0] * mem.posInvertedDenominator; + mem.constantTermAccumulator = mem.constantTermAccumulator + (proof.geminiAEvaluations[0] * tp.shplonkNu * mem.negInvertedDenominator); - Fr numeratorAcc = gamma + (beta * FrLib.from(PERMUTATION_ARGUMENT_VALUE_SEPARATOR + offset)); - Fr denominatorAcc = gamma - (beta * FrLib.from(offset + 1)); + mem.batchingChallenge = tp.shplonkNu.sqr(); + uint256 boundary = NUMBER_UNSHIFTED + 2; - { - for (uint256 i = 0; i < $NUM_PUBLIC_INPUTS - PAIRING_POINTS_SIZE; i++) { - Fr pubInput = FrLib.fromBytes32(publicInputs[i]); + // Compute Shplonk constant term contributions from Aₗ(± r^{2ˡ}) for l = 1, ..., m-1; + // Compute scalar multipliers for each fold commitment + for (uint256 i = 0; i < $LOG_N - 1; ++i) { + bool dummy_round = i >= ($LOG_N - 1); - numerator = numerator * (numeratorAcc + pubInput); - denominator = denominator * (denominatorAcc + pubInput); + if (!dummy_round) { + // Update inverted denominators + mem.posInvertedDenominator = (tp.shplonkZ - powers_of_evaluation_challenge[i + 1]).invert(); + mem.negInvertedDenominator = (tp.shplonkZ + powers_of_evaluation_challenge[i + 1]).invert(); + + // Compute the scalar multipliers for Aₗ(± r^{2ˡ}) and [Aₗ] + mem.scalingFactorPos = mem.batchingChallenge * mem.posInvertedDenominator; + mem.scalingFactorNeg = mem.batchingChallenge * tp.shplonkNu * mem.negInvertedDenominator; + scalars[boundary + i] = mem.scalingFactorNeg.neg() + mem.scalingFactorPos.neg(); - numeratorAcc = numeratorAcc + beta; - denominatorAcc = denominatorAcc - beta; - } + // Accumulate the const term contribution given by + // v^{2l} * Aₗ(r^{2ˡ}) /(z-r^{2^l}) + v^{2l+1} * Aₗ(-r^{2ˡ}) /(z+ r^{2^l}) + Fr accumContribution = mem.scalingFactorNeg * proof.geminiAEvaluations[i + 1]; + accumContribution = accumContribution + mem.scalingFactorPos * foldPosEvaluations[i + 1]; + mem.constantTermAccumulator = mem.constantTermAccumulator + accumContribution; + } + // Update the running power of v + mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu * tp.shplonkNu; - for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) { - Fr pubInput = pairingPointObject[i]; + commitments[boundary + i] = proof.geminiFoldComms[i]; + } - numerator = numerator * (numeratorAcc + pubInput); - denominator = denominator * (denominatorAcc + pubInput); + boundary += $LOG_N - 1; - numeratorAcc = numeratorAcc + beta; - denominatorAcc = denominatorAcc - beta; - } - } + // Finalize the batch opening claim + mem.denominators[0] = Fr.wrap(1).div(tp.shplonkZ - tp.geminiR); + mem.denominators[1] = Fr.wrap(1).div(tp.shplonkZ - SUBGROUP_GENERATOR * tp.geminiR); + mem.denominators[2] = mem.denominators[0]; + mem.denominators[3] = mem.denominators[0]; - // Fr delta = numerator / denominator; // TOOO: batch invert later? - publicInputDelta = FrLib.div(numerator, denominator); + mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu * tp.shplonkNu; + for (uint256 i = 0; i < LIBRA_EVALUATIONS; i++) { + Fr scalingFactor = mem.denominators[i] * mem.batchingChallenge; + mem.batchingScalars[i] = scalingFactor.neg(); + mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu; + mem.constantTermAccumulator = mem.constantTermAccumulator + scalingFactor * proof.libraPolyEvals[i]; } + scalars[boundary] = mem.batchingScalars[0]; + scalars[boundary + 1] = mem.batchingScalars[1] + mem.batchingScalars[2]; + scalars[boundary + 2] = mem.batchingScalars[3]; - function verifySumcheck(Honk.ZKProof memory proof, ZKTranscript memory tp) internal view returns (bool verified) { - Fr roundTargetSum = tp.libraChallenge * proof.libraSum; // default 0 - Fr powPartialEvaluation = Fr.wrap(1); + for (uint256 i = 0; i < LIBRA_COMMITMENTS; i++) { + commitments[boundary++] = proof.libraCommitments[i]; + } + + commitments[boundary] = Honk.G1Point({ x: 1, y: 2 }); + scalars[boundary++] = mem.constantTermAccumulator; - // We perform sumcheck reductions over log n rounds ( the multivariate degree ) - for (uint256 round; round < $LOG_N; ++round) { - Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory roundUnivariate = proof.sumcheckUnivariates[round]; - Fr totalSum = roundUnivariate[0] + roundUnivariate[1]; - if (totalSum != roundTargetSum) revert SumcheckFailed(); + if (!checkEvalsConsistency(proof.libraPolyEvals, tp.geminiR, tp.sumCheckUChallenges, proof.libraEvaluation)) { + revert ConsistencyCheckFailed(); + } - Fr roundChallenge = tp.sumCheckUChallenges[round]; + Honk.G1Point memory quotient_commitment = proof.kzgQuotient; - // Update the round target for the next rounf - roundTargetSum = computeNextTargetSum(roundUnivariate, roundChallenge); - powPartialEvaluation = - powPartialEvaluation * (Fr.wrap(1) + roundChallenge * (tp.gateChallenges[round] - Fr.wrap(1))); - } + commitments[boundary] = quotient_commitment; + scalars[boundary] = tp.shplonkZ; // evaluation challenge - // Last round - Fr grandHonkRelationSum = RelationsLib.accumulateRelationEvaluations( - proof.sumcheckEvaluations, tp.relationParameters, tp.alphas, powPartialEvaluation - ); + PairingInputs memory pair; + pair.P_0 = batchMul(commitments, scalars); + pair.P_1 = negateInplace(quotient_commitment); - Fr evaluation = Fr.wrap(1); - for (uint256 i = 2; i < $LOG_N; i++) { - evaluation = evaluation * tp.sumCheckUChallenges[i]; - } + // Aggregate pairing points + Fr recursionSeparator = generateRecursionSeparator(proof.pairingPointObject, pair.P_0, pair.P_1); + (Honk.G1Point memory P_0_other, Honk.G1Point memory P_1_other) = convertPairingPointsToG1(proof.pairingPointObject); - grandHonkRelationSum = - grandHonkRelationSum * (Fr.wrap(1) - evaluation) + proof.libraEvaluation * tp.libraChallenge; - verified = (grandHonkRelationSum == roundTargetSum); + // Validate the points from the proof are on the curve + validateOnCurve(P_0_other); + validateOnCurve(P_1_other); + + // accumulate with aggregate points in proof + pair.P_0 = mulWithSeperator(pair.P_0, P_0_other, recursionSeparator); + pair.P_1 = mulWithSeperator(pair.P_1, P_1_other, recursionSeparator); + + return pairing(pair.P_0, pair.P_1); + } + + struct SmallSubgroupIpaIntermediates { + Fr[SUBGROUP_SIZE] challengePolyLagrange; + Fr challengePolyEval; + Fr lagrangeFirst; + Fr lagrangeLast; + Fr rootPower; + Fr[SUBGROUP_SIZE] denominators; // this has to disappear + Fr diff; + } + + function checkEvalsConsistency( + Fr[LIBRA_EVALUATIONS] memory libraPolyEvals, + Fr geminiR, + Fr[CONST_PROOF_SIZE_LOG_N] memory uChallenges, + Fr libraEval + ) internal view returns (bool check) { + Fr one = Fr.wrap(1); + Fr vanishingPolyEval = geminiR.pow(SUBGROUP_SIZE) - one; + if (vanishingPolyEval == Fr.wrap(0)) { + revert GeminiChallengeInSubgroup(); } - // Return the new target sum for the next sumcheck round - function computeNextTargetSum(Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory roundUnivariates, Fr roundChallenge) - internal - view - returns (Fr targetSum) - { - Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory BARYCENTRIC_LAGRANGE_DENOMINATORS = [ - Fr.wrap(0x0000000000000000000000000000000000000000000000000000000000009d80), - Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffec51), - Fr.wrap(0x00000000000000000000000000000000000000000000000000000000000005a0), - Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593effffd31), - Fr.wrap(0x0000000000000000000000000000000000000000000000000000000000000240), - Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593effffd31), - Fr.wrap(0x00000000000000000000000000000000000000000000000000000000000005a0), - Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffec51), - Fr.wrap(0x0000000000000000000000000000000000000000000000000000000000009d80) - ]; - - // To compute the next target sum, we evaluate the given univariate at a point u (challenge). - - // Performing Barycentric evaluations - // Compute B(x) - Fr numeratorValue = Fr.wrap(1); - for (uint256 i = 0; i < ZK_BATCHED_RELATION_PARTIAL_LENGTH; ++i) { - numeratorValue = numeratorValue * (roundChallenge - Fr.wrap(i)); - } - - Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory denominatorInverses; - for (uint256 i = 0; i < ZK_BATCHED_RELATION_PARTIAL_LENGTH; ++i) { - denominatorInverses[i] = FrLib.invert(BARYCENTRIC_LAGRANGE_DENOMINATORS[i] * (roundChallenge - Fr.wrap(i))); - } - - for (uint256 i = 0; i < ZK_BATCHED_RELATION_PARTIAL_LENGTH; ++i) { - targetSum = targetSum + roundUnivariates[i] * denominatorInverses[i]; - } - - // Scale the sum by the value of B(x) - targetSum = targetSum * numeratorValue; - } - - uint256 constant LIBRA_COMMITMENTS = 3; - uint256 constant LIBRA_EVALUATIONS = 4; - uint256 constant LIBRA_UNIVARIATES_LENGTH = 9; - - struct PairingInputs { - Honk.G1Point P_0; - Honk.G1Point P_1; - } - - function verifyShplemini(Honk.ZKProof memory proof, Honk.VerificationKey memory vk, ZKTranscript memory tp) - internal - view - returns (bool verified) - { - CommitmentSchemeLib.ShpleminiIntermediates memory mem; // stack - - // - Compute vector (r, r², ... , r²⁽ⁿ⁻¹⁾), where n = log_circuit_size - Fr[] memory powers_of_evaluation_challenge = CommitmentSchemeLib.computeSquares(tp.geminiR, $LOG_N); - // Arrays hold values that will be linearly combined for the gemini and shplonk batch openings - Fr[] memory scalars = new Fr[](NUMBER_UNSHIFTED + $LOG_N + LIBRA_COMMITMENTS + 3); - Honk.G1Point[] memory commitments = new Honk.G1Point[](NUMBER_UNSHIFTED + $LOG_N + LIBRA_COMMITMENTS + 3); - - mem.posInvertedDenominator = (tp.shplonkZ - powers_of_evaluation_challenge[0]).invert(); - mem.negInvertedDenominator = (tp.shplonkZ + powers_of_evaluation_challenge[0]).invert(); - - mem.unshiftedScalar = mem.posInvertedDenominator + (tp.shplonkNu * mem.negInvertedDenominator); - mem.shiftedScalar = - tp.geminiR.invert() * (mem.posInvertedDenominator - (tp.shplonkNu * mem.negInvertedDenominator)); - - scalars[0] = Fr.wrap(1); - commitments[0] = proof.shplonkQ; - - /* Batch multivariate opening claims, shifted and unshifted - * The vector of scalars is populated as follows: - * \f[ - * \left( - * - \left(\frac{1}{z-r} + \nu \times \frac{1}{z+r}\right), - * \ldots, - * - \rho^{i+k-1} \times \left(\frac{1}{z-r} + \nu \times \frac{1}{z+r}\right), - * - \rho^{i+k} \times \frac{1}{r} \times \left(\frac{1}{z-r} - \nu \times \frac{1}{z+r}\right), - * \ldots, - * - \rho^{k+m-1} \times \frac{1}{r} \times \left(\frac{1}{z-r} - \nu \times \frac{1}{z+r}\right) - * \right) - * \f] - * - * The following vector is concatenated to the vector of commitments: - * \f[ - * f_0, \ldots, f_{m-1}, f_{\text{shift}, 0}, \ldots, f_{\text{shift}, k-1} - * \f] - * - * Simultaneously, the evaluation of the multilinear polynomial - * \f[ - * \sum \rho^i \cdot f_i + \sum \rho^{i+k} \cdot f_{\text{shift}, i} - * \f] - * at the challenge point \f$ (u_0,\ldots, u_{n-1}) \f$ is computed. - * - * This approach minimizes the number of iterations over the commitments to multilinear polynomials - * and eliminates the need to store the powers of \f$ \rho \f$. - */ - mem.batchedEvaluation = proof.geminiMaskingEval; - mem.batchingChallenge = tp.rho; - mem.unshiftedScalarNeg = mem.unshiftedScalar.neg(); - mem.shiftedScalarNeg = mem.shiftedScalar.neg(); - - scalars[1] = mem.unshiftedScalarNeg; - for (uint256 i = 0; i < NUMBER_UNSHIFTED; ++i) { - scalars[i + 2] = mem.unshiftedScalarNeg * mem.batchingChallenge; - mem.batchedEvaluation = mem.batchedEvaluation + (proof.sumcheckEvaluations[i] * mem.batchingChallenge); - mem.batchingChallenge = mem.batchingChallenge * tp.rho; - } - // g commitments are accumulated at r - // For each of the to be shifted commitments perform the shift in place by - // adding to the unshifted value. - // We do so, as the values are to be used in batchMul later, and as - // `a * c + b * c = (a + b) * c` this will allow us to reduce memory and compute. - // Applied to w1, w2, w3, w4 and zPerm - for (uint256 i = 0; i < NUMBER_TO_BE_SHIFTED; ++i) { - uint256 scalarOff = i + SHIFTED_COMMITMENTS_START; - uint256 evaluationOff = i + NUMBER_UNSHIFTED; - - scalars[scalarOff] = scalars[scalarOff] + (mem.shiftedScalarNeg * mem.batchingChallenge); - mem.batchedEvaluation = - mem.batchedEvaluation + (proof.sumcheckEvaluations[evaluationOff] * mem.batchingChallenge); - mem.batchingChallenge = mem.batchingChallenge * tp.rho; - } - - commitments[1] = proof.geminiMaskingPoly; - - commitments[2] = vk.qm; - commitments[3] = vk.qc; - commitments[4] = vk.ql; - commitments[5] = vk.qr; - commitments[6] = vk.qo; - commitments[7] = vk.q4; - commitments[8] = vk.qLookup; - commitments[9] = vk.qArith; - commitments[10] = vk.qDeltaRange; - commitments[11] = vk.qElliptic; - commitments[12] = vk.qMemory; - commitments[13] = vk.qNnf; - commitments[14] = vk.qPoseidon2External; - commitments[15] = vk.qPoseidon2Internal; - commitments[16] = vk.s1; - commitments[17] = vk.s2; - commitments[18] = vk.s3; - commitments[19] = vk.s4; - commitments[20] = vk.id1; - commitments[21] = vk.id2; - commitments[22] = vk.id3; - commitments[23] = vk.id4; - commitments[24] = vk.t1; - commitments[25] = vk.t2; - commitments[26] = vk.t3; - commitments[27] = vk.t4; - commitments[28] = vk.lagrangeFirst; - commitments[29] = vk.lagrangeLast; - - // Accumulate proof points - commitments[30] = proof.w1; - commitments[31] = proof.w2; - commitments[32] = proof.w3; - commitments[33] = proof.w4; - commitments[34] = proof.zPerm; - commitments[35] = proof.lookupInverses; - commitments[36] = proof.lookupReadCounts; - commitments[37] = proof.lookupReadTags; - - /* Batch gemini claims from the prover - * place the commitments to gemini aᵢ to the vector of commitments, compute the contributions from - * aᵢ(−r²ⁱ) for i=1, … , n−1 to the constant term accumulator, add corresponding scalars - * - * 1. Moves the vector - * \f[ - * \left( \text{com}(A_1), \text{com}(A_2), \ldots, \text{com}(A_{n-1}) \right) - * \f] - * to the 'commitments' vector. - * - * 2. Computes the scalars: - * \f[ - * \frac{\nu^{2}}{z + r^2}, \frac{\nu^3}{z + r^4}, \ldots, \frac{\nu^{n-1}}{z + r^{2^{n-1}}} - * \f] - * and places them into the 'scalars' vector. - * - * 3. Accumulates the summands of the constant term: - * \f[ - * \sum_{i=2}^{n-1} \frac{\nu^{i} \cdot A_i(-r^{2^i})}{z + r^{2^i}} - * \f] - * and adds them to the 'constant_term_accumulator'. - */ - - // Add contributions from A₀(r) and A₀(-r) to constant_term_accumulator: - // Compute the evaluations Aₗ(r^{2ˡ}) for l = 0, ..., $LOG_N - 1 - Fr[] memory foldPosEvaluations = CommitmentSchemeLib.computeFoldPosEvaluations( - tp.sumCheckUChallenges, - mem.batchedEvaluation, - proof.geminiAEvaluations, - powers_of_evaluation_challenge, - $LOG_N - ); - - mem.constantTermAccumulator = foldPosEvaluations[0] * mem.posInvertedDenominator; - mem.constantTermAccumulator = - mem.constantTermAccumulator + (proof.geminiAEvaluations[0] * tp.shplonkNu * mem.negInvertedDenominator); - - mem.batchingChallenge = tp.shplonkNu.sqr(); - uint256 boundary = NUMBER_UNSHIFTED + 2; - - // Compute Shplonk constant term contributions from Aₗ(± r^{2ˡ}) for l = 1, ..., m-1; - // Compute scalar multipliers for each fold commitment - for (uint256 i = 0; i < $LOG_N - 1; ++i) { - bool dummy_round = i >= ($LOG_N - 1); - - if (!dummy_round) { - // Update inverted denominators - mem.posInvertedDenominator = (tp.shplonkZ - powers_of_evaluation_challenge[i + 1]).invert(); - mem.negInvertedDenominator = (tp.shplonkZ + powers_of_evaluation_challenge[i + 1]).invert(); - - // Compute the scalar multipliers for Aₗ(± r^{2ˡ}) and [Aₗ] - mem.scalingFactorPos = mem.batchingChallenge * mem.posInvertedDenominator; - mem.scalingFactorNeg = mem.batchingChallenge * tp.shplonkNu * mem.negInvertedDenominator; - scalars[boundary + i] = mem.scalingFactorNeg.neg() + mem.scalingFactorPos.neg(); - - // Accumulate the const term contribution given by - // v^{2l} * Aₗ(r^{2ˡ}) /(z-r^{2^l}) + v^{2l+1} * Aₗ(-r^{2ˡ}) /(z+ r^{2^l}) - Fr accumContribution = mem.scalingFactorNeg * proof.geminiAEvaluations[i + 1]; - accumContribution = accumContribution + mem.scalingFactorPos * foldPosEvaluations[i + 1]; - mem.constantTermAccumulator = mem.constantTermAccumulator + accumContribution; - } - // Update the running power of v - mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu * tp.shplonkNu; - - commitments[boundary + i] = proof.geminiFoldComms[i]; - } - - boundary += $LOG_N - 1; - - // Finalize the batch opening claim - mem.denominators[0] = Fr.wrap(1).div(tp.shplonkZ - tp.geminiR); - mem.denominators[1] = Fr.wrap(1).div(tp.shplonkZ - SUBGROUP_GENERATOR * tp.geminiR); - mem.denominators[2] = mem.denominators[0]; - mem.denominators[3] = mem.denominators[0]; - - mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu * tp.shplonkNu; - for (uint256 i = 0; i < LIBRA_EVALUATIONS; i++) { - Fr scalingFactor = mem.denominators[i] * mem.batchingChallenge; - mem.batchingScalars[i] = scalingFactor.neg(); - mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu; - mem.constantTermAccumulator = mem.constantTermAccumulator + scalingFactor * proof.libraPolyEvals[i]; - } - scalars[boundary] = mem.batchingScalars[0]; - scalars[boundary + 1] = mem.batchingScalars[1] + mem.batchingScalars[2]; - scalars[boundary + 2] = mem.batchingScalars[3]; - - for (uint256 i = 0; i < LIBRA_COMMITMENTS; i++) { - commitments[boundary++] = proof.libraCommitments[i]; - } - - commitments[boundary] = Honk.G1Point({x: 1, y: 2}); - scalars[boundary++] = mem.constantTermAccumulator; - - if (!checkEvalsConsistency(proof.libraPolyEvals, tp.geminiR, tp.sumCheckUChallenges, proof.libraEvaluation)) { - revert ConsistencyCheckFailed(); - } - - Honk.G1Point memory quotient_commitment = proof.kzgQuotient; - - commitments[boundary] = quotient_commitment; - scalars[boundary] = tp.shplonkZ; // evaluation challenge - - PairingInputs memory pair; - pair.P_0 = batchMul(commitments, scalars); - pair.P_1 = negateInplace(quotient_commitment); - - // Aggregate pairing points - Fr recursionSeparator = generateRecursionSeparator(proof.pairingPointObject, pair.P_0, pair.P_1); - (Honk.G1Point memory P_0_other, Honk.G1Point memory P_1_other) = - convertPairingPointsToG1(proof.pairingPointObject); - - // Validate the points from the proof are on the curve - validateOnCurve(P_0_other); - validateOnCurve(P_1_other); - - // accumulate with aggregate points in proof - pair.P_0 = mulWithSeperator(pair.P_0, P_0_other, recursionSeparator); - pair.P_1 = mulWithSeperator(pair.P_1, P_1_other, recursionSeparator); - - return pairing(pair.P_0, pair.P_1); - } - - struct SmallSubgroupIpaIntermediates { - Fr[SUBGROUP_SIZE] challengePolyLagrange; - Fr challengePolyEval; - Fr lagrangeFirst; - Fr lagrangeLast; - Fr rootPower; - Fr[SUBGROUP_SIZE] denominators; // this has to disappear - Fr diff; - } - - function checkEvalsConsistency( - Fr[LIBRA_EVALUATIONS] memory libraPolyEvals, - Fr geminiR, - Fr[CONST_PROOF_SIZE_LOG_N] memory uChallenges, - Fr libraEval - ) internal view returns (bool check) { - Fr one = Fr.wrap(1); - Fr vanishingPolyEval = geminiR.pow(SUBGROUP_SIZE) - one; - if (vanishingPolyEval == Fr.wrap(0)) { - revert GeminiChallengeInSubgroup(); - } - - SmallSubgroupIpaIntermediates memory mem; - mem.challengePolyLagrange[0] = one; - for (uint256 round = 0; round < $LOG_N; round++) { - uint256 currIdx = 1 + LIBRA_UNIVARIATES_LENGTH * round; - mem.challengePolyLagrange[currIdx] = one; - for (uint256 idx = currIdx + 1; idx < currIdx + LIBRA_UNIVARIATES_LENGTH; idx++) { - mem.challengePolyLagrange[idx] = mem.challengePolyLagrange[idx - 1] * uChallenges[round]; - } - } - - mem.rootPower = one; - mem.challengePolyEval = Fr.wrap(0); - for (uint256 idx = 0; idx < SUBGROUP_SIZE; idx++) { - mem.denominators[idx] = mem.rootPower * geminiR - one; - mem.denominators[idx] = mem.denominators[idx].invert(); - mem.challengePolyEval = mem.challengePolyEval + mem.challengePolyLagrange[idx] * mem.denominators[idx]; - mem.rootPower = mem.rootPower * SUBGROUP_GENERATOR_INVERSE; - } - - Fr numerator = vanishingPolyEval * Fr.wrap(SUBGROUP_SIZE).invert(); - mem.challengePolyEval = mem.challengePolyEval * numerator; - mem.lagrangeFirst = mem.denominators[0] * numerator; - mem.lagrangeLast = mem.denominators[SUBGROUP_SIZE - 1] * numerator; - - mem.diff = mem.lagrangeFirst * libraPolyEvals[2]; - - mem.diff = mem.diff - + (geminiR - SUBGROUP_GENERATOR_INVERSE) - * (libraPolyEvals[1] - libraPolyEvals[2] - libraPolyEvals[0] * mem.challengePolyEval); - mem.diff = mem.diff + mem.lagrangeLast * (libraPolyEvals[2] - libraEval) - vanishingPolyEval * libraPolyEvals[3]; - - check = mem.diff == Fr.wrap(0); - } - - // This implementation is the same as above with different constants - function batchMul(Honk.G1Point[] memory base, Fr[] memory scalars) - internal - view - returns (Honk.G1Point memory result) - { - uint256 limit = NUMBER_UNSHIFTED + $LOG_N + LIBRA_COMMITMENTS + 3; + SmallSubgroupIpaIntermediates memory mem; + mem.challengePolyLagrange[0] = one; + for (uint256 round = 0; round < $LOG_N; round++) { + uint256 currIdx = 1 + LIBRA_UNIVARIATES_LENGTH * round; + mem.challengePolyLagrange[currIdx] = one; + for (uint256 idx = currIdx + 1; idx < currIdx + LIBRA_UNIVARIATES_LENGTH; idx++) { + mem.challengePolyLagrange[idx] = mem.challengePolyLagrange[idx - 1] * uChallenges[round]; + } + } + + mem.rootPower = one; + mem.challengePolyEval = Fr.wrap(0); + for (uint256 idx = 0; idx < SUBGROUP_SIZE; idx++) { + mem.denominators[idx] = mem.rootPower * geminiR - one; + mem.denominators[idx] = mem.denominators[idx].invert(); + mem.challengePolyEval = mem.challengePolyEval + mem.challengePolyLagrange[idx] * mem.denominators[idx]; + mem.rootPower = mem.rootPower * SUBGROUP_GENERATOR_INVERSE; + } + + Fr numerator = vanishingPolyEval * Fr.wrap(SUBGROUP_SIZE).invert(); + mem.challengePolyEval = mem.challengePolyEval * numerator; + mem.lagrangeFirst = mem.denominators[0] * numerator; + mem.lagrangeLast = mem.denominators[SUBGROUP_SIZE - 1] * numerator; + + mem.diff = mem.lagrangeFirst * libraPolyEvals[2]; - // Validate all points are on the curve - for (uint256 i = 0; i < limit; ++i) { - validateOnCurve(base[i]); - } + mem.diff = + mem.diff + + (geminiR - SUBGROUP_GENERATOR_INVERSE) * + (libraPolyEvals[1] - libraPolyEvals[2] - libraPolyEvals[0] * mem.challengePolyEval); + mem.diff = mem.diff + mem.lagrangeLast * (libraPolyEvals[2] - libraEval) - vanishingPolyEval * libraPolyEvals[3]; - bool success = true; - assembly { - let free := mload(0x40) + check = mem.diff == Fr.wrap(0); + } - let count := 0x01 - for {} lt(count, add(limit, 1)) { count := add(count, 1) } { - // Get loop offsets - let base_base := add(base, mul(count, 0x20)) - let scalar_base := add(scalars, mul(count, 0x20)) + // This implementation is the same as above with different constants + function batchMul(Honk.G1Point[] memory base, Fr[] memory scalars) internal view returns (Honk.G1Point memory result) { + uint256 limit = NUMBER_UNSHIFTED + $LOG_N + LIBRA_COMMITMENTS + 3; - mstore(add(free, 0x40), mload(mload(base_base))) - mstore(add(free, 0x60), mload(add(0x20, mload(base_base)))) - // Add scalar - mstore(add(free, 0x80), mload(scalar_base)) + // Validate all points are on the curve + for (uint256 i = 0; i < limit; ++i) { + validateOnCurve(base[i]); + } + + bool success = true; + assembly { + let free := mload(0x40) - success := and(success, staticcall(gas(), 7, add(free, 0x40), 0x60, add(free, 0x40), 0x40)) - // accumulator = accumulator + accumulator_2 - success := and(success, staticcall(gas(), 6, free, 0x80, free, 0x40)) - } + let count := 0x01 + for {} lt(count, add(limit, 1)) { + count := add(count, 1) + } { + // Get loop offsets + let base_base := add(base, mul(count, 0x20)) + let scalar_base := add(scalars, mul(count, 0x20)) - // Return the result - mstore(result, mload(free)) - mstore(add(result, 0x20), mload(add(free, 0x20))) - } + mstore(add(free, 0x40), mload(mload(base_base))) + mstore(add(free, 0x60), mload(add(0x20, mload(base_base)))) + // Add scalar + mstore(add(free, 0x80), mload(scalar_base)) - require(success, ShpleminiFailed()); + success := and(success, staticcall(gas(), 7, add(free, 0x40), 0x60, add(free, 0x40), 0x40)) + // accumulator = accumulator + accumulator_2 + success := and(success, staticcall(gas(), 6, free, 0x80, free, 0x40)) + } + + // Return the result + mstore(result, mload(free)) + mstore(add(result, 0x20), mload(add(free, 0x20))) } + + require(success, ShpleminiFailed()); + } } contract HonkVerifier is BaseZKHonkVerifier(N, LOG_N, VK_HASH, NUMBER_OF_PUBLIC_INPUTS) { - function loadVerificationKey() internal pure override returns (Honk.VerificationKey memory) { - return HonkVerificationKey.loadVerificationKey(); - } + function loadVerificationKey() internal pure override returns (Honk.VerificationKey memory) { + return HonkVerificationKey.loadVerificationKey(); + } }