@@ -6,7 +6,7 @@ import {Bytes} from "@openzeppelin/contracts/utils/Bytes.sol";
6
6
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol " ;
7
7
import {IDKIMRegistry} from "@zk-email/contracts/DKIMRegistry.sol " ;
8
8
import {IGroth16Verifier} from "@zk-email/email-tx-builder/src/interfaces/IGroth16Verifier.sol " ;
9
- import {EmailAuthMsg, EmailProof} from "@zk-email/email-tx-builder/src/interfaces/IEmailTypes.sol " ;
9
+ import {EmailProof} from "@zk-email/email-tx-builder/src/interfaces/IEmailTypes.sol " ;
10
10
import {CommandUtils} from "@zk-email/email-tx-builder/src/libraries/CommandUtils.sol " ;
11
11
12
12
/**
@@ -43,7 +43,6 @@ library ZKEmailUtils {
43
43
NoError,
44
44
DKIMPublicKeyHash, // The DKIM public key hash verification fails
45
45
MaskedCommandLength, // The masked command length exceeds the maximum
46
- SkippedCommandPrefixSize, // The skipped command prefix size is invalid
47
46
MismatchedCommand, // The command does not match the proof command
48
47
InvalidFieldPoint, // The Groth16 field point is invalid
49
48
EmailProof // The email proof verification fails
@@ -59,33 +58,38 @@ library ZKEmailUtils {
59
58
60
59
/// @dev Variant of {isValidZKEmail} that validates the `["signHash", "{uint}"]` command template.
61
60
function isValidZKEmail (
62
- EmailAuthMsg memory emailAuthMsg ,
61
+ EmailProof memory emailProof ,
63
62
IDKIMRegistry dkimregistry ,
64
- IGroth16Verifier groth16Verifier
63
+ IGroth16Verifier groth16Verifier ,
64
+ bytes32 hash
65
65
) internal view returns (EmailProofError) {
66
66
string [] memory signHashTemplate = new string [](2 );
67
67
signHashTemplate[0 ] = "signHash " ;
68
68
signHashTemplate[1 ] = CommandUtils.UINT_MATCHER; // UINT_MATCHER is always lowercase
69
- return isValidZKEmail (emailAuthMsg, dkimregistry, groth16Verifier, signHashTemplate, Case.LOWERCASE);
69
+ bytes [] memory signHashParams = new bytes [](1 );
70
+ signHashParams[0 ] = abi.encode (hash);
71
+ return
72
+ isValidZKEmail (emailProof, dkimregistry, groth16Verifier, signHashTemplate, signHashParams, Case.LOWERCASE);
70
73
}
71
74
72
75
/**
73
- * @dev Validates a ZKEmail authentication message .
76
+ * @dev Validates a ZKEmail proof against a command template .
74
77
*
75
- * This function takes an email authentication message , a DKIM registry contract, and a verifier contract
76
- * as inputs. It performs several validation checks and returns a tuple containing a boolean success flag
77
- * and an {EmailProofError} if validation failed. Returns {EmailProofError.NoError} if all validations pass,
78
- * or false with a specific {EmailProofError} indicating which validation check failed.
78
+ * This function takes an email proof , a DKIM registry contract, and a verifier contract
79
+ * as inputs. It performs several validation checks and returns an {EmailProofError} indicating the result.
80
+ * Returns {EmailProofError.NoError} if all validations pass, or a specific {EmailProofError} indicating
81
+ * which validation check failed.
79
82
*
80
83
* NOTE: Attempts to validate the command for all possible string {Case} values.
81
84
*/
82
85
function isValidZKEmail (
83
- EmailAuthMsg memory emailAuthMsg ,
86
+ EmailProof memory emailProof ,
84
87
IDKIMRegistry dkimregistry ,
85
88
IGroth16Verifier groth16Verifier ,
86
- string [] memory template
89
+ string [] memory template ,
90
+ bytes [] memory templateParams
87
91
) internal view returns (EmailProofError) {
88
- return isValidZKEmail (emailAuthMsg , dkimregistry, groth16Verifier, template, Case.ANY);
92
+ return isValidZKEmail (emailProof , dkimregistry, groth16Verifier, template, templateParams , Case.ANY);
89
93
}
90
94
91
95
/**
@@ -94,66 +98,99 @@ library ZKEmailUtils {
94
98
* Useful for templates with Ethereum address matchers (i.e. `{ethAddr}`), which are case-sensitive (e.g., `["someCommand", "{address}"]`).
95
99
*/
96
100
function isValidZKEmail (
97
- EmailAuthMsg memory emailAuthMsg ,
101
+ EmailProof memory emailProof ,
98
102
IDKIMRegistry dkimregistry ,
99
103
IGroth16Verifier groth16Verifier ,
100
104
string [] memory template ,
105
+ bytes [] memory templateParams ,
101
106
Case stringCase
102
107
) internal view returns (EmailProofError) {
103
- if (emailAuthMsg.skippedCommandPrefix >= COMMAND_BYTES) {
104
- return EmailProofError.SkippedCommandPrefixSize;
105
- } else if (bytes (emailAuthMsg.proof.maskedCommand).length > COMMAND_BYTES) {
108
+ if (bytes (emailProof.maskedCommand).length > COMMAND_BYTES) {
106
109
return EmailProofError.MaskedCommandLength;
107
- } else if (! _commandMatch (emailAuthMsg , template, stringCase)) {
110
+ } else if (! _commandMatch (emailProof , template, templateParams , stringCase)) {
108
111
return EmailProofError.MismatchedCommand;
109
- } else if (
110
- ! dkimregistry.isDKIMPublicKeyHashValid (emailAuthMsg.proof.domainName, emailAuthMsg.proof.publicKeyHash)
111
- ) {
112
+ } else if (! dkimregistry.isDKIMPublicKeyHashValid (emailProof.domainName, emailProof.publicKeyHash)) {
112
113
return EmailProofError.DKIMPublicKeyHash;
113
114
}
115
+
114
116
(uint256 [2 ] memory pA , uint256 [2 ][2 ] memory pB , uint256 [2 ] memory pC ) = abi.decode (
115
- emailAuthMsg.proof .proof,
117
+ emailProof .proof,
116
118
(uint256 [2 ], uint256 [2 ][2 ], uint256 [2 ])
117
119
);
118
-
119
- uint256 q = Q - 1 ; // upper bound of the field elements
120
- if (
121
- pA[0 ] > q ||
122
- pA[1 ] > q ||
123
- pB[0 ][0 ] > q ||
124
- pB[0 ][1 ] > q ||
125
- pB[1 ][0 ] > q ||
126
- pB[1 ][1 ] > q ||
127
- pC[0 ] > q ||
128
- pC[1 ] > q
129
- ) return EmailProofError.InvalidFieldPoint;
120
+ if (! _isValidFieldPoint (pA, pB, pC)) {
121
+ return EmailProofError.InvalidFieldPoint;
122
+ }
130
123
131
124
return
132
- groth16Verifier.verifyProof (pA, pB, pC, toPubSignals (emailAuthMsg.proof ))
125
+ groth16Verifier.verifyProof (pA, pB, pC, toPubSignals (emailProof ))
133
126
? EmailProofError.NoError
134
127
: EmailProofError.EmailProof;
135
128
}
136
129
137
- /// @dev Compares the command in the email authentication message with the expected command.
130
+ /**
131
+ * @dev Verifies that calldata bytes (`input`) represents a valid `EmailProof` object. If encoding is valid,
132
+ * returns true and the calldata view at the object. Otherwise, returns false and an invalid calldata object.
133
+ *
134
+ * NOTE: The returned `emailProof` object should not be accessed if `success` is false. Trying to access the data may
135
+ * cause revert/panic.
136
+ */
137
+ function tryDecodeEmailProof (
138
+ bytes calldata input
139
+ ) internal pure returns (bool success , EmailProof calldata emailProof ) {
140
+ assembly ("memory-safe" ) {
141
+ emailProof := input.offset
142
+ }
143
+
144
+ // Minimum length to hold 8 objects (32 bytes each)
145
+ if (input.length < 0x100 ) return (false , emailProof);
146
+
147
+ // Get offset of non-value-type elements relative to the input buffer
148
+ uint256 domainNameOffset = uint256 (bytes32 (input[0x00 :]));
149
+ uint256 maskedCommandOffset = uint256 (bytes32 (input[0x60 :]));
150
+ uint256 proofOffset = uint256 (bytes32 (input[0xe0 :]));
151
+
152
+ // The elements length (at the offset) should be 32 bytes long. We check that this is within the
153
+ // buffer bounds. Since we know input.length is at least 32, we can subtract with no overflow risk.
154
+ if (
155
+ input.length - 0x20 < domainNameOffset ||
156
+ input.length - 0x20 < maskedCommandOffset ||
157
+ input.length - 0x20 < proofOffset
158
+ ) return (false , emailProof);
159
+
160
+ // Get the lengths. offset + 32 is bounded by input.length so it does not overflow.
161
+ uint256 domainNameLength = uint256 (bytes32 (input[domainNameOffset:]));
162
+ uint256 maskedCommandLength = uint256 (bytes32 (input[maskedCommandOffset:]));
163
+ uint256 proofLength = uint256 (bytes32 (input[proofOffset:]));
164
+
165
+ // Check that the input buffer is long enough to store the non-value-type elements
166
+ // Since we know input.length is at least xxxOffset + 32, we can subtract with no overflow risk.
167
+ if (
168
+ input.length - domainNameOffset - 0x20 < domainNameLength ||
169
+ input.length - maskedCommandOffset - 0x20 < maskedCommandLength ||
170
+ input.length - proofOffset - 0x20 < proofLength
171
+ ) return (false , emailProof);
172
+
173
+ return (true , emailProof);
174
+ }
175
+
176
+ /// @dev Compares the command in the email proof with the expected command template.
138
177
function _commandMatch (
139
- EmailAuthMsg memory emailAuthMsg ,
178
+ EmailProof memory proof ,
140
179
string [] memory template ,
180
+ bytes [] memory templateParams ,
141
181
Case stringCase
142
182
) private pure returns (bool ) {
143
- bytes [] memory commandParams = emailAuthMsg.commandParams; // Not a memory copy
144
- uint256 skippedCommandPrefix = emailAuthMsg.skippedCommandPrefix; // Not a memory copy
145
- string memory command = string (bytes (emailAuthMsg.proof.maskedCommand).slice (skippedCommandPrefix)); // Not a memory copy
146
-
147
183
if (stringCase != Case.ANY)
148
- return commandParams.computeExpectedCommand (template, uint8 (stringCase)).equal (command);
184
+ return templateParams.computeExpectedCommand (template, uint8 (stringCase)).equal (proof.maskedCommand);
185
+
149
186
return
150
- commandParams .computeExpectedCommand (template, uint8 (Case.LOWERCASE)).equal (command ) ||
151
- commandParams .computeExpectedCommand (template, uint8 (Case.UPPERCASE)).equal (command ) ||
152
- commandParams .computeExpectedCommand (template, uint8 (Case.CHECKSUM)).equal (command );
187
+ templateParams .computeExpectedCommand (template, uint8 (Case.LOWERCASE)).equal (proof.maskedCommand ) ||
188
+ templateParams .computeExpectedCommand (template, uint8 (Case.UPPERCASE)).equal (proof.maskedCommand ) ||
189
+ templateParams .computeExpectedCommand (template, uint8 (Case.CHECKSUM)).equal (proof.maskedCommand );
153
190
}
154
191
155
192
/**
156
- * @dev Builds the expected public signals array for the Groth16 verifier from the given EmailAuthMsg .
193
+ * @dev Builds the expected public signals array for the Groth16 verifier from the given EmailProof .
157
194
*
158
195
* Packs the domain, public key hash, email nullifier, timestamp, masked command, account salt, and isCodeExist fields
159
196
* into a uint256 array in the order expected by the verifier circuit.
@@ -216,4 +253,21 @@ library ZKEmailUtils {
216
253
}
217
254
return fields;
218
255
}
256
+
257
+ /// @dev Checks if the field points are valid in the range of [0, Q).
258
+ function _isValidFieldPoint (
259
+ uint256 [2 ] memory pA ,
260
+ uint256 [2 ][2 ] memory pB ,
261
+ uint256 [2 ] memory pC
262
+ ) private pure returns (bool ) {
263
+ return
264
+ pA[0 ] < Q &&
265
+ pA[1 ] < Q &&
266
+ pB[0 ][0 ] < Q &&
267
+ pB[0 ][1 ] < Q &&
268
+ pB[1 ][0 ] < Q &&
269
+ pB[1 ][1 ] < Q &&
270
+ pC[0 ] < Q &&
271
+ pC[1 ] < Q;
272
+ }
219
273
}
0 commit comments