//! Solidity verifier contract template.

/// Template for the PLONK verifier contract.
///
/// This uses BN254 pairing precompiles (EIP-196, EIP-197):
/// - 0x06: ECADD
/// - 0x07: ECMUL
/// - 0x08: ECPAIRING
pub const VERIFIER_TEMPLATE: &str = r#"// SPDX-License-Identifier: Apache-2.0
pragma solidity {{SOLIDITY_VERSION}};

/// @title {{CONTRACT_NAME}}
/// @notice Auto-generated PLONK verifier for aggregated proofs
/// @dev Generated by samaharam - Heterogeneous Proof Aggregator
contract {{CONTRACT_NAME}} {
    // ============ Constants ============
    
    /// @notice BN254 curve order
    uint256 internal constant PRIME_Q = 
        21888242871839275222246405745257275088696311157297823662689037894645226208583;
    
    /// @notice Scalar field order  
    uint256 internal constant PRIME_R =
        21888242871839275222246405745257275088548364400416034343698204186575808495617;

    // ============ Verification Key ============
    
{{VK_POINTS}}

    // ============ Errors ============
    
    error InvalidProofLength();
    error InvalidPublicInputs();
    error PairingCheckFailed();
    error EcAddFailed();
    error EcMulFailed();
    error EcPairingFailed();

    // ============ Verification ============

    /// @notice Verify an aggregated proof
    /// @param proof The encoded proof data
    /// @param publicInputs The public inputs array
    /// @return success True if the proof is valid
    function verify(
        bytes calldata proof,
        uint256[] calldata publicInputs
    ) external view returns (bool success) {
        // Validate input lengths
        if (proof.length != {{PROOF_LENGTH}}) revert InvalidProofLength();
        if (publicInputs.length != {{NUM_PUBLIC_INPUTS}}) revert InvalidPublicInputs();

        // Decode proof elements
        {{PROOF_DECODE}}

        // Compute public input contribution
        {{PUBLIC_INPUT_COMPUTATION}}

        // Pairing check
        {{PAIRING_CHECK}}

        return true;
    }

    // ============ Precompile Wrappers ============

    /// @notice Elliptic curve addition
    function ecAdd(
        uint256 x1, uint256 y1,
        uint256 x2, uint256 y2
    ) internal view returns (uint256 x, uint256 y) {
        bool success;
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, x1)
            mstore(add(ptr, 0x20), y1)
            mstore(add(ptr, 0x40), x2)
            mstore(add(ptr, 0x60), y2)
            success := staticcall(gas(), 0x06, ptr, 0x80, ptr, 0x40)
            x := mload(ptr)
            y := mload(add(ptr, 0x20))
        }
        if (!success) revert EcAddFailed();
    }

    /// @notice Elliptic curve scalar multiplication
    function ecMul(
        uint256 x, uint256 y, uint256 s
    ) internal view returns (uint256 rx, uint256 ry) {
        bool success;
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, x)
            mstore(add(ptr, 0x20), y)
            mstore(add(ptr, 0x40), s)
            success := staticcall(gas(), 0x07, ptr, 0x60, ptr, 0x40)
            rx := mload(ptr)
            ry := mload(add(ptr, 0x20))
        }
        if (!success) revert EcMulFailed();
    }

    /// @notice Pairing check: e(a1, b1) * e(a2, b2) * ... == 1
    function ecPairing(
        uint256[] memory input
    ) internal view returns (bool success) {
        uint256 inputLen = input.length;
        assembly {
            let ptr := mload(0x40)
            for { let i := 0 } lt(i, inputLen) { i := add(i, 1) } {
                mstore(add(ptr, mul(i, 0x20)), mload(add(add(input, 0x20), mul(i, 0x20))))
            }
            success := staticcall(
                gas(),
                0x08,
                ptr,
                mul(inputLen, 0x20),
                ptr,
                0x20
            )
            success := and(success, mload(ptr))
        }
    }
}
"#;

/// Minimal template for testing.
pub const MINIMAL_TEMPLATE: &str = r#"// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.20;

contract {{CONTRACT_NAME}} {
    function verify(bytes calldata, uint256[] calldata) external pure returns (bool) {
        return true; // Stub
    }
}
"#;

/// Aggregated verifier template with optimized 2-pairing KZG check.
pub const AGGREGATED_VERIFIER_TEMPLATE: &str = r#"// SPDX-License-Identifier: Apache-2.0
pragma solidity {{SOLIDITY_VERSION}};

/// @title {{CONTRACT_NAME}}
/// @notice Optimized PLONK verifier for aggregated proofs (BN254)
/// @dev Uses 2-pairing KZG check (60% cheaper than Groth16)
contract {{CONTRACT_NAME}} {
    // ============ BN254 Curve Parameters ============
    
    uint256 internal constant P = 
        21888242871839275222246405745257275088696311157297823662689037894645226208583;
    
    // ============ Precompile Addresses ============
    
    address internal constant EC_PAIRING = address(0x08);

    // ============ Verification Key ============
    
{{VK_CONSTANTS}}

    // ============ G2 Generators (Standard) ============
    
    uint256 internal constant G2_X1 = 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed;
    uint256 internal constant G2_X2 = 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2;
    uint256 internal constant G2_Y1 = 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa;
    uint256 internal constant G2_Y2 = 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b;

    // ============ Errors ============
    
    error InvalidProofLength();
    error PairingCheckFailed();

    // ============ Verification ============

    /// @notice Verify an aggregated proof
    /// @param proof The encoded proof data (100+ bytes: adjusted_commitment + combined_quotient + count)
    /// @return success True if the proof is valid
    function verify(
        bytes calldata proof,
        uint256[] calldata /* publicInputs */
    ) external view returns (bool success) {
        if (proof.length < 100) revert InvalidProofLength();
        
        uint256[2] memory a;
        uint256[2] memory q;
        
        assembly {
            // Load adjusted_commitment (A)
            let p := proof.offset
            mstore(a, calldataload(p))
            mstore(add(a, 32), calldataload(add(p, 32)))
            
            // Load combined_quotient (Q)
            mstore(q, calldataload(add(p, 64)))
            mstore(add(q, 32), calldataload(add(p, 96)))
        }

        // Optimized KZG check: e(A, G2) · e(-Q, τG2) == 1
        return verifyKzg(a, q);
    }

    /// @notice Verify an aggregated KZG proof with minimal gas cost
    function verifyKzg(
        uint256[2] memory adjustedCommitment,
        uint256[2] memory combinedQuotient
    ) public view returns (bool) {
        // Validate points
        if (!_isOnCurve(adjustedCommitment[0], adjustedCommitment[1])) return false;
        if (!_isOnCurve(combinedQuotient[0], combinedQuotient[1])) return false;

        // Security Patch: Reject identity point (0,0) for adjustedCommitment
        if (adjustedCommitment[0] == 0 && adjustedCommitment[1] == 0) return false;
        
        uint256[12] memory input;
        
        // First pairing: e(adjusted, G2)
        input[0] = adjustedCommitment[0];
        input[1] = adjustedCommitment[1];
        input[2] = G2_X2;
        input[3] = G2_X1;
        input[4] = G2_Y2;
        input[5] = G2_Y1;
        
        // Second pairing: e(-quotient, τG2)
        input[6] = combinedQuotient[0];
        input[7] = (P - combinedQuotient[1]) % P;  // -y mod P
        input[8] = VK_TAU_G2_X2;
        input[9] = VK_TAU_G2_X1;
        input[10] = VK_TAU_G2_Y2;
        input[11] = VK_TAU_G2_Y1;
        
        uint256[1] memory out;
        bool success;
        assembly {
            success := staticcall(gas(), 0x08, input, 384, out, 32)
        }
        
        return success && out[0] == 1;
    }

    function _isOnCurve(uint256 x, uint256 y) internal pure returns (bool) {
        if (x >= P || y >= P) return false;
        if (x == 0 && y == 0) return true;
        uint256 lhs = mulmod(y, y, P);
        uint256 rhs = addmod(mulmod(mulmod(x, x, P), x, P), 3, P);
        return lhs == rhs;
    }
}
"#;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn template_contains_placeholders() {
        assert!(VERIFIER_TEMPLATE.contains("{{CONTRACT_NAME}}"));
        assert!(VERIFIER_TEMPLATE.contains("{{VK_POINTS}}"));
        assert!(VERIFIER_TEMPLATE.contains("{{PROOF_LENGTH}}"));
    }

    #[test]
    fn template_has_precompile_addresses() {
        assert!(VERIFIER_TEMPLATE.contains("0x06")); // ECADD
        assert!(VERIFIER_TEMPLATE.contains("0x07")); // ECMUL
        assert!(VERIFIER_TEMPLATE.contains("0x08")); // ECPAIRING
    }
}
