Cryptography

Smart contract cryptography utilities and implementations

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.

Utils

ZKEmailUtils

WebAuthn

DKIMRegistry

Abstract Signers

SignerZKEmail

SignerWebAuthn

Verifiers

ERC7913ZKEmailVerifier

ERC7913WebAuthnVerifier

import "@openzeppelin/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);
    }
}

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 IDKIMRegistry.KeyHashRegistered event.

NOTE: 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 IDKIMRegistry.KeyHashRegistered event for each key hash.

NOTE: 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 IDKIMRegistry.KeyHashRevoked event.

import "@openzeppelin/contracts/utils/cryptography/WebAuthn.sol";

Library for verifying WebAuthn Authentication Assertions.

WebAuthn enables strong authentication for smart contracts using P256 as an alternative to traditional secp256k1 ECDSA signatures. This library verifies signatures generated during WebAuthn authentication ceremonies as specified in the WebAuthn Level 2 standard.

For blockchain use cases, the following WebAuthn validations are intentionally omitted:

  • Origin validation: Origin verification in clientDataJSON is omitted as blockchain contexts rely on authenticator and dapp frontend enforcement. Standard authenticators implement proper origin validation.
  • RP ID hash validation: Verification of rpIdHash in authenticatorData against expected RP ID hash is omitted. This is typically handled by platform-level security measures. Including an expiry timestamp in signed data is recommended for enhanced security.
  • Signature counter: Verification of signature counter increments is omitted. While useful for detecting credential cloning, on-chain operations typically include nonce protection, making this check redundant.
  • Extension outputs: Extension output value verification is omitted as these are not essential for core authentication security in blockchain applications.
  • Attestation: Attestation object verification is omitted as this implementation focuses on authentication (webauthn.get) rather than registration ceremonies.

Inspired by:

verify(bytes challenge, struct WebAuthn.WebAuthnAuth auth, bytes32 qx, bytes32 qy) → bool

internal

#

Performs standard verification of a WebAuthn Authentication Assertion.

verify(bytes challenge, struct WebAuthn.WebAuthnAuth auth, bytes32 qx, bytes32 qy, bool requireUV) → bool

internal

#

Performs verification of a WebAuthn Authentication Assertion. This variants allow the caller to select whether of not to require the UV flag (step 17).

Verifies:

  1. Type is "webauthn.get" (see WebAuthn._validateExpectedTypeHash)
  2. Challenge matches the expected value (see WebAuthn._validateChallenge)
  3. Cryptographic signature is valid for the given public key
  4. confirming physical user presence during authentication
  5. (if requireUV is true) confirming stronger user authentication (biometrics/PIN)
  6. Backup Eligibility (BE) and Backup State (BS) bits relationship is valid

tryDecodeAuth(bytes input) → bool success, struct WebAuthn.WebAuthnAuth auth

internal

#

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

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

import "@openzeppelin/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.

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

internal

#

Variant of ZKEmailUtils.isValidZKEmail that validates the ["signHash", "../access#AccessManagerLight-ADMIN_ROLE-uint8"] 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 ZKEmailUtils.EmailProofError indicating the result. Returns EmailProofError.NoError if all validations pass, or a specific ZKEmailUtils.EmailProofError indicating which validation check failed.

NOTE: Attempts to validate the command for all possible string ZKEmailUtils.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 ZKEmailUtils.isValidZKEmail that validates a template with a specific string ZKEmailUtils.Case.

Useful for templates with Ethereum address matchers (i.e. ethAddr), which are case-sensitive (e.g., ["someCommand", "../access#AccessManagerLight-_groups-mapping-address----Masks-Mask-"]).

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.

NOTE: 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.

import "@openzeppelin/contracts/utils/cryptography/signers/SignerWebAuthn.sol";

Implementation of SignerP256 that supports WebAuthn authentication assertions.

This contract enables signature validation using WebAuthn authentication assertions, leveraging the P256 public key stored in the contract. It allows for both WebAuthn and raw P256 signature validation, providing compatibility with both signature types.

The signature is expected to be an abi-encoded WebAuthn.WebAuthnAuth struct.

Example usage:

contract MyAccountWebAuthn is Account, SignerWebAuthn, Initializable {
    function initialize(bytes32 qx, bytes32 qy) public initializer {
        _setSigner(qx, qy);
    }
}

Failing to call ERC7579Signature._setSigner either during construction (if used standalone)

or during initialization (if used as a clone) may leave the signer either front-runnable or unusable.

_rawSignatureValidation(bytes32 hash, bytes signature) → bool

internal

#

Validates a raw signature using the WebAuthn authentication assertion.

In case the signature can't be validated, it falls back to the SignerP256-_rawSignatureValidation method for raw P256 signature validation by passing the raw r and s values from the signature.

import "@openzeppelin/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 SignerZKEmail.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:

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);
  }
}

either during construction (if used standalone) or during initialization (if used as a clone) may leave the signer either front-runnable or unusable.

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

#

setDKIMRegistry(contract IDKIMRegistry registry)

internal

#

Set the DKIMRegistry contract address.

setVerifier(contract IGroth16Verifier verifier)

internal

#

Set the SignerZKEmail.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.

import "@openzeppelin/contracts/utils/cryptography/verifiers/ERC7913WebAuthnVerifier.sol";

ERC-7913 signature verifier that supports WebAuthn authentication assertions.

This verifier enables the validation of WebAuthn signatures using P256 public keys. The key is expected to be a 64-byte concatenation of the P256 public key coordinates (qx || qy). The signature is expected to be an abi-encoded WebAuthn.WebAuthnAuth struct.

Uses WebAuthn-verifyMinimal for signature verification, which performs the essential WebAuthn checks: type validation, challenge matching, and cryptographic signature verification.

NOTE: Wallets that may require default P256 validation may install a P256 verifier separately.

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

public

#

Verifies signature as a valid signature of hash by key.

MUST return the bytes4 magic value IERC7913SignatureVerifier.verify.selector if the signature is valid. SHOULD return 0xffffffff or revert if the signature is not valid. SHOULD return 0xffffffff or revert if the key is empty

import "@openzeppelin/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 ERC7913ZKEmailVerifier._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);
  }

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 ERC7913ZKEmailVerifier._decodeKey for the key encoding format.

The signature is an ABI-encoded ZKEmailUtils.EmailProofError 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);