samaharam 0.2.0

Scalable heterogeneous zero-knowledge proof aggregation for EVM chains
Documentation
//! Complete Solidity verifier with BN254 pairing logic.
//!
//! This template generates a fully functional verifier contract.

/// Complete PLONK verifier template with pairing logic.
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"));
    }
}