Skip to content

Commit e46e538

Browse files
ernestognwAmxx
andauthored
Add SignerZKEmail and ZKEmailUtils (#96)
Co-authored-by: Hadrien Croubois <[email protected]>
1 parent c57182a commit e46e538

File tree

21 files changed

+1162
-9
lines changed

21 files changed

+1162
-9
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@
99
[submodule "lib/forge-std"]
1010
path = lib/forge-std
1111
url = https://github.com/foundry-rs/forge-std.git
12+
[submodule "lib/email-tx-builder"]
13+
path = lib/email-tx-builder
14+
url = https://github.com/zkemail/email-tx-builder

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
lib
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.24;
4+
5+
import {Account} from "../../account/Account.sol";
6+
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
7+
import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
8+
import {ERC7739, EIP712} from "../../utils/cryptography/ERC7739.sol";
9+
import {ERC7821} from "../../account/extensions/ERC7821.sol";
10+
import {SignerZKEmail} from "../../utils/cryptography/SignerZKEmail.sol";
11+
import {IDKIMRegistry} from "@zk-email/contracts/DKIMRegistry.sol";
12+
import {IVerifier} from "@zk-email/email-tx-builder/interfaces/IVerifier.sol";
13+
14+
contract AccountZKEmailMock is Account, SignerZKEmail, ERC7739, ERC7821, ERC721Holder, ERC1155Holder {
15+
constructor(
16+
bytes32 accountSalt_,
17+
IDKIMRegistry registry_,
18+
IVerifier verifier_,
19+
uint256 templateId_
20+
) EIP712("AccountZKEmailMock", "1") {
21+
_setAccountSalt(accountSalt_);
22+
_setDKIMRegistry(registry_);
23+
_setVerifier(verifier_);
24+
_setTemplateId(templateId_);
25+
}
26+
27+
/// @inheritdoc ERC7821
28+
function _erc7821AuthorizedExecutor(
29+
address caller,
30+
bytes32 mode,
31+
bytes calldata executionData
32+
) internal view virtual override returns (bool) {
33+
return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
34+
}
35+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// contracts/MyAccountZKEmail.sol
2+
// SPDX-License-Identifier: MIT
3+
4+
pragma solidity ^0.8.24;
5+
6+
import {Account} from "../../../account/Account.sol";
7+
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
8+
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
9+
import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
10+
import {ERC7739} from "../../../utils/cryptography/ERC7739.sol";
11+
import {ERC7821} from "../../../account/extensions/ERC7821.sol";
12+
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
13+
import {SignerZKEmail} from "../../../utils/cryptography/SignerZKEmail.sol";
14+
import {IDKIMRegistry} from "@zk-email/contracts/DKIMRegistry.sol";
15+
import {IVerifier} from "@zk-email/email-tx-builder/interfaces/IVerifier.sol";
16+
17+
contract MyAccountZKEmail is Account, SignerZKEmail, ERC7739, ERC7821, ERC721Holder, ERC1155Holder, Initializable {
18+
constructor() EIP712("MyAccountZKEmail", "1") {}
19+
20+
function initialize(
21+
bytes32 accountSalt_,
22+
IDKIMRegistry registry_,
23+
IVerifier verifier_,
24+
uint256 templateId_
25+
) public initializer {
26+
_setAccountSalt(accountSalt_);
27+
_setDKIMRegistry(registry_);
28+
_setVerifier(verifier_);
29+
_setTemplateId(templateId_);
30+
}
31+
32+
/// @dev Allows the entry point as an authorized executor.
33+
function _erc7821AuthorizedExecutor(
34+
address caller,
35+
bytes32 mode,
36+
bytes calldata executionData
37+
) internal view virtual override returns (bool) {
38+
return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
39+
}
40+
}

contracts/mocks/import.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
pragma solidity ^0.8.20;
44

55
import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
6+
import {ECDSAOwnedDKIMRegistry} from "@zk-email/email-tx-builder/utils/ECDSAOwnedDKIMRegistry.sol";
67
import {ERC1271WalletMock} from "@openzeppelin/contracts/mocks/ERC1271WalletMock.sol";
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {IVerifier, EmailProof} from "@zk-email/email-tx-builder/interfaces/IVerifier.sol";
6+
7+
contract ZKEmailVerifierMock is IVerifier {
8+
function commandBytes() external pure returns (uint256) {
9+
// Same as in https://github.com/zkemail/email-tx-builder/blob/1452943807a5fdc732e1113c34792c76cf7dd031/packages/contracts/src/utils/Verifier.sol#L15
10+
return 605;
11+
}
12+
13+
function verifyEmailProof(EmailProof memory proof) external pure returns (bool) {
14+
return proof.proof.length > 0 && bytes1(proof.proof[0]) == 0x01; // boolean true
15+
}
16+
}

contracts/utils/README.adoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Miscellaneous contracts and libraries containing utility functions you can use t
1212
* {SignerECDSA}, {SignerERC7913}, {SignerP256}, {SignerRSA}: Implementations of an {AbstractSigner} with specific signature validation algorithms.
1313
* {ERC7913SignatureVerifierP256}, {ERC7913SignatureVerifierRSA}: Ready to use ERC-7913 signature verifiers for P256 and RSA keys
1414
* {SignerECDSA}, {SignerP256}, {SignerRSA}: Implementations of an {AbstractSigner} with specific signature validation algorithms.
15+
* {SignerZKEmail}: Implementation of an {AbstractSigner} that enables email-based authentication through zero-knowledge proofs.
16+
* {ZKEmailUtils}: Library for ZKEmail signature validation utilities, enabling email-based authentication through zero-knowledge proofs.
1517
* {EnumerableSetExtended} and {EnumerableMapExtended}: Extensions of the `EnumerableSet` and `EnumerableMap` libraries with more types, including non-value types.
1618
* {Masks}: Library to handle `bytes32` masks.
1719

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

2224
{{ERC7739Utils}}
2325

26+
{{ZKEmailUtils}}
27+
2428
=== Abstract Signers
2529

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

3640
{{SignerRSA}}
3741

42+
{{SignerZKEmail}}
43+
3844
=== ERC-7913
3945

4046
{{ERC7913Utils}}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.24;
4+
5+
import {IDKIMRegistry} from "@zk-email/contracts/DKIMRegistry.sol";
6+
import {IVerifier} from "@zk-email/email-tx-builder/interfaces/IVerifier.sol";
7+
import {EmailAuthMsg} from "@zk-email/email-tx-builder/interfaces/IEmailTypes.sol";
8+
import {AbstractSigner} from "./AbstractSigner.sol";
9+
import {ZKEmailUtils} from "./ZKEmailUtils.sol";
10+
11+
/**
12+
* @dev Implementation of {AbstractSigner} using https://docs.zk.email[ZKEmail] signatures.
13+
*
14+
* ZKEmail enables secure authentication and authorization through email messages, leveraging
15+
* DKIM signatures from a {DKIMRegistry} and zero-knowledge proofs enabled by a {verifier}
16+
* contract that ensures email authenticity without revealing sensitive information. The DKIM
17+
* registry is trusted to correctly update DKIM keys, but users can override this behaviour and
18+
* set their own keys. This contract implements the core functionality for validating email-based
19+
* signatures in smart contracts.
20+
*
21+
* Developers must set the following components during contract initialization:
22+
*
23+
* * {accountSalt} - A unique identifier derived from the user's email address and account code.
24+
* * {DKIMRegistry} - An instance of the DKIM registry contract for domain verification.
25+
* * {verifier} - An instance of the Verifier contract for zero-knowledge proof validation.
26+
* * {templateId} - The template ID of the sign hash command, defining the expected format.
27+
*
28+
* Example of usage:
29+
*
30+
* ```solidity
31+
* contract MyAccountZKEmail is Account, SignerZKEmail, Initializable {
32+
* constructor(bytes32 accountSalt, IDKIMRegistry registry, IVerifier verifier, uint256 templateId) {
33+
* // Will revert if the signer is already initialized
34+
* _setAccountSalt(accountSalt);
35+
* _setDKIMRegistry(registry);
36+
* _setVerifier(verifier);
37+
* _setTemplateId(templateId);
38+
* }
39+
* }
40+
* ```
41+
*
42+
* IMPORTANT: Avoiding to call {_setAccountSalt}, {_setDKIMRegistry}, {_setVerifier} and {_setTemplateId}
43+
* either during construction (if used standalone) or during initialization (if used as a clone) may
44+
* leave the signer either front-runnable or unusable.
45+
*/
46+
abstract contract SignerZKEmail is AbstractSigner {
47+
using ZKEmailUtils for EmailAuthMsg;
48+
49+
bytes32 private _accountSalt;
50+
IDKIMRegistry private _registry;
51+
IVerifier private _verifier;
52+
uint256 private _templateId;
53+
54+
/// @dev Proof verification error.
55+
error InvalidEmailProof(ZKEmailUtils.EmailProofError err);
56+
57+
/**
58+
* @dev Unique identifier for owner of this contract defined as a hash of an email address and an account code.
59+
*
60+
* An account code is a random integer in a finite scalar field of https://neuromancer.sk/std/bn/bn254[BN254] curve.
61+
* It is a private randomness to derive a CREATE2 salt of the user's Ethereum address
62+
* from the email address, i.e., userEtherAddr := CREATE2(hash(userEmailAddr, accountCode)).
63+
*
64+
* The account salt is used for:
65+
*
66+
* * Privacy: Enables email address privacy on-chain so long as the randomly generated account code is not revealed
67+
* to an adversary.
68+
* * Security: Provides a unique identifier that cannot be easily guessed or brute-forced, as it's derived
69+
* from both the email address and a random account code.
70+
* * Deterministic Address Generation: Enables the creation of deterministic addresses based on email addresses,
71+
* allowing users to recover their accounts using only their email.
72+
*/
73+
function accountSalt() public view virtual returns (bytes32) {
74+
return _accountSalt;
75+
}
76+
77+
/// @dev An instance of the DKIM registry contract.
78+
/// See https://docs.zk.email/architecture/dkim-verification[DKIM Verification].
79+
// solhint-disable-next-line func-name-mixedcase
80+
function DKIMRegistry() public view virtual returns (IDKIMRegistry) {
81+
return _registry;
82+
}
83+
84+
/// @dev An instance of the Verifier contract.
85+
/// See https://docs.zk.email/architecture/zk-proofs#how-zk-email-uses-zero-knowledge-proofs[ZK Proofs].
86+
function verifier() public view virtual returns (IVerifier) {
87+
return _verifier;
88+
}
89+
90+
/// @dev The command template of the sign hash command.
91+
function templateId() public view virtual returns (uint256) {
92+
return _templateId;
93+
}
94+
95+
/// @dev Set the {accountSalt}.
96+
function _setAccountSalt(bytes32 accountSalt_) internal virtual {
97+
_accountSalt = accountSalt_;
98+
}
99+
100+
/// @dev Set the {DKIMRegistry} contract address.
101+
function _setDKIMRegistry(IDKIMRegistry registry_) internal virtual {
102+
_registry = registry_;
103+
}
104+
105+
/// @dev Set the {verifier} contract address.
106+
function _setVerifier(IVerifier verifier_) internal virtual {
107+
_verifier = verifier_;
108+
}
109+
110+
/// @dev Set the command's {templateId}.
111+
function _setTemplateId(uint256 templateId_) internal virtual {
112+
_templateId = templateId_;
113+
}
114+
115+
/**
116+
* @dev See {AbstractSigner-_rawSignatureValidation}. Validates a raw signature by:
117+
*
118+
* 1. Decoding the email authentication message from the signature
119+
* 2. Verifying the hash matches the command parameters
120+
* 3. Checking the template ID matches
121+
* 4. Validating the account salt
122+
* 5. Verifying the email proof
123+
*/
124+
function _rawSignatureValidation(
125+
bytes32 hash,
126+
bytes calldata signature
127+
) internal view virtual override returns (bool) {
128+
// Check if the signature is long enough to contain the EmailAuthMsg
129+
// The minimum length is 512 bytes (initial part + pointer offsets)
130+
// - `templateId` is a uint256 (32 bytes).
131+
// - `commandParams` is a dynamic array of bytes32 (32 bytes offset).
132+
// - `skippedCommandPrefixSize` is a uint256 (32 bytes).
133+
// - `proof` is a struct with the following fields (32 bytes offset):
134+
// - `domainName` is a dynamic string (32 bytes offset).
135+
// - `publicKeyHash` is a bytes32 (32 bytes).
136+
// - `timestamp` is a uint256 (32 bytes).
137+
// - `maskedCommand` is a dynamic string (32 bytes offset).
138+
// - `emailNullifier` is a bytes32 (32 bytes).
139+
// - `accountSalt` is a bytes32 (32 bytes).
140+
// - `isCodeExist` is a boolean, so its length is 1 byte padded to 32 bytes.
141+
// - `proof` is a dynamic bytes (32 bytes offset).
142+
// There are 128 bytes for the EmailAuthMsg type and 256 bytes for the proof.
143+
// Considering all dynamic elements are empty (i.e. `commandParams` = [], `domainName` = "", `maskedCommand` = "", `proof` = []),
144+
// then we have 128 bytes for the EmailAuthMsg type, 256 bytes for the proof and 4 * 32 for the length of the dynamic elements.
145+
// So the minimum length is 128 + 256 + 4 * 32 = 512 bytes.
146+
if (signature.length < 512) return false;
147+
EmailAuthMsg memory emailAuthMsg = abi.decode(signature, (EmailAuthMsg));
148+
return (abi.decode(emailAuthMsg.commandParams[0], (bytes32)) == hash &&
149+
emailAuthMsg.templateId == templateId() &&
150+
emailAuthMsg.proof.accountSalt == accountSalt() &&
151+
emailAuthMsg.isValidZKEmail(DKIMRegistry(), verifier()) == ZKEmailUtils.EmailProofError.NoError);
152+
}
153+
}

0 commit comments

Comments
 (0)