Consensus

Purpose

The Consensus contract manages a permissioned set of signers and enforces multi-signature validation logic using either EIP-712 or EIP-1271 signatures. It is a lightweight module designed for verifying offchain consensus before executing critical actions such as deposit and redemptions via SignatureQueues.

It supports:

  • Threshold-based consensus

  • Two signature modes: EIP712 (EOA) and EIP1271 (contract-based)

  • Dynamic signer set management

  • Stateless, reusable verification interface

Core Concepts

Threshold-Based Verification

To validate an action, a set of authorized signers must collectively submit signatures. The number of valid signatures must be greater than or equal to the configured threshold.

Signature Types

Each signer is associated with a SignatureType:

  • EIP712 – Used for externally owned accounts (standard ECDSA.recover)

  • EIP1271 – Used for contract accounts (via isValidSignature())

Storage Layout

struct ConsensusStorage {
    uint256 threshold;
    EnumerableMap.AddressToUintMap signers;
}
  • threshold: Minimum number of valid signatures required for verification to succeed.

  • signers: Mapping of signer addresses → their configured signature type.

Initialization

function initialize(bytes calldata data)
  • Expects abi.encode(owner) as input.

  • Sets the initial owner using OwnableUpgradeable.

Signature Verification

checkSignatures

function checkSignatures(bytes32 orderHash, Signature[] calldata signatures) public view returns (bool)
  • Returns true if:

    • At least threshold signatures are present

    • Each signature is:

      • From an authorized signer

      • Valid according to the signer’s configured signature type

  • Returns false otherwise

Signature validation behavior:

  • EIP712: Uses ECDSA.recover(orderHash, sig) and matches signer

  • EIP1271: Calls isValidSignature(orderHash, sig) on the contract

requireValidSignatures

function requireValidSignatures(bytes32 orderHash, Signature[] calldata signatures) external view
  • Same logic as checkSignatures, but reverts with InvalidSignatures error if validation fails

Signer Management (Owner-only)

setThreshold

function setThreshold(uint256 threshold_) external onlyOwner
  • Sets a new threshold

  • Must be > 0 and ≤ signers.length()

  • Emits ThresholdSet

addSigner

function addSigner(address signer, uint256 threshold_, SignatureType sigType) external onlyOwner
  • Adds a new signer with specified signature type

  • Updates threshold (as part of signer addition)

  • Reverts if:

    • signer == address(0)

    • Signer already exists

  • Emits SignerAdded and ThresholdSet

removeSigner

function removeSigner(address signer, uint256 threshold_) external onlyOwner
  • Removes signer from consensus set

  • Updates threshold

  • Reverts if signer not found

  • Emits SignerRemoved and ThresholdSet

View Functions

Function
Returns

threshold()

Current consensus threshold

signers()

Total number of signers

signerAt(uint256)

Signer address and type at index

isSigner(address)

Boolean indicating if address is a signer

Events

  • Initialized(bytes)

  • ThresholdSet(uint256)

  • SignerAdded(address signer, SignatureType)

  • SignerRemoved(address signer)

  • InvalidSignatures(bytes32 hash, Signature[] signatures) (used in revert)

Security Considerations

  • Only the owner (via OwnableUpgradeable) may update signer set or threshold

  • Signatures are stateless and externally verifiable

  • Replay protection (e.g., nonce checks) must be handled by upstream systems (nonces)

  • Signers using EIP1271 are trusted for contract logic – contracts must not be mutable without governance

Last updated