// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {CheckpointExtensionBase} from "./CheckpointExtensionBase.sol"; import {CheckpointStorage} from "../storage/CheckpointStorage.sol"; import {CheckpointEIP712} from "../libraries/CheckpointEIP712.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; /// @notice Validator registry + ECDSA recovery for batch attestations and 65-byte sig bundles. contract ValidatorSigVerifierExtension is CheckpointExtensionBase { using ECDSA for bytes32; mapping(address => bool) public validators; uint256 public threshold; address public verifyingContract; event ValidatorSetUpdated(address[] validators, uint256 threshold); function HOOK_BEFORE_SUBMIT() external pure override returns (uint32) { return 1 << 0; } function HOOK_AFTER_SUBMIT() external pure override returns (uint32) { return 0; } function HOOK_ON_CCIP() external pure override returns (uint32) { return 0; } function setVerifyingContract(address hub) external { verifyingContract = hub; } function setValidators(address[] calldata addrs, uint256 newThreshold) external { for (uint256 i = 0; i < addrs.length; i++) { validators[addrs[i]] = true; } threshold = newThreshold; emit ValidatorSetUpdated(addrs, newThreshold); } function beforeSubmit(CheckpointStorage.CheckpointHeader calldata header, bytes calldata validatorSignatures) external view { if (threshold == 0) return; require(verifyingContract != address(0), "verifier unset"); bytes32 digest = CheckpointEIP712.digest(verifyingContract, block.chainid, header); if (validatorSignatures.length == 65) { address signer = ECDSA.recover(digest, validatorSignatures); require(validators[signer], "attest signer"); return; } require(validatorSignatures.length >= threshold * 65, "sig length"); uint256 valid; for (uint256 i = 0; i + 65 <= validatorSignatures.length; i += 65) { bytes memory sig = new bytes(65); for (uint256 j = 0; j < 65; j++) { sig[j] = validatorSignatures[i + j]; } address signer = ECDSA.recover(digest, sig); if (validators[signer]) valid++; } require(valid >= threshold, "threshold"); } function afterSubmit(CheckpointStorage.CheckpointHeader calldata, bytes calldata) external pure override {} function onCCIPReceive(bytes calldata) external pure override {} }