Cryptography

A collection of contracts and libraries that implement various signature validation schemes and cryptographic primitives. These utilities enable secure authentication, multisignature operations, and advanced cryptographic operations in smart contracts.

  • ZKEmailUtils: Library for ZKEmail signature validation utilities, enabling email-based authentication through zero-knowledge proofs.

  • DKIMRegistry: Implementation of ERC-7969 to enable onchain verification of DomainKeys Identified Mail (DKIM) signatures.

  • SignerZKEmail: Implementation of an AbstractSigner that enables email-based authentication through zero-knowledge proofs.

  • ERC7913ZKEmailVerifier: Ready to use ERC-7913 signature verifiers for ZKEmail.

Utils

ZKEmailUtils

import "@openzeppelin/community-contracts/utils/cryptography/ZKEmailUtils.sol";

Library for ZKEmail Groth16 proof validation utilities.

ZKEmail is a protocol that enables email-based authentication and authorization for smart contracts using zero-knowledge proofs. It allows users to prove ownership of an email address without revealing the email content or private keys.

The validation process involves several key components:

  • A DKIMRegistry (DomainKeys Identified Mail) verification mechanism to ensure the email was sent from a valid domain. Defined by an IDKIMRegistry interface.

  • A command template validation mechanism to ensure the email command matches the expected format and parameters.

  • A zero-knowledge proof verification mechanism to ensure the email was actually sent and received without revealing its contents. Defined by an IGroth16Verifier interface.

Functions
  • isValidZKEmail(emailProof, dkimregistry, groth16Verifier, hash)

  • isValidZKEmail(emailProof, dkimregistry, groth16Verifier, template, templateParams)

  • isValidZKEmail(emailProof, dkimregistry, groth16Verifier, template, templateParams, stringCase)

  • tryDecodeEmailProof(input)

  • toPubSignals(proof)

Internal Variables
  • uint256 constant DOMAIN_FIELDS

  • uint256 constant DOMAIN_BYTES

  • uint256 constant COMMAND_FIELDS

  • uint256 constant COMMAND_BYTES

  • uint256 constant Q

isValidZKEmail(struct EmailProof emailProof, contract IDKIMRegistry dkimregistry, contract IGroth16Verifier groth16Verifier, bytes32 hash) → enum ZKEmailUtils.EmailProofError internal

Variant of isValidZKEmail that validates the ["signHash", "{uint}"] command template.

isValidZKEmail(struct EmailProof emailProof, contract IDKIMRegistry dkimregistry, contract IGroth16Verifier groth16Verifier, string[] template, bytes[] templateParams) → enum ZKEmailUtils.EmailProofError internal

Validates a ZKEmail proof against a command template.

This function takes an email proof, a DKIM registry contract, and a verifier contract as inputs. It performs several validation checks and returns an EmailProofError indicating the result. Returns {EmailProofError.NoError} if all validations pass, or a specific EmailProofError indicating which validation check failed.

Attempts to validate the command for all possible string Case values.

isValidZKEmail(struct EmailProof emailProof, contract IDKIMRegistry dkimregistry, contract IGroth16Verifier groth16Verifier, string[] template, bytes[] templateParams, enum ZKEmailUtils.Case stringCase) → enum ZKEmailUtils.EmailProofError internal

Variant of isValidZKEmail that validates a template with a specific string Case.

Useful for templates with Ethereum address matchers (i.e. {ethAddr}), which are case-sensitive (e.g., ["someCommand", "{address}"]).

tryDecodeEmailProof(bytes input) → bool success, struct EmailProof emailProof internal

Verifies that calldata bytes (input) represents a valid EmailProof object. If encoding is valid, returns true and the calldata view at the object. Otherwise, returns false and an invalid calldata object.

The returned emailProof object should not be accessed if success is false. Trying to access the data may cause revert/panic.

toPubSignals(struct EmailProof proof) → uint256[34] pubSignals internal

Builds the expected public signals array for the Groth16 verifier from the given EmailProof.

Packs the domain, public key hash, email nullifier, timestamp, masked command, account salt, and isCodeExist fields into a uint256 array in the order expected by the verifier circuit.

uint256 DOMAIN_FIELDS internal constant

uint256 DOMAIN_BYTES internal constant

uint256 COMMAND_FIELDS internal constant

uint256 COMMAND_BYTES internal constant

uint256 Q internal constant

The base field size for BN254 elliptic curve used in Groth16 proofs.

DKIMRegistry

import "@openzeppelin/community-contracts/utils/cryptography/DKIMRegistry.sol";

Implementation of the ERC-7969 interface for registering and validating DomainKeys Identified Mail (DKIM) public key hashes onchain.

This contract provides a standard way to register and validate DKIM public key hashes, enabling email-based account abstraction and secure account recovery mechanisms. Domain owners can register their DKIM public key hashes and third parties can verify their validity.

The contract stores mappings of domain hashes to DKIM public key hashes, where:

  • Domain hash: keccak256 hash of the lowercase domain name

  • Key hash: keccak256 hash of the DKIM public key

Example of usage:

contract MyDKIMRegistry is DKIMRegistry, Ownable {
    function setKeyHash(bytes32 domainHash, bytes32 keyHash) public onlyOwner {
        _setKeyHash(domainHash, keyHash);
    }

    function setKeyHashes(bytes32 domainHash, bytes32[] memory keyHashes) public onlyOwner {
        _setKeyHashes(domainHash, keyHashes);
    }

    function revokeKeyHash(bytes32 domainHash, bytes32 keyHash) public onlyOwner {
        _revokeKeyHash(domainHash, keyHash);
    }
}
Functions
  • isKeyHashValid(domainHash, keyHash)

  • _setKeyHash(domainHash, keyHash)

  • _setKeyHashes(domainHash, keyHashes)

  • _revokeKeyHash(domainHash, keyHash)

Events
IDKIMRegistry
  • KeyHashRegistered(domainHash, keyHash)

  • KeyHashRevoked(domainHash)

isKeyHashValid(bytes32 domainHash, bytes32 keyHash) → bool public

Returns whether a DKIM key hash is valid for a given domain.

_setKeyHash(bytes32 domainHash, bytes32 keyHash) internal

Sets a DKIM key hash as valid for a domain. Internal version without access control.

Emits a {KeyHashRegistered} event.

This function does not validate that keyHash is non-zero. Consider adding validation in derived contracts if needed.

_setKeyHashes(bytes32 domainHash, bytes32[] keyHashes) internal

Sets multiple DKIM key hashes as valid for a domain in a single transaction. Internal version without access control.

Emits a {KeyHashRegistered} event for each key hash.

This function does not validate that the keyHashes array is non-empty. Consider adding validation in derived contracts if needed.

_revokeKeyHash(bytes32 domainHash, bytes32 keyHash) internal

Revokes a DKIM key hash for a domain, making it invalid. Internal version without access control.

Emits a {KeyHashRevoked} event.

Abstract Signers

SignerZKEmail

import "@openzeppelin/community-contracts/utils/cryptography/signers/SignerZKEmail.sol";

Implementation of {AbstractSigner} using ZKEmail signatures.

ZKEmail enables secure authentication and authorization through email messages, leveraging DKIM signatures from a DKIMRegistry and zero-knowledge proofs enabled by a verifier contract that ensures email authenticity without revealing sensitive information. The DKIM registry is trusted to correctly update DKIM keys, but users can override this behaviour and set their own keys. This contract implements the core functionality for validating email-based signatures in smart contracts.

Developers must set the following components during contract initialization:

  • accountSalt - A unique identifier derived from the user’s email address and account code.

  • DKIMRegistry - An instance of the DKIM registry contract for domain verification.

  • verifier - An instance of the Groth16Verifier contract for zero-knowledge proof validation.

Example of usage:

contract MyAccountZKEmail is Account, SignerZKEmail, Initializable {
  function initialize(
      bytes32 accountSalt,
      IDKIMRegistry registry,
      IGroth16Verifier groth16Verifier
  ) public initializer {
      // Will revert if the signer is already initialized
      _setAccountSalt(accountSalt);
      _setDKIMRegistry(registry);
      _setVerifier(groth16Verifier);
  }
}
Failing to call _setAccountSalt, _setDKIMRegistry, and _setVerifier either during construction (if used standalone) or during initialization (if used as a clone) may leave the signer either front-runnable or unusable.
Functions
  • accountSalt()

  • DKIMRegistry()

  • verifier()

  • _setAccountSalt(accountSalt_)

  • _setDKIMRegistry(registry_)

  • _setVerifier(verifier_)

  • _rawSignatureValidation(hash, signature)

Errors
  • InvalidEmailProof(err)

accountSalt() → bytes32 public

Unique identifier for owner of this contract defined as a hash of an email address and an account code.

An account code is a random integer in a finite scalar field of BN254 curve. It is a private randomness to derive a CREATE2 salt of the user’s Ethereum address from the email address, i.e., userEtherAddr := CREATE2(hash(userEmailAddr, accountCode)).

The account salt is used for:

  • Privacy: Enables email address privacy on-chain so long as the randomly generated account code is not revealed to an adversary.

  • Security: Provides a unique identifier that cannot be easily guessed or brute-forced, as it’s derived from both the email address and a random account code.

  • Deterministic Address Generation: Enables the creation of deterministic addresses based on email addresses, allowing users to recover their accounts using only their email.

DKIMRegistry() → contract IDKIMRegistry public

An instance of the DKIM registry contract. See DKIM Verification.

verifier() → contract IGroth16Verifier public

An instance of the Groth16Verifier contract. See ZK Proofs.

_setAccountSalt(bytes32 accountSalt_) internal

Set the accountSalt.

_setDKIMRegistry(contract IDKIMRegistry registry_) internal

Set the DKIMRegistry contract address.

_setVerifier(contract IGroth16Verifier verifier_) internal

Set the verifier contract address.

_rawSignatureValidation(bytes32 hash, bytes signature) → bool internal

See {AbstractSigner-_rawSignatureValidation}. Validates a raw signature by:

  1. Decoding the email proof from the signature

  2. Validating the account salt matches

  3. Verifying the email proof using ZKEmail utilities

InvalidEmailProof(enum ZKEmailUtils.EmailProofError err) error

Proof verification error.

Verifiers

ERC7913ZKEmailVerifier

import "@openzeppelin/community-contracts/utils/cryptography/verifiers/ERC7913ZKEmailVerifier.sol";

ERC-7913 signature verifier that supports ZKEmail accounts.

This contract verifies signatures produced through ZKEmail’s zero-knowledge proofs which allows users to authenticate using their email addresses.

The key decoding logic is customizable: users may override the _decodeKey function to enforce restrictions or validation on the decoded values (e.g., requiring a specific verifier or registry). To remain compliant with ERC-7913’s statelessness, it is recommended to enforce such restrictions using immutable variables only.

Example of overriding _decodeKey to enforce a specific verifier, registry:

  function _decodeKey(bytes calldata key) internal view override returns (
      IDKIMRegistry registry,
      bytes32 accountSalt,
      IGroth16Verifier verifier
  ) {
      (registry, accountSalt, verifier) = super._decodeKey(key);
      require(verifier == _verifier, "Invalid verifier");
      require(registry == _registry, "Invalid registry");
      return (registry, accountSalt, verifier);
  }
Functions
  • verify(key, hash, signature)

  • _decodeKey(key)

verify(bytes key, bytes32 hash, bytes signature) → bytes4 public

Verifies a zero-knowledge proof of an email signature validated by a DKIMRegistry contract.

The key format is ABI-encoded (IDKIMRegistry, bytes32, IGroth16Verifier) where:

  • IDKIMRegistry: The registry contract that validates DKIM public key hashes

  • bytes32: The account salt that uniquely identifies the user’s email address

  • IGroth16Verifier: The verifier contract instance for ZK proof verification.

See _decodeKey for the key encoding format.

The signature is an ABI-encoded {EmailProof} struct containing the proof details.

Signature encoding:

bytes memory signature = abi.encode(EmailProof({
    domainName: "example.com", // The domain name of the email sender
    publicKeyHash: bytes32(0x...), // Hash of the DKIM public key used to sign the email
    timestamp: block.timestamp, // When the email was sent
    maskedCommand: "signHash 12345...", // The command being executed, with sensitive data masked
    emailNullifier: bytes32(0x...), // Unique identifier for the email to prevent replay attacks
    accountSalt: bytes32(0x...), // Unique identifier derived from email and account code
    isCodeExist: true, // Whether the account code exists in the proof
    proof: bytes(0x...) // The zero-knowledge proof verifying the email's authenticity
}));

_decodeKey(bytes key) → contract IDKIMRegistry registry, bytes32 accountSalt, contract IGroth16Verifier verifier internal

Decodes the key into its components.

bytes memory key = abi.encode(registry, accountSalt, verifier);