Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
89cecfc
Add ZKEmailSigner
ernestognw Mar 25, 2025
bea0548
Remove yarn.lock
ernestognw Mar 25, 2025
b968068
Update ZKEmailSigner.sol
Amxx Mar 26, 2025
b99b9f8
Add initializer
ernestognw Mar 27, 2025
f5a2643
Merge branch 'master' into feature/zk-email
ernestognw Apr 3, 2025
9577e06
Run `forge upate lib/email-tx-builder` to bring recent changes
ernestognw Apr 3, 2025
813b226
Add ZKEmailUtils and update ZKEmailSigner
ernestognw Apr 3, 2025
ef5fe72
Add BN256 curve reference
ernestognw Apr 3, 2025
d9cba45
Add .prettierignore to ignore lib linting
ernestognw Apr 3, 2025
ccc43fa
Exclude zk-email from pragma consistency checks
ernestognw Apr 3, 2025
24ed077
Update contracts/utils/cryptography/ZKEmailSigner.sol
Amxx Apr 3, 2025
82850cb
Update contracts/utils/cryptography/ZKEmailSigner.sol
Amxx Apr 3, 2025
b6a3fdf
Update ZKEmailSigner.sol
Amxx Apr 3, 2025
3dd1fb9
Run `forge update`
ernestognw Apr 3, 2025
6bc3140
Document ZKEmailSigner and ZKEmailUtils
ernestognw Apr 3, 2025
27dcc0d
Rename to SignerZKEmail for consistency
ernestognw Apr 3, 2025
35b0bd6
Document ZKEmailSigner and ZKEmailUtils
ernestognw Apr 3, 2025
900b668
Update README to include SignerZKEmail and ZKEmailUtils documentation
ernestognw Apr 3, 2025
e8fa099
Simplify return type for ZKEmailUtils.isValidZKEmail
Amxx Apr 4, 2025
4b6426c
Setup tests
ernestognw Apr 4, 2025
581123d
Rename commandTemplate to templateId
ernestognw Apr 4, 2025
11b197f
Address https://github.com/OpenZeppelin/openzeppelin-community-contra…
Amxx Apr 4, 2025
6d5f974
Fix compilation
ernestognw Apr 4, 2025
3e9d0e3
Refactor ZKEmailUtils
ernestognw Apr 4, 2025
72a4cfc
Adjustment
ernestognw Apr 4, 2025
9742daa
Refactor ZKEmailUtils
ernestognw Apr 4, 2025
34aa505
Note _matchCase
ernestognw Apr 4, 2025
fa8b58b
Remove _commandMatch
ernestognw Apr 4, 2025
471e4de
refactor
Amxx Apr 4, 2025
aa7cdaa
Readd refactored _commandMatch
ernestognw Apr 4, 2025
190f0b3
Remove extra line
ernestognw Apr 4, 2025
202b59f
Add missing logic to remove prefix using Bytes library
ernestognw Apr 4, 2025
8f0a057
Fix compilation
ernestognw Apr 4, 2025
ed5e027
Pragma increase for mcopy support
ernestognw Apr 4, 2025
4ec7569
Apply pragma to SignerZKEmail too
ernestognw Apr 4, 2025
8a202bd
Fix pragma in mocks
ernestognw Apr 4, 2025
d3e0ddf
Add @JohnGuilding and @zkfriendly to CODEOWNERS
ernestognw Apr 4, 2025
6d4f9f6
Try alternative syntax
ernestognw Apr 4, 2025
683f3fc
Revert CODEOWNERS
ernestognw Apr 4, 2025
c3275d2
Address review suggestions
ernestognw Apr 8, 2025
1d428d4
Add ZKEmailUtils tests
ernestognw Apr 8, 2025
521fbdd
Improve coverage
ernestognw Apr 8, 2025
7842fa6
Moar coverage
ernestognw Apr 8, 2025
6bb0c00
Remove verifyEmail from signer
ernestognw Apr 8, 2025
8a5a283
Merge branch 'master' into feature/zk-email
ernestognw Apr 8, 2025
c039530
Remove unnecessary imports
ernestognw Apr 10, 2025
cd6f66f
Reorder ZKEmailUtils checks
ernestognw Apr 10, 2025
de69659
Moar tests
ernestognw Apr 10, 2025
e70f6d1
Simplify ZKEmailUtils.t.sol
ernestognw Apr 10, 2025
a6dcddd
Update syntax in _commandMatch to test codecov
ernestognw Apr 10, 2025
de157d8
Improve fuzz tests
ernestognw Apr 11, 2025
c9672d9
Improve fuzz tests
ernestognw Apr 11, 2025
9ee4ccd
Remove DKIM mock
ernestognw Apr 11, 2025
f26d26b
Add Hardhat tests
ernestognw Apr 11, 2025
799cf6c
Fix codespell
ernestognw Apr 11, 2025
f27f1a1
Add AccountZKEmail.test.js
ernestognw Apr 11, 2025
e1d6297
Fix codespell
ernestognw Apr 11, 2025
c924e97
Merge branch 'master' into feature/zk-email
ernestognw Apr 12, 2025
e7f44f2
nit
ernestognw Apr 12, 2025
9fed941
Reorder README.adoc
ernestognw Apr 12, 2025
0138787
forge update
ernestognw Apr 16, 2025
9f1171e
Merge branch 'master' into feature/zk-email
ernestognw Apr 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std.git
[submodule "lib/email-tx-builder"]
path = lib/email-tx-builder
url = https://github.com/zkemail/email-tx-builder
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
lib
35 changes: 35 additions & 0 deletions contracts/mocks/account/AccountZKEmailMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import {Account} from "../../account/Account.sol";
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import {ERC7739, EIP712} from "../../utils/cryptography/ERC7739.sol";
import {ERC7821} from "../../account/extensions/ERC7821.sol";
import {SignerZKEmail} from "../../utils/cryptography/SignerZKEmail.sol";
import {IDKIMRegistry} from "@zk-email/contracts/DKIMRegistry.sol";
import {IVerifier} from "@zk-email/email-tx-builder/interfaces/IVerifier.sol";

contract AccountZKEmailMock is Account, SignerZKEmail, ERC7739, ERC7821, ERC721Holder, ERC1155Holder {
constructor(
bytes32 accountSalt_,
IDKIMRegistry registry_,
IVerifier verifier_,
uint256 templateId_
) EIP712("AccountZKEmailMock", "1") {
_setAccountSalt(accountSalt_);
_setDKIMRegistry(registry_);
_setVerifier(verifier_);
_setTemplateId(templateId_);
}

/// @inheritdoc ERC7821
function _erc7821AuthorizedExecutor(
address caller,
bytes32 mode,
bytes calldata executionData
) internal view virtual override returns (bool) {
return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
}
}
40 changes: 40 additions & 0 deletions contracts/mocks/docs/account/MyAccountZKEmail.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// contracts/MyAccountZKEmail.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import {Account} from "../../../account/Account.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import {ERC7739} from "../../../utils/cryptography/ERC7739.sol";
import {ERC7821} from "../../../account/extensions/ERC7821.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {SignerZKEmail} from "../../../utils/cryptography/SignerZKEmail.sol";
import {IDKIMRegistry} from "@zk-email/contracts/DKIMRegistry.sol";
import {IVerifier} from "@zk-email/email-tx-builder/interfaces/IVerifier.sol";

contract MyAccountZKEmail is Account, SignerZKEmail, ERC7739, ERC7821, ERC721Holder, ERC1155Holder, Initializable {
constructor() EIP712("MyAccountZKEmail", "1") {}

function initialize(
bytes32 accountSalt_,
IDKIMRegistry registry_,
IVerifier verifier_,
uint256 templateId_
) public initializer {
_setAccountSalt(accountSalt_);
_setDKIMRegistry(registry_);
_setVerifier(verifier_);
_setTemplateId(templateId_);
}

/// @dev Allows the entry point as an authorized executor.
function _erc7821AuthorizedExecutor(
address caller,
bytes32 mode,
bytes calldata executionData
) internal view virtual override returns (bool) {
return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
}
}
1 change: 1 addition & 0 deletions contracts/mocks/import.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
pragma solidity ^0.8.20;

import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
import {ECDSAOwnedDKIMRegistry} from "@zk-email/email-tx-builder/utils/ECDSAOwnedDKIMRegistry.sol";
import {ERC1271WalletMock} from "@openzeppelin/contracts/mocks/ERC1271WalletMock.sol";
16 changes: 16 additions & 0 deletions contracts/mocks/utils/cryptography/ZKEmailVerifierMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {IVerifier, EmailProof} from "@zk-email/email-tx-builder/interfaces/IVerifier.sol";

contract ZKEmailVerifierMock is IVerifier {
function commandBytes() external pure returns (uint256) {
// Same as in https://github.com/zkemail/email-tx-builder/blob/1452943807a5fdc732e1113c34792c76cf7dd031/packages/contracts/src/utils/Verifier.sol#L15
return 605;
}

function verifyEmailProof(EmailProof memory proof) external pure returns (bool) {
return proof.proof.length > 0 && bytes1(proof.proof[0]) == 0x01; // boolean true
}
}
6 changes: 6 additions & 0 deletions contracts/utils/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Miscellaneous contracts and libraries containing utility functions you can use t
* {SignerECDSA}, {SignerERC7913}, {SignerP256}, {SignerRSA}: Implementations of an {AbstractSigner} with specific signature validation algorithms.
* {ERC7913SignatureVerifierP256}, {ERC7913SignatureVerifierRSA}: Ready to use ERC-7913 signature verifiers for P256 and RSA keys
* {SignerECDSA}, {SignerP256}, {SignerRSA}: Implementations of an {AbstractSigner} with specific signature validation algorithms.
* {SignerZKEmail}: Implementation of an {AbstractSigner} that enables email-based authentication through zero-knowledge proofs.
* {ZKEmailUtils}: Library for ZKEmail signature validation utilities, enabling email-based authentication through zero-knowledge proofs.
* {EnumerableSetExtended} and {EnumerableMapExtended}: Extensions of the `EnumerableSet` and `EnumerableMap` libraries with more types, including non-value types.
* {Masks}: Library to handle `bytes32` masks.

Expand All @@ -21,6 +23,8 @@ Miscellaneous contracts and libraries containing utility functions you can use t

{{ERC7739Utils}}

{{ZKEmailUtils}}

=== Abstract Signers

{{AbstractSigner}}
Expand All @@ -35,6 +39,8 @@ Miscellaneous contracts and libraries containing utility functions you can use t

{{SignerRSA}}

{{SignerZKEmail}}

=== ERC-7913

{{ERC7913Utils}}
Expand Down
153 changes: 153 additions & 0 deletions contracts/utils/cryptography/SignerZKEmail.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import {IDKIMRegistry} from "@zk-email/contracts/DKIMRegistry.sol";
import {IVerifier} from "@zk-email/email-tx-builder/interfaces/IVerifier.sol";
import {EmailAuthMsg} from "@zk-email/email-tx-builder/interfaces/IEmailTypes.sol";
import {AbstractSigner} from "./AbstractSigner.sol";
import {ZKEmailUtils} from "./ZKEmailUtils.sol";

/**
* @dev Implementation of {AbstractSigner} using https://docs.zk.email[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 Verifier contract for zero-knowledge proof validation.
* * {templateId} - The template ID of the sign hash command, defining the expected format.
*
* Example of usage:
*
* ```solidity
* contract MyAccountZKEmail is Account, SignerZKEmail, Initializable {
* constructor(bytes32 accountSalt, IDKIMRegistry registry, IVerifier verifier, uint256 templateId) {
* // Will revert if the signer is already initialized
* _setAccountSalt(accountSalt);
* _setDKIMRegistry(registry);
* _setVerifier(verifier);
* _setTemplateId(templateId);
* }
* }
* ```
*
* IMPORTANT: Avoiding to call {_setAccountSalt}, {_setDKIMRegistry}, {_setVerifier} and {_setTemplateId}
* either during construction (if used standalone) or during initialization (if used as a clone) may
* leave the signer either front-runnable or unusable.
*/
abstract contract SignerZKEmail is AbstractSigner {
using ZKEmailUtils for EmailAuthMsg;

bytes32 private _accountSalt;
IDKIMRegistry private _registry;
IVerifier private _verifier;
uint256 private _templateId;

/// @dev Proof verification error.
error InvalidEmailProof(ZKEmailUtils.EmailProofError err);

/**
* @dev 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 https://neuromancer.sk/std/bn/bn254[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.
*/
function accountSalt() public view virtual returns (bytes32) {
return _accountSalt;
}

/// @dev An instance of the DKIM registry contract.
/// See https://docs.zk.email/architecture/dkim-verification[DKIM Verification].
// solhint-disable-next-line func-name-mixedcase
function DKIMRegistry() public view virtual returns (IDKIMRegistry) {
return _registry;
}

/// @dev An instance of the Verifier contract.
/// See https://docs.zk.email/architecture/zk-proofs#how-zk-email-uses-zero-knowledge-proofs[ZK Proofs].
function verifier() public view virtual returns (IVerifier) {
return _verifier;
}

/// @dev The command template of the sign hash command.
function templateId() public view virtual returns (uint256) {
return _templateId;
}

/// @dev Set the {accountSalt}.
function _setAccountSalt(bytes32 accountSalt_) internal virtual {
_accountSalt = accountSalt_;
}

/// @dev Set the {DKIMRegistry} contract address.
function _setDKIMRegistry(IDKIMRegistry registry_) internal virtual {
_registry = registry_;
}

/// @dev Set the {verifier} contract address.
function _setVerifier(IVerifier verifier_) internal virtual {
_verifier = verifier_;
}

/// @dev Set the command's {templateId}.
function _setTemplateId(uint256 templateId_) internal virtual {
_templateId = templateId_;
}

/**
* @dev See {AbstractSigner-_rawSignatureValidation}. Validates a raw signature by:
*
* 1. Decoding the email authentication message from the signature
* 2. Verifying the hash matches the command parameters
* 3. Checking the template ID matches
* 4. Validating the account salt
* 5. Verifying the email proof
*/
function _rawSignatureValidation(
bytes32 hash,
bytes calldata signature
) internal view virtual override returns (bool) {
// Check if the signature is long enough to contain the EmailAuthMsg
// The minimum length is 512 bytes (initial part + pointer offsets)
// - `templateId` is a uint256 (32 bytes).
// - `commandParams` is a dynamic array of bytes32 (32 bytes offset).
// - `skippedCommandPrefixSize` is a uint256 (32 bytes).
// - `proof` is a struct with the following fields (32 bytes offset):
// - `domainName` is a dynamic string (32 bytes offset).
// - `publicKeyHash` is a bytes32 (32 bytes).
// - `timestamp` is a uint256 (32 bytes).
// - `maskedCommand` is a dynamic string (32 bytes offset).
// - `emailNullifier` is a bytes32 (32 bytes).
// - `accountSalt` is a bytes32 (32 bytes).
// - `isCodeExist` is a boolean, so its length is 1 byte padded to 32 bytes.
// - `proof` is a dynamic bytes (32 bytes offset).
// There are 128 bytes for the EmailAuthMsg type and 256 bytes for the proof.
// Considering all dynamic elements are empty (i.e. `commandParams` = [], `domainName` = "", `maskedCommand` = "", `proof` = []),
// then we have 128 bytes for the EmailAuthMsg type, 256 bytes for the proof and 4 * 32 for the length of the dynamic elements.
// So the minimum length is 128 + 256 + 4 * 32 = 512 bytes.
if (signature.length < 512) return false;
EmailAuthMsg memory emailAuthMsg = abi.decode(signature, (EmailAuthMsg));
return (abi.decode(emailAuthMsg.commandParams[0], (bytes32)) == hash &&
emailAuthMsg.templateId == templateId() &&
emailAuthMsg.proof.accountSalt == accountSalt() &&
emailAuthMsg.isValidZKEmail(DKIMRegistry(), verifier()) == ZKEmailUtils.EmailProofError.NoError);
}
}
Loading
Loading