Input Proofs

Input proof validation and batching strategies in FHEVM. Explains why proofs are essential (preventing replay attacks and invalid data) and demonstrates gas-efficient batching where one proof validates multiple encrypted inputs.

circle-info

To run this example correctly, make sure the files are placed in the following directories:

  • .sol file → <your-project-root-dir>/contracts/

  • .ts file → <your-project-root-dir>/test/

This ensures Hardhat can compile and test your contracts as expected.

chevron-right🔐 FHE API Reference (8 items)hashtag

Types: euint32 · euint64 · externalEuint32 · externalEuint64

Functions:

  • FHE.add() - Homomorphic addition: result = a + b (overflow wraps)

  • FHE.allow() - Grants PERMANENT permission for address to decrypt/use value

  • FHE.allowThis() - Grants contract permission to operate on ciphertext

  • FHE.fromExternal() - Validates and converts external encrypted input using inputProof

// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.24;

import {
    FHE,
    euint32,
    euint64,
    externalEuint32,
    externalEuint64
} from "@fhevm/solidity/lib/FHE.sol";
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";

/**
 * @notice Input proof validation and batching strategies in FHEVM.
 *         Explains why proofs are essential (preventing replay attacks and invalid
 *         data) and demonstrates gas-efficient batching where one proof validates
 *         multiple encrypted inputs.
 *
 * @dev Batching multiple values in ONE proof saves ~50k gas vs separate proofs.
 */
contract FHEInputProof is ZamaEthereumConfig {
    euint32 private _singleValue;
    euint32 private _valueA;
    euint64 private _valueB;

    /// @notice Receive single encrypted value with proof
    function setSingleValue(
        externalEuint32 encryptedInput,
        bytes calldata inputProof
    ) external {
        // 🔐 Why proof needed? Prevents: garbage data, wrong type, replay attacks
        _singleValue = FHE.fromExternal(encryptedInput, inputProof);

        FHE.allowThis(_singleValue);
        FHE.allow(_singleValue, msg.sender);
    }

    /// @notice Receive multiple values with SINGLE proof (gas efficient!)
    /// @dev Client batches: input.add32(a).add64(b).encrypt() → one proof for both!
    function setMultipleValues(
        externalEuint32 inputA,
        externalEuint64 inputB,
        bytes calldata inputProof // Single proof covers both!
    ) external {
        // 💡 Same proof validates both - saves ~50k gas!
        _valueA = FHE.fromExternal(inputA, inputProof);
        _valueB = FHE.fromExternal(inputB, inputProof);

        // Each value needs own permissions
        FHE.allowThis(_valueA);
        FHE.allowThis(_valueB);
        FHE.allow(_valueA, msg.sender);
        FHE.allow(_valueB, msg.sender);
    }

    /// @notice Add new encrypted input to existing stored value
    function addToValue(
        externalEuint32 addend,
        bytes calldata inputProof
    ) external {
        // Validate the new input
        euint32 validatedAddend = FHE.fromExternal(addend, inputProof);

        // Combine with stored value
        _singleValue = FHE.add(_singleValue, validatedAddend);

        FHE.allowThis(_singleValue);
        FHE.allow(_singleValue, msg.sender);
    }

    function getSingleValue() external view returns (euint32) {
        return _singleValue;
    }

    function getValueA() external view returns (euint32) {
        return _valueA;
    }

    function getValueB() external view returns (euint64) {
        return _valueB;
    }
}

Last updated