Skip to main content

Signature Verification

Relay Contracts support two distinct signature verification approaches optimized for different validator set sizes. Choosing the right approach is crucial for gas efficiency and scalability.

Overview

Both verifiers use BLS BN254 signatures for their cryptographic properties:

  • Signature Aggregation: Multiple signatures can be combined into one
  • Key Aggregation: Multiple public keys can be combined
  • Batch Verification: Verify many signatures efficiently
  • Deterministic: Same input always produces same output

Simple Verifier (SigVerifierBlsBn254Simple)

Best for: Up to ~125 validators Gas Cost: Linear with validator set size Verification Type: 1

How It Works

  1. Input Requirements: Full validator set must be provided for each verification
  2. On-chain Processing: Contract performs all cryptographic operations
  3. Gas Usage: Increases linearly with number of validators
  4. Simplicity: No external dependencies or preprocessing

Verification Process

function verifySignature(
bytes32 message, // Message that was signed
bytes memory signature, // Aggregated BLS signature
ValidatorSetProof memory proof // Full validator set data
) external view returns (bool) {
// 1. Reconstruct validator set from proof
BN254.G1Point memory aggregatedKey = BN254.G1Point(0, 0);

for (uint256 i = 0; i < proof.validators.length; i++) {
// 2. Aggregate public keys based on participation bitmap
if (proof.bitmap[i]) {
aggregatedKey = aggregatedKey.add(proof.validators[i].publicKey);
}
}

// 3. Verify aggregated signature against aggregated key
return BLS.pairing(
signature.hashToG1(message),
BN254.generatorG2(),
aggregatedKey.negate(),
proof.aggregatedPublicKey
);
}

Data Structures

struct ValidatorSetProof {
Validator[] validators; // Full validator information
bytes bitmap; // Which validators signed (bitfield)
BN254.G1Point aggregatedPublicKey; // Pre-computed aggregate key
uint256 totalVotingPower; // Total power of signers
}

struct Validator {
address operator; // Operator address
BN254.G1Point publicKey; // BLS public key
uint256 votingPower; // Voting power weight
}

Advantages

  • Simple Implementation: No external dependencies
  • Immediate Verification: No preprocessing required
  • Transparent: All logic is on-chain and auditable
  • Flexible: Can verify partial signatures easily

Limitations

  • Gas Scaling: Becomes expensive with large validator sets
  • Data Overhead: Must provide full validator set each time

ZK Verifier (SigVerifierBlsBn254ZK)

Best for: 100+ validators Gas Cost: Nearly constant regardless of validator set size Verification Type: 0

How It Works

  1. ZK Proof Generation: Off-chain generation of validity proof
  2. Constant Verification: On-chain verification of ZK proof
  3. Gnark Integration: Uses gnark library for ZK proof system
  4. Preprocessing: Requires setup for specific validator set sizes

Architecture

Off-chain:     Full Validator Set + Signature → ZK Proof Generation

On-chain: ZK Proof + Message → Constant Cost Verification

Supported Configurations

The system includes pre-built verifier circuits that are chosen according to max operators count configuration

// Available verifier contracts
import {Verifier as Verifier_10} from "./data/zk/Verifier_10.sol";
import {Verifier as Verifier_100} from "./data/zk/Verifier_100.sol";
import {Verifier as Verifier_1000} from "./data/zk/Verifier_1000.sol";

Verification Process

function verifySignature(
bytes32 message, // Message that was signed
bytes memory signature, // Not used directly
bytes memory zkProof // ZK proof of valid signature
) external view returns (bool) {
// 1. Extract public inputs from committed validator set
uint256[] memory publicInputs = _extractPublicInputs(message);

// 2. Verify ZK proof against public inputs
return zkVerifier.verifyProof(
zkProof,
publicInputs
);
}

ZK Proof Contents

The ZK proof establishes:

  • Valid Signature: Aggregated signature is valid for given message
  • Validator Set Consistency: Signers are from committed validator set
  • Voting Power: Sufficient voting power threshold met
  • Key Correctness: Public keys match committed validator set

Gas Estimations

Around 380000 gas for verification

Key Performance Insights

ZK Verification Characteristics

  • Constant Gas Cost: ZK verification maintains nearly constant gas costs (~383k-388k gas) regardless of validator set size
  • Time Scaling: Verification time increases with validator count but remains reasonable (~14.5s for 200 validators)
  • Scalability: Can handle very large validator sets without hitting block gas limits

Fully On-chain Verification Characteristics

  • Linear Gas Scaling: Gas costs increase linearly with validator count
  • Cost Efficiency: More cost-effective for small validator sets (≤50 validators)
  • Block Limit: Approaches Ethereum block gas limit around 1000+ validators
  • Optimization Impact: Optimized version shows ~13% gas reduction for 100 validators

Advantages

  • Scalability: Constant gas cost regardless of validator count
  • Efficiency: Can handle very large validator sets
  • Privacy: Can hide validator participation details
  • Future-Proof: Scales to any reasonable validator set size

Limitations

  • Complexity: Requires ZK proof generation infrastructure
  • Setup Dependency: Must choose correct verifier contract size
  • Circuit Constraints: Fixed maximum validator counts per circuit

Implementation Details

Settlement Integration

Both verifiers integrate with Settlement contract:

contract MySettlement is Settlement {
function initialize(
SettlementInitParams memory settlementInitParams,
address defaultAdmin
) public initializer {
__Settlement_init(settlementInitParams);
__OzAccessControl_init();
_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);
}
}

// Settlement uses configured verifier
ISettlement.SettlementInitParams({
// ... other params
sigVerifier: address(chosenVerifier)
})

Validator Set Headers

Both verifiers work with compressed validator set headers:

struct ValSetHeader {
uint8 version;
uint8 requiredKeyTag;
uint48 epoch;
uint48 captureTimestamp;
uint256 quorumThreshold;
bytes32 validatorsSszMRoot;
bytes32 previousHeaderHash;
}

Migration Between Verifiers

Networks can migrate between verifier types:

  1. Deploy New Verifier: Deploy the target verifier contract
  2. Update Settlement: Change the verifier address in Settlement
  3. Coordinate Transition: Ensure all network participants update
  4. Verify Functionality: Test with new verifier before full migration