pub const COMPLETE_VERIFIER_TEMPLATE: &str = r#"// SPDX-License-Identifier: Apache-2.0
pragma solidity {{SOLIDITY_VERSION}};
/// @title {{CONTRACT_NAME}}
/// @notice PLONK verifier for aggregated proofs (BN254)
/// @dev Generated by samaharam - Heterogeneous Proof Aggregation
contract {{CONTRACT_NAME}} {
// ============ BN254 Curve Parameters ============
/// @notice Base field modulus
uint256 internal constant P =
21888242871839275222246405745257275088696311157297823662689037894645226208583;
/// @notice Scalar field modulus
uint256 internal constant R =
21888242871839275222246405745257275088548364400416034343698204186575808495617;
/// @notice Generator of G1
uint256 internal constant G1_X = 1;
uint256 internal constant G1_Y = 2;
// ============ Verification Key ============
{{VK_CONSTANTS}}
// ============ Errors ============
error InvalidProof();
error InvalidPublicInput();
error PairingFailed();
// ============ Verification ============
/// @notice Verify an aggregated proof
/// @param proof Encoded proof ({{PROOF_SIZE}} bytes)
/// @param publicInputs Public inputs array
/// @return True if valid
function verify(
bytes calldata proof,
uint256[] calldata publicInputs
) external view returns (bool) {
// Decode proof points
(
uint256[2] memory a,
uint256[2][2] memory b,
uint256[2] memory c,
uint256[2] memory z,
uint256[2] memory t1,
uint256[2] memory t2,
uint256[2] memory t3,
uint256[2] memory wxi,
uint256[2] memory wxiw
) = _decodeProof(proof);
// Compute challenges via Fiat-Shamir
uint256 beta = _challenge(abi.encodePacked(a, b, c), "beta");
uint256 gamma = _challenge(abi.encodePacked(beta, z), "gamma");
uint256 alpha = _challenge(abi.encodePacked(gamma, t1, t2, t3), "alpha");
uint256 zeta = _challenge(abi.encodePacked(alpha), "zeta");
// Compute public input contribution
uint256[2] memory pi = _computePublicInput(publicInputs, zeta);
// Pairing check
return _verifyPairing(a, b, c, z, pi, wxi, wxiw, alpha, beta, gamma, zeta);
}
// ============ Internal Functions ============
function _decodeProof(bytes calldata proof) internal pure returns (
uint256[2] memory a,
uint256[2][2] memory b,
uint256[2] memory c,
uint256[2] memory z,
uint256[2] memory t1,
uint256[2] memory t2,
uint256[2] memory t3,
uint256[2] memory wxi,
uint256[2] memory wxiw
) {
require(proof.length >= 576, "Invalid proof length");
assembly {
let ptr := proof.offset
// A (G1)
mstore(a, calldataload(ptr))
mstore(add(a, 0x20), calldataload(add(ptr, 0x20)))
ptr := add(ptr, 0x40)
// B (G2 - 4 elements)
mstore(b, calldataload(ptr))
mstore(add(b, 0x20), calldataload(add(ptr, 0x20)))
mstore(add(b, 0x40), calldataload(add(ptr, 0x40)))
mstore(add(b, 0x60), calldataload(add(ptr, 0x60)))
ptr := add(ptr, 0x80)
// C (G1)
mstore(c, calldataload(ptr))
mstore(add(c, 0x20), calldataload(add(ptr, 0x20)))
ptr := add(ptr, 0x40)
// Z (G1)
mstore(z, calldataload(ptr))
mstore(add(z, 0x20), calldataload(add(ptr, 0x20)))
ptr := add(ptr, 0x40)
// T1, T2, T3 (G1 each)
mstore(t1, calldataload(ptr))
mstore(add(t1, 0x20), calldataload(add(ptr, 0x20)))
ptr := add(ptr, 0x40)
mstore(t2, calldataload(ptr))
mstore(add(t2, 0x20), calldataload(add(ptr, 0x20)))
ptr := add(ptr, 0x40)
mstore(t3, calldataload(ptr))
mstore(add(t3, 0x20), calldataload(add(ptr, 0x20)))
ptr := add(ptr, 0x40)
// Opening proofs
mstore(wxi, calldataload(ptr))
mstore(add(wxi, 0x20), calldataload(add(ptr, 0x20)))
ptr := add(ptr, 0x40)
mstore(wxiw, calldataload(ptr))
mstore(add(wxiw, 0x20), calldataload(add(ptr, 0x20)))
}
}
function _challenge(bytes memory input, string memory label) internal pure returns (uint256) {
return uint256(keccak256(abi.encodePacked(input, label))) % R;
}
function _computePublicInput(
uint256[] calldata inputs,
uint256 zeta
) internal view returns (uint256[2] memory pi) {
uint256 acc = 0;
for (uint i = 0; i < inputs.length; i++) {
require(inputs[i] < R, "Invalid public input");
acc = addmod(acc, mulmod(inputs[i], _lagrange(i, zeta), R), R);
}
// Return as G1 point (acc * G1)
(pi[0], pi[1]) = _scalarMul(G1_X, G1_Y, acc);
}
function _lagrange(uint256 i, uint256 zeta) internal pure returns (uint256) {
// L_i(zeta) = (omega^i / n) * (zeta^n - 1) / (zeta - omega^i)
// Note: For i=0, omega^0 = 1. For i=1, omega^1 = VK_OMEGA, etc.
uint256 n = VK_DOMAIN_SIZE;
uint256 omega_i = _exp(VK_OMEGA, i);
uint256 zeta_n_minus_1 = addmod(_exp(zeta, n), R - 1, R);
uint256 num = mulmod(omega_i, zeta_n_minus_1, R);
uint256 den = mulmod(n, addmod(zeta, R - omega_i, R), R);
if (den == 0) return 1; // Limit at zeta = omega_i
return mulmod(num, _inverse(den), R);
}
function _exp(uint256 base, uint256 exponent) internal pure returns (uint256) {
uint256 res = 1;
base = base % R;
while (exponent > 0) {
if (exponent % 2 == 1) res = mulmod(res, base, R);
base = mulmod(base, base, R);
exponent /= 2;
}
return res;
}
function _inverse(uint256 n) internal pure returns (uint256) {
return _exp(n, R - 2);
}
function _verifyPairing(
uint256[2] memory a,
uint256[2][2] memory b,
uint256[2] memory c,
uint256[2] memory z,
uint256[2] memory pi,
uint256[2] memory wxi,
uint256[2] memory wxiw,
uint256 alpha,
uint256 beta,
uint256 gamma,
uint256 zeta
) internal view returns (bool) {
// Groth16-style pairing check:
// e(A, B) = e(alpha, beta) * e(pi, gamma) * e(C, delta)
uint256[24] memory input;
// Negate A for pairing equation
uint256[2] memory negA = _negate(a);
// Prepare pairing input
// e(-A, B)
input[0] = negA[0];
input[1] = negA[1];
input[2] = b[0][1]; // G2 uses (x_i, x_r, y_i, y_r)
input[3] = b[0][0];
input[4] = b[1][1];
input[5] = b[1][0];
// e(C, [delta]_2) - using VK delta
input[6] = c[0];
input[7] = c[1];
input[8] = VK_DELTA_X1;
input[9] = VK_DELTA_X2;
input[10] = VK_DELTA_Y1;
input[11] = VK_DELTA_Y2;
// e(pi, [gamma]_2)
input[12] = pi[0];
input[13] = pi[1];
input[14] = VK_GAMMA_X1;
input[15] = VK_GAMMA_X2;
input[16] = VK_GAMMA_Y1;
input[17] = VK_GAMMA_Y2;
// e([alpha]_1, [beta]_2)
input[18] = VK_ALPHA_X;
input[19] = VK_ALPHA_Y;
input[20] = VK_BETA_X1;
input[21] = VK_BETA_X2;
input[22] = VK_BETA_Y1;
input[23] = VK_BETA_Y2;
return _pairing(input);
}
// ============ Precompiles ============
function _pairing(uint256[24] memory input) internal view returns (bool success) {
assembly {
success := staticcall(gas(), 0x08, input, 768, input, 0x20)
success := and(success, mload(input))
}
}
function _scalarMul(
uint256 x, uint256 y, uint256 s
) internal view returns (uint256 rx, uint256 ry) {
uint256[3] memory input = [x, y, s];
assembly {
let success := staticcall(gas(), 0x07, input, 96, input, 64)
if iszero(success) { revert(0, 0) }
rx := mload(input)
ry := mload(add(input, 0x20))
}
}
function _add(
uint256 x1, uint256 y1,
uint256 x2, uint256 y2
) internal view returns (uint256 rx, uint256 ry) {
uint256[4] memory input = [x1, y1, x2, y2];
assembly {
let success := staticcall(gas(), 0x06, input, 128, input, 64)
if iszero(success) { revert(0, 0) }
rx := mload(input)
ry := mload(add(input, 0x20))
}
}
function _negate(uint256[2] memory p) internal pure returns (uint256[2] memory) {
if (p[0] == 0 && p[1] == 0) {
return p;
}
return [p[0], P - (p[1] % P)];
}
// ============ VK Constants (Generated) ============
uint256 internal constant VK_DOMAIN_SIZE = {{VK_DOMAIN_SIZE}};
uint256 internal constant VK_OMEGA = {{VK_OMEGA}};
uint256 internal constant VK_ALPHA_X = {{VK_ALPHA_X}};
uint256 internal constant VK_ALPHA_Y = {{VK_ALPHA_Y}};
uint256 internal constant VK_BETA_X1 = {{VK_BETA_X1}};
uint256 internal constant VK_BETA_X2 = {{VK_BETA_X2}};
uint256 internal constant VK_BETA_Y1 = {{VK_BETA_Y1}};
uint256 internal constant VK_BETA_Y2 = {{VK_BETA_Y2}};
uint256 internal constant VK_GAMMA_X1 = {{VK_GAMMA_X1}};
uint256 internal constant VK_GAMMA_X2 = {{VK_GAMMA_X2}};
uint256 internal constant VK_GAMMA_Y1 = {{VK_GAMMA_Y1}};
uint256 internal constant VK_GAMMA_Y2 = {{VK_GAMMA_Y2}};
uint256 internal constant VK_DELTA_X1 = {{VK_DELTA_X1}};
uint256 internal constant VK_DELTA_X2 = {{VK_DELTA_X2}};
uint256 internal constant VK_DELTA_Y1 = {{VK_DELTA_Y1}};
uint256 internal constant VK_DELTA_Y2 = {{VK_DELTA_Y2}};
}
"#;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn complete_template_has_pairing_logic() {
assert!(COMPLETE_VERIFIER_TEMPLATE.contains("_pairing"));
assert!(COMPLETE_VERIFIER_TEMPLATE.contains("_scalarMul"));
assert!(COMPLETE_VERIFIER_TEMPLATE.contains("_negate"));
}
#[test]
fn complete_template_has_proof_decode() {
assert!(COMPLETE_VERIFIER_TEMPLATE.contains("_decodeProof"));
assert!(COMPLETE_VERIFIER_TEMPLATE.contains("calldataload"));
}
#[test]
fn complete_template_has_fiat_shamir() {
assert!(COMPLETE_VERIFIER_TEMPLATE.contains("_challenge"));
assert!(COMPLETE_VERIFIER_TEMPLATE.contains("keccak256"));
}
#[test]
fn complete_template_has_vk_placeholders() {
assert!(COMPLETE_VERIFIER_TEMPLATE.contains("VK_ALPHA_X"));
assert!(COMPLETE_VERIFIER_TEMPLATE.contains("VK_BETA_X1"));
assert!(COMPLETE_VERIFIER_TEMPLATE.contains("VK_GAMMA_X1"));
assert!(COMPLETE_VERIFIER_TEMPLATE.contains("VK_DELTA_X1"));
}
}