Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
126 changes: 126 additions & 0 deletions contracts/utils/cryptography/ZKEmailSigner.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {IDKIMRegistry} from "@zk-email/contracts/DKIMRegistry.sol";
import {IVerifier, EmailProof} from "@zk-email/email-tx-builder/interfaces/IVerifier.sol";
import {EmailAuthMsg} from "@zk-email/email-tx-builder/interfaces/IEmailTypes.sol";
import {CommandUtils} from "@zk-email/email-tx-builder/libraries/CommandUtils.sol";
import {AbstractSigner} from "./AbstractSigner.sol";

abstract contract ZKEmailSigner is AbstractSigner {
bytes32 private _accountSalt;
IDKIMRegistry private _registry;
IVerifier private _verifier;
uint256 private _commandTemplate;

enum EmailProofError {
NoError,
CommandTemplate, // The template ID doesn't match
DKIMPublicKeyHash, // The DKIM public key hash verification fails
AccountSalt, // The account salt doesn't match
MaskedCommandLength, // The masked command length exceeds the maximum
SkippedCommandPrefixSize, // The skipped command prefix size is invalid
Command, // The command format is invalid
EmailProof // The email proof verification fails
}

error InvalidEmailProof(EmailProofError err);

/// @dev Unique identifier for owner of this contract defined as a hash of an email address and an account code.
function accountSalt() public view virtual returns (bytes32) {
return _accountSalt;
}

/// @dev An instance of the DKIM registry contract.
// solhint-disable-next-line func-name-mixedcase
function DKIMRegistry() public view virtual returns (IDKIMRegistry) {
return _registry;
}

/// @dev An instance of the Verifier contract.
function verifier() public view virtual returns (IVerifier) {
return _verifier;
}

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

/// @dev Initialize the contract with the account salt, DKIM registry, verifier, and command template.
function _setUp(
bytes32 accountSalt_,
IDKIMRegistry registry_,
IVerifier verifier_,
uint256 commandTemplate_
) internal virtual {
_accountSalt = accountSalt_;
_registry = registry_;
_verifier = verifier_;
_commandTemplate = commandTemplate_;
}

/** @dev Authenticate the email sender and authorize the message in the email command.
*
* NOTE: This function only verifies the authenticity of the email and command, without
* handling replay protection. The calling contract should implement its own mechanisms
* to prevent replay attacks, similar to how nonces are used with ECDSA signatures.
*/
function verifyEmail(EmailAuthMsg memory emailAuthMsg) public view virtual {
(bool verified, EmailProofError err) = _verifyEmail(emailAuthMsg);
if (!verified) revert InvalidEmailProof(err);
}

/// @inheritdoc AbstractSigner
function _rawSignatureValidation(
bytes32 hash,
bytes calldata signature
) internal view virtual override returns (bool) {
// signature is a serialized EmailAuthMsg
EmailAuthMsg memory emailAuthMsg = abi.decode(signature, (EmailAuthMsg));
(bool verified, ) = _verifyEmail(emailAuthMsg);
return verified && abi.decode(emailAuthMsg.commandParams[0], (bytes32)) == hash;
}

/// @dev Internal function to verify an email authenticated message that doesn't revert and returns a boolean and the error instead.
function _verifyEmail(EmailAuthMsg memory emailAuthMsg) internal view virtual returns (bool, EmailProofError) {
if (commandTemplate() != emailAuthMsg.templateId) return (false, EmailProofError.CommandTemplate);
string[] memory signHashTemplate = new string[](2);
signHashTemplate[0] = "signHash";
signHashTemplate[1] = "{uint}";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can move that down so that we don't pay the associated cost if the different checks (that don't use it) fails

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for gas optimisation but another idea is that these could be stored as constants. Makes this function a bit cleaner.

Originally, we were replicating some earlier logic here where the command templates were dynamic

Copy link
Member Author

@ernestognw ernestognw Apr 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense to use constants as part of the library. For the "{uint}" one, I'm using the constant from your CommandUtils library.

Perhaps we add an template input to the isValidZKEmail?

function isValidZKEmail(
    EmailAuthMsg memory emailAuthMsg,
    IDKIMRegistry dkimregistry,
    IVerifier verifier,
    string memory template
) internal view returns (bool, EmailProofError) {
    if (!dkimregistry.isDKIMPublicKeyHashValid(emailAuthMsg.proof.domainName, emailAuthMsg.proof.publicKeyHash)) {
        return (false, EmailProofError.DKIMPublicKeyHash);
    } else if (bytes(emailAuthMsg.proof.maskedCommand).length > verifier.commandBytes()) {
        return (false, EmailProofError.MaskedCommandLength);
    } else if (emailAuthMsg.skippedCommandPrefix >= verifier.commandBytes()) {
        return (false, EmailProofError.SkippedCommandPrefixSize);
    } else {
        // Construct an expectedCommand from template and the values of emailAuthMsg.commandParams.
        string memory trimmedMaskedCommand = CommandUtils.removePrefix(
            emailAuthMsg.proof.maskedCommand,
            emailAuthMsg.skippedCommandPrefix
        );
        for (uint256 stringCase = 0; stringCase < 2; stringCase++) {
            if (
                CommandUtils.computeExpectedCommand(emailAuthMsg.commandParams, template, stringCase).equal(
                    trimmedMaskedCommand
                )
            ) {
                if (verifier.verifyEmailProof(emailAuthMsg.proof)) return (true, EmailProofError.NoError);
                else return (false, EmailProofError.EmailProof);
            }
        }
        return (false, EmailProofError.Command);
    }
}

In this way the implementation can construct the template out of constants. One of them being signHash.


if (!DKIMRegistry().isDKIMPublicKeyHashValid(emailAuthMsg.proof.domainName, emailAuthMsg.proof.publicKeyHash))
return (false, EmailProofError.DKIMPublicKeyHash);
if (accountSalt() != emailAuthMsg.proof.accountSalt) return (false, EmailProofError.AccountSalt);
if (bytes(emailAuthMsg.proof.maskedCommand).length > verifier().commandBytes())
return (false, EmailProofError.MaskedCommandLength);
if (emailAuthMsg.skippedCommandPrefix >= verifier().commandBytes())
return (false, EmailProofError.SkippedCommandPrefixSize);

// Construct an expectedCommand from template and the values of emailAuthMsg.commandParams.
string memory trimmedMaskedCommand = CommandUtils.removePrefix(
emailAuthMsg.proof.maskedCommand,
emailAuthMsg.skippedCommandPrefix
);
string memory expectedCommand = "";
for (uint256 stringCase = 0; stringCase < 3; stringCase++) {
expectedCommand = CommandUtils.computeExpectedCommand(
emailAuthMsg.commandParams,
signHashTemplate,
stringCase
);
if (Strings.equal(expectedCommand, trimmedMaskedCommand)) {
break;
}
if (stringCase == 2) {
return (false, EmailProofError.Command);
}
}

if (!verifier().verifyEmailProof(emailAuthMsg.proof)) return (false, EmailProofError.EmailProof);
return (true, EmailProofError.NoError);
}
}
2 changes: 1 addition & 1 deletion lib/@openzeppelin-contracts
2 changes: 1 addition & 1 deletion lib/@openzeppelin-contracts-upgradeable
Submodule @openzeppelin-contracts-upgradeable updated 93 files
+5 −0 .changeset/blue-nails-give.md
+1 −1 .changeset/brown-turkeys-marry.md
+5 −0 .changeset/fair-pumpkins-compete.md
+5 −0 .changeset/fast-coats-try.md
+5 −0 .changeset/fuzzy-crews-poke.md
+5 −0 .changeset/good-cameras-rush.md
+5 −0 .changeset/good-zebras-ring.md
+5 −0 .changeset/gorgeous-apes-jam.md
+0 −5 .changeset/lucky-teachers-sip.md
+5 −0 .changeset/nice-cherries-reply.md
+5 −0 .changeset/ninety-rings-suffer.md
+5 −0 .changeset/quiet-shrimps-kiss.md
+5 −0 .changeset/rare-shirts-unite.md
+5 −0 .changeset/sixty-tips-wink.md
+4 −4 .github/actions/gas-compare/action.yml
+2 −2 .github/workflows/checks.yml
+1 −1 CHANGELOG.md
+3 −0 FUNDING.json
+1 −1 GUIDELINES.md
+1 −1 RELEASING.md
+5 −5 audits/2017-03.md
+2 −2 contracts/governance/GovernorUpgradeable.sol
+8 −0 contracts/governance/README.adoc
+1 −1 contracts/governance/TimelockControllerUpgradeable.sol
+1 −1 contracts/governance/extensions/GovernorCountingFractionalUpgradeable.sol
+65 −0 contracts/governance/extensions/GovernorSuperQuorumUpgradeable.sol
+1 −1 contracts/governance/extensions/GovernorTimelockControlUpgradeable.sol
+16 −13 contracts/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol
+153 −0 contracts/governance/extensions/GovernorVotesSuperQuorumFractionUpgradeable.sol
+52 −0 contracts/mocks/ArraysMockUpgradeable.sol
+8 −0 contracts/mocks/MerkleTreeMockUpgradeable.sol
+28 −0 contracts/mocks/WithInit.sol
+100 −0 contracts/mocks/governance/GovernorSuperQuorumMockUpgradeable.sol
+43 −0 contracts/mocks/governance/GovernorVotesSuperQuorumFractionMockUpgradeable.sol
+12 −2 contracts/proxy/utils/Initializable.sol
+2 −2 contracts/token/ERC20/ERC20Upgradeable.sol
+1 −1 contracts/token/ERC20/extensions/draft-ERC20TemporaryApprovalUpgradeable.sol
+1 −1 contracts/token/ERC6909/README.adoc
+2 −2 contracts/utils/README.adoc
+1 −1 fv-requirements.txt
+0 −1 hardhat.config.js
+47 −15 hardhat/common-contracts.js
+1 −1 lib/erc4626-tests
+1 −1 lib/forge-std
+1 −1 lib/halmos-cheatcodes
+1 −1 lib/openzeppelin-contracts
+10 −10 package-lock.json
+2 −2 package.json
+5 −1 scripts/checks/coverage.sh
+50 −0 scripts/fetch-common-contracts.js
+23 −21 scripts/generate/templates/Arrays.js
+7 −1 scripts/generate/templates/Arrays.opts.js
+1 −1 scripts/generate/templates/Checkpoints.js
+1 −1 scripts/generate/templates/Checkpoints.t.js
+25 −0 scripts/generate/templates/EnumerableMap.js
+27 −119 scripts/generate/templates/EnumerableSet.js
+5 −9 scripts/generate/templates/EnumerableSet.opts.js
+1 −1 scripts/generate/templates/SlotDerivation.js
+1 −1 scripts/generate/templates/TransientSlot.js
+1 −1 scripts/release/format-changelog.js
+2 −2 scripts/set-max-old-space-size.sh
+1 −1 test/access/AccessControl.behavior.js
+12 −16 test/account/utils/draft-ERC4337Utils.test.js
+2 −2 test/account/utils/draft-ERC7579Utils.t.sol
+1 −0 test/bin/EntryPoint080.abi
+ test/bin/EntryPoint080.bytecode
+1 −0 test/bin/SenderCreator080.abi
+ test/bin/SenderCreator080.bytecode
+168 −0 test/governance/extensions/GovernorSuperQuorum.test.js
+79 −0 test/governance/extensions/GovernorSuperQuorumGreaterThanQuorum.t.sol
+160 −0 test/governance/extensions/GovernorVotesSuperQuorumFraction.test.js
+7 −5 test/helpers/enums.js
+2 −20 test/helpers/erc4337.js
+12 −0 test/helpers/precompiles.js
+7 −2 test/helpers/random.js
+1 −1 test/metatx/ERC2771Context.test.js
+1 −1 test/proxy/beacon/BeaconProxy.test.js
+2 −2 test/token/ERC20/extensions/ERC20Wrapper.test.js
+1 −1 test/token/ERC6909/ERC6909.behavior.js
+2 −2 test/token/ERC6909/extensions/ERC6909ContentURI.test.js
+47 −43 test/utils/Arrays.test.js
+7 −0 test/utils/Strings.test.js
+33 −0 test/utils/cryptography/MessageHashUtils.t.sol
+34 −5 test/utils/cryptography/MessageHashUtils.test.js
+21 −7 test/utils/cryptography/SignatureChecker.test.js
+60 −22 test/utils/math/Math.t.sol
+298 −147 test/utils/math/Math.test.js
+3 −3 test/utils/structs/Checkpoints.t.sol
+49 −2 test/utils/structs/EnumerableMap.behavior.js
+1 −0 test/utils/structs/EnumerableMap.test.js
+43 −0 test/utils/structs/EnumerableSet.behavior.js
+5 −7 test/utils/structs/EnumerableSet.test.js
+108 −28 test/utils/structs/MerkleTree.test.js
1 change: 1 addition & 0 deletions lib/email-tx-builder
Submodule email-tx-builder added at bc7919
33 changes: 31 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
"zeppelin"
],
"dependencies": {
"@axelar-network/axelar-gmp-sdk-solidity": "^6.0.6"
"@axelar-network/axelar-gmp-sdk-solidity": "^6.0.6",
"@zk-email/contracts": "^6.3.2"
},
"devDependencies": {
"@openzeppelin/contracts": "file:lib/@openzeppelin-contracts",
Expand Down
2 changes: 2 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
@openzeppelin/contracts-upgradeable/=lib/@openzeppelin-contracts-upgradeable/contracts/
@openzeppelin/community-contracts/=contracts/
@axelar-network/axelar-gmp-sdk-solidity/=node_modules/@axelar-network/axelar-gmp-sdk-solidity/
@zk-email/email-tx-builder/=lib/email-tx-builder/packages/contracts/src/
@zk-email/contracts/=node_modules/@zk-email/contracts/
Loading