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
- Input Requirements: Full validator set must be provided for each verification
- On-chain Processing: Contract performs all cryptographic operations
- Gas Usage: Increases linearly with number of validators
- 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
- ZK Proof Generation: Off-chain generation of validity proof
- Constant Verification: On-chain verification of ZK proof
- Gnark Integration: Uses gnark library for ZK proof system
- 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:
- Deploy New Verifier: Deploy the target verifier contract
- Update Settlement: Change the verifier address in Settlement
- Coordinate Transition: Ensure all network participants update
- Verify Functionality: Test with new verifier before full migration