Skip to content

Commit 48ec526

Browse files
authored
Simplify ZKEmailUtils and remove Verifier.sol indirection (#204)
1 parent e42a92a commit 48ec526

File tree

13 files changed

+408
-96
lines changed

13 files changed

+408
-96
lines changed

contracts/mocks/account/AccountZKEmailMock.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,18 @@ import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Hol
99
import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
1010
import {SignerZKEmail} from "../../utils/cryptography/signers/SignerZKEmail.sol";
1111
import {IDKIMRegistry} from "@zk-email/contracts/DKIMRegistry.sol";
12-
import {IVerifier} from "@zk-email/email-tx-builder/src/interfaces/IVerifier.sol";
12+
import {IGroth16Verifier} from "@zk-email/email-tx-builder/src/interfaces/IGroth16Verifier.sol";
1313

1414
contract AccountZKEmailMock is Account, SignerZKEmail, ERC7739, ERC7821, ERC721Holder, ERC1155Holder {
1515
constructor(
1616
bytes32 accountSalt_,
1717
IDKIMRegistry registry_,
18-
IVerifier verifier_,
18+
IGroth16Verifier groth16Verifier_,
1919
uint256 templateId_
2020
) EIP712("AccountZKEmailMock", "1") {
2121
_setAccountSalt(accountSalt_);
2222
_setDKIMRegistry(registry_);
23-
_setVerifier(verifier_);
23+
_setVerifier(groth16Verifier_);
2424
_setTemplateId(templateId_);
2525
}
2626

contracts/mocks/import.sol

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/Upgradeabl
77
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
88
import {ERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
99
import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
10-
import {AccountECDSAMock, AccountERC7579Mock} from "@openzeppelin/contracts/mocks/account/AccountMock.sol";
10+
import {
11+
AccountECDSAMock,
12+
AccountERC7579Mock,
13+
AccountERC7913Mock
14+
} from "@openzeppelin/contracts/mocks/account/AccountMock.sol";
1115
import {ERC1271WalletMock} from "@openzeppelin/contracts/mocks/ERC1271WalletMock.sol";
1216
import {CallReceiverMock} from "@openzeppelin/contracts/mocks/CallReceiverMock.sol";
1317
import {ERC7913P256Verifier} from "@openzeppelin/contracts/utils/cryptography/verifiers/ERC7913P256Verifier.sol";
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {IGroth16Verifier} from "@zk-email/email-tx-builder/src/interfaces/IGroth16Verifier.sol";
6+
7+
contract ZKEmailGroth16VerifierMock is IGroth16Verifier {
8+
function verifyProof(
9+
uint[2] calldata _pA,
10+
uint[2][2] calldata _pB,
11+
uint[2] calldata _pC,
12+
uint[34] calldata /* _pubSignals */
13+
) public pure returns (bool) {
14+
return
15+
_pA[0] == 1 &&
16+
_pA[1] == 2 &&
17+
_pB[0][0] == 3 &&
18+
_pB[0][1] == 4 &&
19+
_pB[1][0] == 5 &&
20+
_pB[1][1] == 6 &&
21+
_pC[0] == 7 &&
22+
_pC[1] == 8;
23+
}
24+
}

contracts/mocks/utils/cryptography/ZKEmailVerifierMock.sol

Lines changed: 0 additions & 16 deletions
This file was deleted.

contracts/utils/cryptography/ZKEmailUtils.sol

Lines changed: 105 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ pragma solidity ^0.8.24;
55
import {Bytes} from "@openzeppelin/contracts/utils/Bytes.sol";
66
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
77
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";
99
import {EmailAuthMsg} from "@zk-email/email-tx-builder/src/interfaces/IEmailTypes.sol";
1010
import {CommandUtils} from "@zk-email/email-tx-builder/src/libraries/CommandUtils.sol";
1111

1212
/**
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.
1414
*
1515
* ZKEmail is a protocol that enables email-based authentication and authorization for smart contracts
1616
* 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
2323
* * A https://docs.zk.email/email-tx-builder/architecture/command-templates[command template] validation
2424
* mechanism to ensure the email command matches the expected format and parameters.
2525
* * 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.
2727
*/
2828
library ZKEmailUtils {
2929
using CommandUtils for bytes[];
3030
using Bytes for bytes;
3131
using Strings for string;
3232

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+
3341
/// @dev Enumeration of possible email proof validation errors.
3442
enum EmailProofError {
3543
NoError,
3644
DKIMPublicKeyHash, // The DKIM public key hash verification fails
3745
MaskedCommandLength, // The masked command length exceeds the maximum
3846
SkippedCommandPrefixSize, // The skipped command prefix size is invalid
3947
MismatchedCommand, // The command does not match the proof command
48+
InvalidFieldPoint, // The Groth16 field point is invalid
4049
EmailProof // The email proof verification fails
4150
}
4251

@@ -52,12 +61,12 @@ library ZKEmailUtils {
5261
function isValidZKEmail(
5362
EmailAuthMsg memory emailAuthMsg,
5463
IDKIMRegistry dkimregistry,
55-
IVerifier verifier
64+
IGroth16Verifier groth16Verifier
5665
) internal view returns (EmailProofError) {
5766
string[] memory signHashTemplate = new string[](2);
5867
signHashTemplate[0] = "signHash";
5968
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);
6170
}
6271

6372
/**
@@ -73,10 +82,10 @@ library ZKEmailUtils {
7382
function isValidZKEmail(
7483
EmailAuthMsg memory emailAuthMsg,
7584
IDKIMRegistry dkimregistry,
76-
IVerifier verifier,
85+
IGroth16Verifier groth16Verifier,
7786
string[] memory template
7887
) internal view returns (EmailProofError) {
79-
return isValidZKEmail(emailAuthMsg, dkimregistry, verifier, template, Case.ANY);
88+
return isValidZKEmail(emailAuthMsg, dkimregistry, groth16Verifier, template, Case.ANY);
8089
}
8190

8291
/**
@@ -87,23 +96,42 @@ library ZKEmailUtils {
8796
function isValidZKEmail(
8897
EmailAuthMsg memory emailAuthMsg,
8998
IDKIMRegistry dkimregistry,
90-
IVerifier verifier,
99+
IGroth16Verifier groth16Verifier,
91100
string[] memory template,
92101
Case stringCase
93102
) internal view returns (EmailProofError) {
94-
if (emailAuthMsg.skippedCommandPrefix >= verifier.commandBytes()) {
103+
if (emailAuthMsg.skippedCommandPrefix >= COMMAND_BYTES) {
95104
return EmailProofError.SkippedCommandPrefixSize;
96-
} else if (bytes(emailAuthMsg.proof.maskedCommand).length > verifier.commandBytes()) {
105+
} else if (bytes(emailAuthMsg.proof.maskedCommand).length > COMMAND_BYTES) {
97106
return EmailProofError.MaskedCommandLength;
98107
} else if (!_commandMatch(emailAuthMsg, template, stringCase)) {
99108
return EmailProofError.MismatchedCommand;
100109
} else if (
101110
!dkimregistry.isDKIMPublicKeyHashValid(emailAuthMsg.proof.domainName, emailAuthMsg.proof.publicKeyHash)
102111
) {
103112
return EmailProofError.DKIMPublicKeyHash;
104-
} else {
105-
return verifier.verifyEmailProof(emailAuthMsg.proof) ? EmailProofError.NoError : EmailProofError.EmailProof;
106113
}
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;
107135
}
108136

109137
/// @dev Compares the command in the email authentication message with the expected command.
@@ -123,4 +151,69 @@ library ZKEmailUtils {
123151
commandParams.computeExpectedCommand(template, uint8(Case.UPPERCASE)).equal(command) ||
124152
commandParams.computeExpectedCommand(template, uint8(Case.CHECKSUM)).equal(command);
125153
}
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+
}
126219
}

contracts/utils/cryptography/signers/SignerZKEmail.sol

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
pragma solidity ^0.8.24;
44

55
import {IDKIMRegistry} from "@zk-email/contracts/DKIMRegistry.sol";
6-
import {IVerifier} from "@zk-email/email-tx-builder/src/interfaces/IVerifier.sol";
6+
import {IGroth16Verifier} from "@zk-email/email-tx-builder/src/interfaces/IGroth16Verifier.sol";
77
import {EmailAuthMsg} from "@zk-email/email-tx-builder/src/interfaces/IEmailTypes.sol";
88
import {AbstractSigner} from "@openzeppelin/contracts/utils/cryptography/signers/AbstractSigner.sol";
99
import {ZKEmailUtils} from "../ZKEmailUtils.sol";
@@ -22,7 +22,7 @@ import {ZKEmailUtils} from "../ZKEmailUtils.sol";
2222
*
2323
* * {accountSalt} - A unique identifier derived from the user's email address and account code.
2424
* * {DKIMRegistry} - An instance of the DKIM registry contract for domain verification.
25-
* * {verifier} - An instance of the Verifier contract for zero-knowledge proof validation.
25+
* * {verifier} - An instance of the Groth16Verifier contract for zero-knowledge proof validation.
2626
* * {templateId} - The template ID of the sign hash command, defining the expected format.
2727
*
2828
* Example of usage:
@@ -32,13 +32,13 @@ import {ZKEmailUtils} from "../ZKEmailUtils.sol";
3232
* function initialize(
3333
* bytes32 accountSalt,
3434
* IDKIMRegistry registry,
35-
* IVerifier verifier,
35+
* IGroth16Verifier groth16Verifier,
3636
* uint256 templateId
3737
* ) public initializer {
3838
* // Will revert if the signer is already initialized
3939
* _setAccountSalt(accountSalt);
4040
* _setDKIMRegistry(registry);
41-
* _setVerifier(verifier);
41+
* _setGroth16Verifier(groth16Verifier);
4242
* _setTemplateId(templateId);
4343
* }
4444
* }
@@ -53,7 +53,7 @@ abstract contract SignerZKEmail is AbstractSigner {
5353

5454
bytes32 private _accountSalt;
5555
IDKIMRegistry private _registry;
56-
IVerifier private _verifier;
56+
IGroth16Verifier private _groth16Verifier;
5757
uint256 private _templateId;
5858

5959
/// @dev Proof verification error.
@@ -87,11 +87,11 @@ abstract contract SignerZKEmail is AbstractSigner {
8787
}
8888

8989
/**
90-
* @dev An instance of the Verifier contract.
90+
* @dev An instance of the Groth16Verifier contract.
9191
* See https://docs.zk.email/architecture/zk-proofs#how-zk-email-uses-zero-knowledge-proofs[ZK Proofs].
9292
*/
93-
function verifier() public view virtual returns (IVerifier) {
94-
return _verifier;
93+
function verifier() public view virtual returns (IGroth16Verifier) {
94+
return _groth16Verifier;
9595
}
9696

9797
/// @dev The command template of the sign hash command.
@@ -110,8 +110,8 @@ abstract contract SignerZKEmail is AbstractSigner {
110110
}
111111

112112
/// @dev Set the {verifier} contract address.
113-
function _setVerifier(IVerifier verifier_) internal virtual {
114-
_verifier = verifier_;
113+
function _setVerifier(IGroth16Verifier verifier_) internal virtual {
114+
_groth16Verifier = verifier_;
115115
}
116116

117117
/// @dev Set the command's {templateId}.

contracts/utils/cryptography/verifiers/ERC7913ZKEmailVerifier.sol

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
pragma solidity ^0.8.24;
44

55
import {IDKIMRegistry} from "@zk-email/contracts/DKIMRegistry.sol";
6-
import {IVerifier} from "@zk-email/email-tx-builder/src/interfaces/IVerifier.sol";
6+
import {IGroth16Verifier} from "@zk-email/email-tx-builder/src/interfaces/IGroth16Verifier.sol";
77
import {EmailAuthMsg} from "@zk-email/email-tx-builder/src/interfaces/IEmailTypes.sol";
88
import {IERC7913SignatureVerifier} from "@openzeppelin/contracts/interfaces/IERC7913.sol";
99
import {ZKEmailUtils} from "../ZKEmailUtils.sol";
@@ -25,7 +25,7 @@ import {ZKEmailUtils} from "../ZKEmailUtils.sol";
2525
* function _decodeKey(bytes calldata key) internal view override returns (
2626
* IDKIMRegistry registry,
2727
* bytes32 accountSalt,
28-
* IVerifier verifier,
28+
* IGroth16Verifier verifier,
2929
* uint256 templateId
3030
* ) {
3131
* (registry, accountSalt, verifier, templateId) = super._decodeKey(key);
@@ -35,17 +35,17 @@ import {ZKEmailUtils} from "../ZKEmailUtils.sol";
3535
* }
3636
* ```
3737
*/
38-
abstract contract ERC7913ZKEmailVerifier is IERC7913SignatureVerifier {
38+
contract ERC7913ZKEmailVerifier is IERC7913SignatureVerifier {
3939
using ZKEmailUtils for EmailAuthMsg;
4040

4141
/**
4242
* @dev Verifies a zero-knowledge proof of an email signature validated by a {DKIMRegistry} contract.
4343
*
44-
* The key format is ABI-encoded (IDKIMRegistry, bytes32, IVerifier, uint256) where:
44+
* The key format is ABI-encoded (IDKIMRegistry, bytes32, IGroth16Verifier, uint256) where:
4545
*
4646
* * IDKIMRegistry: The registry contract that validates DKIM public key hashes
4747
* * bytes32: The account salt that uniquely identifies the user's email address
48-
* * IVerifier: The verifier contract instance for ZK proof verification.
48+
* * IGroth16Verifier: The verifier contract instance for ZK proof verification.
4949
* * uint256: The template ID for the command
5050
*
5151
* See {_decodeKey} for the key encoding format.
@@ -77,7 +77,9 @@ abstract contract ERC7913ZKEmailVerifier is IERC7913SignatureVerifier {
7777
bytes32 hash,
7878
bytes calldata signature
7979
) public view virtual override returns (bytes4) {
80-
(IDKIMRegistry registry_, bytes32 accountSalt_, IVerifier verifier_, uint256 templateId_) = _decodeKey(key);
80+
(IDKIMRegistry registry_, bytes32 accountSalt_, IGroth16Verifier verifier_, uint256 templateId_) = _decodeKey(
81+
key
82+
);
8183
EmailAuthMsg memory emailAuthMsg = abi.decode(signature, (EmailAuthMsg));
8284

8385
return
@@ -102,8 +104,8 @@ abstract contract ERC7913ZKEmailVerifier is IERC7913SignatureVerifier {
102104
internal
103105
view
104106
virtual
105-
returns (IDKIMRegistry registry, bytes32 accountSalt, IVerifier verifier, uint256 templateId)
107+
returns (IDKIMRegistry registry, bytes32 accountSalt, IGroth16Verifier verifier, uint256 templateId)
106108
{
107-
return abi.decode(key, (IDKIMRegistry, bytes32, IVerifier, uint256));
109+
return abi.decode(key, (IDKIMRegistry, bytes32, IGroth16Verifier, uint256));
108110
}
109111
}

0 commit comments

Comments
 (0)