@@ -26,6 +26,7 @@ import {AbstractSigner} from "./AbstractSigner.sol";
26
26
* mechanism to ensure the email was actually sent and received without revealing its contents. Defined by an `IVerifier` interface.
27
27
*/
28
28
library ZKEmailUtils {
29
+ using CommandUtils for bytes [];
29
30
using Strings for string ;
30
31
31
32
/// @dev Enumeration of possible email proof validation errors.
@@ -34,62 +35,84 @@ library ZKEmailUtils {
34
35
DKIMPublicKeyHash, // The DKIM public key hash verification fails
35
36
MaskedCommandLength, // The masked command length exceeds the maximum
36
37
SkippedCommandPrefixSize, // The skipped command prefix size is invalid
37
- Command , // The command format is invalid
38
+ MismatchedCommand , // The command does not match the proof command
38
39
EmailProof // The email proof verification fails
39
40
}
40
41
42
+ /// @dev Enumeration of possible string cases used to compare the command with the expected proven command.
43
+ enum Case {
44
+ LOWERCASE, // Converts the command to hex lowercase.
45
+ UPPERCASE, // Converts the command to hex uppercase.
46
+ CHECKSUM, // Computes a checksum of the command.
47
+ ANY
48
+ }
49
+
50
+ /// @dev Variant of {isValidZKEmail} that validates the `["signHash", "{uint}"]` command template.
51
+ function isValidZKEmail (
52
+ EmailAuthMsg memory emailAuthMsg ,
53
+ IDKIMRegistry dkimregistry ,
54
+ IVerifier verifier
55
+ ) internal view returns (EmailProofError) {
56
+ string [] memory signHashTemplate = new string [](2 );
57
+ signHashTemplate[0 ] = "signHash " ;
58
+ signHashTemplate[1 ] = CommandUtils.UINT_MATCHER;
59
+
60
+ // UINT_MATCHER is always lowercase
61
+ return isValidZKEmail (emailAuthMsg, dkimregistry, verifier, signHashTemplate, Case.LOWERCASE);
62
+ }
63
+
41
64
/**
42
- * @dev Validates a ZK-Email authentication message.
43
- *
44
- * Requirements:
45
- *
46
- * - The DKIM public key hash must be valid according to the registry
47
- * - The masked command length must not exceed the verifier's limit
48
- * - The skipped command prefix size must be valid
49
- * - The command format and parameters must be valid
50
- * - The email proof must be verified by the verifier
65
+ * @dev Validates a ZKEmail authentication message.
51
66
*
52
67
* This function takes an email authentication message, a DKIM registry contract, and a verifier contract
53
68
* as inputs. It performs several validation checks and returns a tuple containing a boolean success flag
54
- * and an {EmailProofError} if validation failed. The function will return true with {EmailProofError.NoError}
55
- * if all validations pass, or false with a specific {EmailProofError} indicating which validation check failed.
69
+ * and an {EmailProofError} if validation failed. Returns {EmailProofError.NoError} if all validations pass,
70
+ * or false with a specific {EmailProofError} indicating which validation check failed.
56
71
*
57
- * NOTE: Validates `["signHash", "{uint}"]` command template by default .
72
+ * NOTE: Attempts to validate the command for all possible string {Case} values .
58
73
*/
59
74
function isValidZKEmail (
60
75
EmailAuthMsg memory emailAuthMsg ,
61
76
IDKIMRegistry dkimregistry ,
62
- IVerifier verifier
77
+ IVerifier verifier ,
78
+ string [] memory template
79
+ ) internal view returns (EmailProofError) {
80
+ return isValidZKEmail (emailAuthMsg, dkimregistry, verifier, template, Case.ANY);
81
+ }
82
+
83
+ /// @dev Variant of {isValidZKEmail} that validates a template with a specific string {Case}.
84
+ function isValidZKEmail (
85
+ EmailAuthMsg memory emailAuthMsg ,
86
+ IDKIMRegistry dkimregistry ,
87
+ IVerifier verifier ,
88
+ string [] memory template ,
89
+ Case stringCase
63
90
) internal view returns (EmailProofError) {
64
91
if (! dkimregistry.isDKIMPublicKeyHashValid (emailAuthMsg.proof.domainName, emailAuthMsg.proof.publicKeyHash)) {
65
92
return EmailProofError.DKIMPublicKeyHash;
66
93
} else if (bytes (emailAuthMsg.proof.maskedCommand).length > verifier.commandBytes ()) {
67
94
return EmailProofError.MaskedCommandLength;
68
95
} else if (emailAuthMsg.skippedCommandPrefix >= verifier.commandBytes ()) {
69
96
return EmailProofError.SkippedCommandPrefixSize;
97
+ } else if (! _commandMatch (emailAuthMsg, template, stringCase)) {
98
+ return EmailProofError.MismatchedCommand;
70
99
} else {
71
- string [] memory signHashTemplate = new string [](2 );
72
- signHashTemplate[0 ] = "signHash " ;
73
- signHashTemplate[1 ] = CommandUtils.UINT_MATCHER;
74
-
75
- // Construct an expectedCommand from template and the values of emailAuthMsg.commandParams.
76
- string memory trimmedMaskedCommand = CommandUtils.removePrefix (
77
- emailAuthMsg.proof.maskedCommand,
78
- emailAuthMsg.skippedCommandPrefix
79
- );
80
- for (uint256 stringCase = 0 ; stringCase < 3 ; stringCase++ ) {
81
- if (
82
- CommandUtils.computeExpectedCommand (emailAuthMsg.commandParams, signHashTemplate, stringCase).equal (
83
- trimmedMaskedCommand
84
- )
85
- ) {
86
- return
87
- verifier.verifyEmailProof (emailAuthMsg.proof)
88
- ? EmailProofError.NoError
89
- : EmailProofError.EmailProof;
90
- }
91
- }
92
- return EmailProofError.Command;
100
+ return verifier.verifyEmailProof (emailAuthMsg.proof) ? EmailProofError.NoError : EmailProofError.EmailProof;
93
101
}
94
102
}
103
+
104
+ function _commandMatch (
105
+ EmailAuthMsg memory emailAuthMsg ,
106
+ string [] memory template ,
107
+ Case stringCase
108
+ ) private pure returns (bool ) {
109
+ bytes [] memory commandParams = emailAuthMsg.commandParams; // Not a memory copy
110
+ string memory maskedCommand = emailAuthMsg.proof.maskedCommand; // Not a memory copy
111
+ return
112
+ stringCase == Case.ANY
113
+ ? (commandParams.computeExpectedCommand (template, uint8 (Case.LOWERCASE)).equal (maskedCommand) ||
114
+ commandParams.computeExpectedCommand (template, uint8 (Case.UPPERCASE)).equal (maskedCommand) ||
115
+ commandParams.computeExpectedCommand (template, uint8 (Case.CHECKSUM)).equal (maskedCommand))
116
+ : commandParams.computeExpectedCommand (template, uint8 (stringCase)).equal (maskedCommand);
117
+ }
95
118
}
0 commit comments