@@ -5,12 +5,12 @@ pragma solidity ^0.8.24;
5
5
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
- import {IVerifier } from "@zk-email/email-tx-builder/src/interfaces/IVerifier .sol " ;
8
+ import {IGroth16Verifier } from "@zk-email/email-tx-builder/src/interfaces/IGroth16Verifier .sol " ;
9
9
import {EmailAuthMsg} 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
/**
13
- * @dev Library for https://docs.zk.email[ZKEmail] signature validation utilities.
13
+ * @dev Library for https://docs.zk.email[ZKEmail] Groth16 proof validation utilities.
14
14
*
15
15
* ZKEmail is a protocol that enables email-based authentication and authorization for smart contracts
16
16
* using zero-knowledge proofs. It allows users to prove ownership of an email address without revealing
@@ -23,20 +23,29 @@ import {CommandUtils} from "@zk-email/email-tx-builder/src/libraries/CommandUtil
23
23
* * A https://docs.zk.email/email-tx-builder/architecture/command-templates[command template] validation
24
24
* mechanism to ensure the email command matches the expected format and parameters.
25
25
* * A https://docs.zk.email/architecture/zk-proofs#how-zk-email-uses-zero-knowledge-proofs[zero-knowledge proof] verification
26
- * mechanism to ensure the email was actually sent and received without revealing its contents. Defined by an `IVerifier ` interface.
26
+ * mechanism to ensure the email was actually sent and received without revealing its contents. Defined by an `IGroth16Verifier ` interface.
27
27
*/
28
28
library ZKEmailUtils {
29
29
using CommandUtils for bytes [];
30
30
using Bytes for bytes ;
31
31
using Strings for string ;
32
32
33
+ uint256 public constant DOMAIN_FIELDS = 9 ;
34
+ uint256 public constant DOMAIN_BYTES = 255 ;
35
+ uint256 public constant COMMAND_FIELDS = 20 ;
36
+ uint256 public constant COMMAND_BYTES = 605 ;
37
+
38
+ /// @dev The base field size for BN254 elliptic curve used in Groth16 proofs.
39
+ uint256 constant Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583 ;
40
+
33
41
/// @dev Enumeration of possible email proof validation errors.
34
42
enum EmailProofError {
35
43
NoError,
36
44
DKIMPublicKeyHash, // The DKIM public key hash verification fails
37
45
MaskedCommandLength, // The masked command length exceeds the maximum
38
46
SkippedCommandPrefixSize, // The skipped command prefix size is invalid
39
47
MismatchedCommand, // The command does not match the proof command
48
+ InvalidFieldPoint, // The Groth16 field point is invalid
40
49
EmailProof // The email proof verification fails
41
50
}
42
51
@@ -52,12 +61,12 @@ library ZKEmailUtils {
52
61
function isValidZKEmail (
53
62
EmailAuthMsg memory emailAuthMsg ,
54
63
IDKIMRegistry dkimregistry ,
55
- IVerifier verifier
64
+ IGroth16Verifier groth16Verifier
56
65
) internal view returns (EmailProofError) {
57
66
string [] memory signHashTemplate = new string [](2 );
58
67
signHashTemplate[0 ] = "signHash " ;
59
68
signHashTemplate[1 ] = CommandUtils.UINT_MATCHER; // UINT_MATCHER is always lowercase
60
- return isValidZKEmail (emailAuthMsg, dkimregistry, verifier , signHashTemplate, Case.LOWERCASE);
69
+ return isValidZKEmail (emailAuthMsg, dkimregistry, groth16Verifier , signHashTemplate, Case.LOWERCASE);
61
70
}
62
71
63
72
/**
@@ -73,10 +82,10 @@ library ZKEmailUtils {
73
82
function isValidZKEmail (
74
83
EmailAuthMsg memory emailAuthMsg ,
75
84
IDKIMRegistry dkimregistry ,
76
- IVerifier verifier ,
85
+ IGroth16Verifier groth16Verifier ,
77
86
string [] memory template
78
87
) internal view returns (EmailProofError) {
79
- return isValidZKEmail (emailAuthMsg, dkimregistry, verifier , template, Case.ANY);
88
+ return isValidZKEmail (emailAuthMsg, dkimregistry, groth16Verifier , template, Case.ANY);
80
89
}
81
90
82
91
/**
@@ -87,23 +96,42 @@ library ZKEmailUtils {
87
96
function isValidZKEmail (
88
97
EmailAuthMsg memory emailAuthMsg ,
89
98
IDKIMRegistry dkimregistry ,
90
- IVerifier verifier ,
99
+ IGroth16Verifier groth16Verifier ,
91
100
string [] memory template ,
92
101
Case stringCase
93
102
) internal view returns (EmailProofError) {
94
- if (emailAuthMsg.skippedCommandPrefix >= verifier. commandBytes () ) {
103
+ if (emailAuthMsg.skippedCommandPrefix >= COMMAND_BYTES ) {
95
104
return EmailProofError.SkippedCommandPrefixSize;
96
- } else if (bytes (emailAuthMsg.proof.maskedCommand).length > verifier. commandBytes () ) {
105
+ } else if (bytes (emailAuthMsg.proof.maskedCommand).length > COMMAND_BYTES ) {
97
106
return EmailProofError.MaskedCommandLength;
98
107
} else if (! _commandMatch (emailAuthMsg, template, stringCase)) {
99
108
return EmailProofError.MismatchedCommand;
100
109
} else if (
101
110
! dkimregistry.isDKIMPublicKeyHashValid (emailAuthMsg.proof.domainName, emailAuthMsg.proof.publicKeyHash)
102
111
) {
103
112
return EmailProofError.DKIMPublicKeyHash;
104
- } else {
105
- return verifier.verifyEmailProof (emailAuthMsg.proof) ? EmailProofError.NoError : EmailProofError.EmailProof;
106
113
}
114
+ (uint256 [2 ] memory pA , uint256 [2 ][2 ] memory pB , uint256 [2 ] memory pC ) = abi.decode (
115
+ emailAuthMsg.proof.proof,
116
+ (uint256 [2 ], uint256 [2 ][2 ], uint256 [2 ])
117
+ );
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;
130
+
131
+ return
132
+ groth16Verifier.verifyProof (pA, pB, pC, toPubSignals (emailAuthMsg))
133
+ ? EmailProofError.NoError
134
+ : EmailProofError.EmailProof;
107
135
}
108
136
109
137
/// @dev Compares the command in the email authentication message with the expected command.
@@ -123,4 +151,69 @@ library ZKEmailUtils {
123
151
commandParams.computeExpectedCommand (template, uint8 (Case.UPPERCASE)).equal (command) ||
124
152
commandParams.computeExpectedCommand (template, uint8 (Case.CHECKSUM)).equal (command);
125
153
}
154
+
155
+ /**
156
+ * @dev Builds the expected public signals array for the Groth16 verifier from the given EmailAuthMsg.
157
+ *
158
+ * Packs the domain, public key hash, email nullifier, timestamp, masked command, account salt, and isCodeExist fields
159
+ * into a uint256 array in the order expected by the verifier circuit.
160
+ */
161
+ function toPubSignals (
162
+ EmailAuthMsg memory emailAuthMsg
163
+ ) internal pure returns (uint256 [DOMAIN_FIELDS + COMMAND_FIELDS + 5 ] memory pubSignals ) {
164
+ uint256 [] memory stringFields;
165
+
166
+ stringFields = _packBytes2Fields (bytes (emailAuthMsg.proof.domainName), DOMAIN_BYTES);
167
+ for (uint256 i = 0 ; i < DOMAIN_FIELDS; i++ ) {
168
+ pubSignals[i] = stringFields[i];
169
+ }
170
+
171
+ pubSignals[DOMAIN_FIELDS] = uint256 (emailAuthMsg.proof.publicKeyHash);
172
+ pubSignals[DOMAIN_FIELDS + 1 ] = uint256 (emailAuthMsg.proof.emailNullifier);
173
+ pubSignals[DOMAIN_FIELDS + 2 ] = uint256 (emailAuthMsg.proof.timestamp);
174
+
175
+ stringFields = _packBytes2Fields (bytes (emailAuthMsg.proof.maskedCommand), COMMAND_BYTES);
176
+ for (uint256 i = 0 ; i < COMMAND_FIELDS; i++ ) {
177
+ pubSignals[DOMAIN_FIELDS + 3 + i] = stringFields[i];
178
+ }
179
+
180
+ pubSignals[DOMAIN_FIELDS + 3 + COMMAND_FIELDS] = uint256 (emailAuthMsg.proof.accountSalt);
181
+ pubSignals[DOMAIN_FIELDS + 3 + COMMAND_FIELDS + 1 ] = emailAuthMsg.proof.isCodeExist ? 1 : 0 ;
182
+
183
+ return pubSignals;
184
+ }
185
+
186
+ /**
187
+ * @dev Packs a bytes array into an array of uint256 fields, each field representing up to 31 bytes.
188
+ * If the input is shorter than the padded size, the remaining bytes are zero-padded.
189
+ */
190
+ function _packBytes2Fields (bytes memory _bytes , uint256 _paddedSize ) private pure returns (uint256 [] memory ) {
191
+ uint256 remain = _paddedSize % 31 ;
192
+ uint256 numFields = (_paddedSize - remain) / 31 ;
193
+ if (remain > 0 ) {
194
+ numFields += 1 ;
195
+ }
196
+ uint256 [] memory fields = new uint [](numFields);
197
+ uint256 idx = 0 ;
198
+ uint256 byteVal = 0 ;
199
+ for (uint256 i = 0 ; i < numFields; i++ ) {
200
+ for (uint256 j = 0 ; j < 31 ; j++ ) {
201
+ idx = i * 31 + j;
202
+ if (idx >= _paddedSize) {
203
+ break ;
204
+ }
205
+ if (idx >= _bytes.length ) {
206
+ byteVal = 0 ;
207
+ } else {
208
+ byteVal = uint256 (uint8 (_bytes[idx]));
209
+ }
210
+ if (j == 0 ) {
211
+ fields[i] = byteVal;
212
+ } else {
213
+ fields[i] += (byteVal << (8 * j));
214
+ }
215
+ }
216
+ }
217
+ return fields;
218
+ }
126
219
}
0 commit comments