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.
ZKEmailUtils
: Library for ZKEmail signature validation utilities, enabling email-based authentication through zero-knowledge proofs.WebAuthn
: Library for verifying WebAuthn Authentication Assertions.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.SignerWebAuthn
: Implementation of SignerP256 that supports WebAuthn authentication assertions.ERC7913ZKEmailVerifier
,ERC7913WebAuthnVerifier
: Ready to use ERC-7913 signature verifiers for ZKEmail and WebAuthn.
Utils
Abstract Signers
Verifiers
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);
}
}
Functions
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:
Functions
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:
- Type is "webauthn.get" (see
WebAuthn._validateExpectedTypeHash
) - Challenge matches the expected value (see
WebAuthn._validateChallenge
) - Cryptographic signature is valid for the given public key
- confirming physical user presence during authentication
- (if
requireUV
is true) confirming stronger user authentication (biometrics/PIN) - 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.
Functions
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.
Functions
_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:
SignerZKEmail.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.SignerZKEmail.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 SignerZKEmail._setAccountSalt
, SignerZKEmail._setDKIMRegistry
, and SignerZKEmail._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() → 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 SignerZKEmail.accountSalt
.
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:
- Decoding the email proof from the signature
- Validating the account salt matches
- 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);