Abstract

The proposed standard extends ERC-1271 to support historical signature validation. This creates a standard interface for onchain and offchain consumption of ERC-1271-based signatures that may have been valid in the past but no longer correspond to a current EIP-1271isValidSignature function.

Motivation

While externally owned account (EOA) signatures are perpetually valid, smart contract accounts (SCAs) can support updates to their signature validation logic, which means offchain EIP-1271 signatures that appear valid at a point in time are not guaranteed to be valid in the future. This inconsistency poses challenges for applications relying on these signatures to prove validity of attestations, statements, intents, or authorizations over time, particularly when interacting with SCAs that support rotating signers.

Specification

Functions

wasValidSignature

  • Purpose: This function checks if a signature was valid at a specific point in time.
  • Parameters:
    • _timestamp: Timestamp at which the signature needs to be validated.
    • _hash: Hash of the data to be signed.
    • _signature: Signature byte array associated with the hash.
    • _data: Arbitrary extra data used for verification.
  • Returns: The bytes4 magic value 0x1626ba7e if the signature is verified as valid.
  • Requirements:
    • MUST NOT modify state, ensuring it can be called safely by other contracts.
    • MUST allow external calls.

Solidity interface

pragma solidity ^0.5.0;
/// @title Example interface for historical validity functionality extending ERC1271
interface IERCXXX {
    /// @dev Should return whether the signature provided was valid at the time indicated by the timestamp.
    /// @param _timestamp The timestamp at which the signature needs to be validated.
    /// @param _hash Hash of the data to be signed.
    /// @param _signature Signature byte array associated with _hash.
    /// @param _data Arbitrary extra data that may be used by the verifier.
    /// @return The bytes4 magic value 0x1626ba7e when function passes
    /// MUST NOT modify state.
    /// MUST allow external calls.
    function wasValidSignature(
        uint64 _timestamp,
        bytes32 _hash,
        bytes memory _signature,
        bytes memory _data
    ) external view returns (bytes4);
}

Rationale

The introduction of historical validation mechanisms in EIP-1271 addresses a critical gap in the evolving landscape of SCAs and their interaction with digital signatures. As SCAs are updated over time, the logic governing signature validation can change, potentially rendering previously valid signatures invalid. This inconsistency poses challenges in scenarios where historical messages and signatures must remain verifiable indefinitely.

Design Decisions

  • wasValidSignature: By allowing verification against a specific point-in-time via the _timestamp parameter, this function provides the flexibility needed for applications to confirm the validity of a signature that may not otherwise appear as valid. This is particularly important for SCA compatibility for attestation and other offchain-signature-based workflows, where the signature may not need to be verified by the chain until some future point in time, and the ability to retroactively invalidate signatures in this manner may be undesirable. The rest of the function's signature matches EIP-1271.

Backwards Compatibility

This EIP is designed to be fully compatible with EIP-1271 and does not affect existing implementations that do not require historical signature validation.

Reference Implementation

While this simple implementation requires data to be posted directly onchain to ensure its validity at a point in time, more opinionated flavors may take advantage of the extra _data parameter to allow verification via more bespoke patterns such as by providing proofs of existence.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/// @title ERCXXXX
/// @dev Contract for managing digital signatures with timestamped verifiers
import {IERC1271} from "./interfaces/IERC1271.sol";
import {IERCXXXX} from "./interfaces/IERCXXXX.sol";
import {Owned} from "solmate/src/auth/Owned.sol";
import {SignatureCheckerLib} from "solady/src/utils/SignatureCheckerLib.sol";

/// @notice Contract that implements the IERCXXXX interface
contract ERCXXXX is IERCXXXX, Owned {
    /// @notice Mapping of approved hashes to their timestamps
    /// @dev If timestamp is 0, then the hash is not approved
    mapping(bytes32 => uint256) public approvedHashes;
    /// @notice Constant for the magic value returned for a valid signature
    bytes4 constant MAGICVALUE = bytes4(0x1626ba7e);

    /// @notice Contract constructor
    constructor() Owned(msg.sender) {}

    /// @notice Checks if the signature was valid at a given timestamp
    /// @param _timestamp The timestamp to check the signature against
    /// @param _hash The hash of the data
    /// @param _signature The signature to validate
    /// @param _data Additional data to validate the signature
    /// @return magicValue Standard magic value for a valid signature, 0x00000000 for an invalid signature
    function wasValidSignature(uint64 _timestamp, bytes32 _hash, bytes memory _signature, bytes memory _data)
        public
        view
        override
        returns (bytes4)
    {
        // If the hash is not approved, return 0x00000000
        if (approvedHashes[_hash] == 0) {
            return 0x00000000;
        } 
        // If the hash was approved before or at the given timestamp, return the magic value
        else if (approvedHashes[_hash] <= _timestamp) {
            return MAGICVALUE;
        } 
        // If the hash was approved after the given timestamp, return 0x00000000
        else {
            return 0x00000000;
        }
    }

    /// @notice Approves a hash
    /// @dev Only the owner can approve a hash
    /// @param hash The hash to approve
    function setApproveHash(bytes32 hash) public onlyOwner {
        approvedHashes[hash] = block.timestamp;
    }
}

Security Considerations

Care must be taken to ensure that the update mechanism for the signature validation logic is secure against unauthorized changes. Additionally, the storage method chosen must be optimized to prevent excessive gas costs associated with storing historical data.

Select a repo