-
Notifications
You must be signed in to change notification settings - Fork 24
Add SignerZKEmail and ZKEmailUtils #96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
89cecfc
bea0548
b968068
b99b9f8
f5a2643
9577e06
813b226
ef5fe72
d9cba45
ccc43fa
24ed077
82850cb
b6a3fdf
3dd1fb9
6bc3140
27dcc0d
35b0bd6
900b668
e8fa099
4b6426c
581123d
11b197f
6d5f974
3e9d0e3
72a4cfc
9742daa
34aa505
fa8b58b
471e4de
aa7cdaa
190f0b3
202b59f
8f0a057
ed5e027
4ec7569
8a202bd
d3e0ddf
6d4f9f6
683f3fc
c3275d2
1d428d4
521fbdd
7842fa6
6bb0c00
8a5a283
c039530
cd6f66f
de69659
e70f6d1
a6dcddd
de157d8
c9672d9
9ee4ccd
f26d26b
799cf6c
f27f1a1
e1d6297
c924e97
e7f44f2
9fed941
0138787
9f1171e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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"; | ||
Amxx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 { | ||
Amxx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
(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}"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Perhaps we add an 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 |
||
|
||
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); | ||
ernestognw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.