From 8a62db1e676aedbb20a403be95fffebef12b97e4 Mon Sep 17 00:00:00 2001 From: SoraSuegami Date: Sun, 15 Sep 2024 01:19:18 +0900 Subject: [PATCH] Update READMEs --- README.md | 52 +- packages/circuits/README.md | 12 +- packages/circuits/input.json | 2136 ++++++++++++++++++++++++++++++++++ packages/contracts/README.md | 224 +++- 4 files changed, 2341 insertions(+), 83 deletions(-) create mode 100644 packages/circuits/input.json diff --git a/README.md b/README.md index 83c14a93..ec39f0c9 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ One issue with existing applications on Ethereum is that all users who execute t Our ether email-auth SDK solves this issue: it allows users to execute any transaction on-chain simply by sending an email. Using the SDK, a developer can build a smart contract with the following features without new ZKP circuits. -1. (Authorization) The contract can authorize any message in the Subject of the email that the user sends with a DKIM signature generated by an email provider, e.g., Gmail. +1. (Authorization) The contract can authorize any message in the email body that the user sends with a DKIM signature generated by an email provider, e.g., Gmail. 2. (Authentication) The contract can authenticate that the given Ethereum address corresponds to the email address in the From field of the email. 3. (Privacy) No on-chain information reveals the user's email address itself. In other words, any adversary who learns only public data cannot estimate the corresponding email address from the Ethereum address. @@ -19,10 +19,10 @@ Using the ether email-auth SDK, we construct a library and tools for any smart a In addition to a user and a smart contract employing our SDK, there is a permissionless server called Relayer. The Relayer connects the off-chain world, where the users are, with the on-chain world, where the contracts reside, without compromising security. Specifically, the user, the Relayer, and the contract collaborate as follows: -1. (Off-chain) The user sends the Relayer an email containing a message to the contract in the Subject. -2. (Off-chain) The Relayer generates **an email-auth message for the given email, consisting of data about the Subject, an Ethereum address corresponding to the user's email address, a ZK proof of the email, and so on**. +1. (Off-chain) The user sends the Relayer an email containing a message called command. +2. (Off-chain) The Relayer generates **an email-auth message for the given email, consisting of data about the command, an Ethereum address corresponding to the user's email address, a ZK proof of the email, and so on**. 3. (Off-chain -> On-chain) The Relayer broadcasts an Ethereum transaction to call the contract with the email-auth message. -4. (On-chain) After verifying the given email-auth message, the contract executes application-specific logic depending on the message in the Subject and the user's Ethereum address. +4. (On-chain) After verifying the given email-auth message, the contract executes application-specific logic according to the command in that message for an Ethereum account derived from the user's email address. 5. (On-chain -> Off-chain) The Relayer sends the user an email to report the execution result of the contract. ![Architecture Flow](./docs/images/architecture-flow.png) @@ -35,35 +35,35 @@ That CREATE2 salt is called account salt, which is published on-chain. **As long as the account code is hidden, no adversary can learn the user's email address from on-chain data.** ### Invitation Code -An invitation code is a hex string of the account code along with a prefix, contained in any field of the email header to be inherited by its reply, e.g., Subject. +An invitation code is a hex string of the account code along with a prefix, contained in the email body and inherited by the user's reply. By confirming that a user sends an email with the invitation code, the contract can ensure that the account code is available to that user. It ensures the user’s liveness even when a malicious relayer or another user generates the user's account code because it prevents them from withholding the account code. **It suggests that the contract must check if the given email sent by the user contains the invitation code before confirming that user’s account for the first time.** Notably, the email-auth message, which represents data in the user's email along with its ZK proof, has a boolean field `isCodeExist` such that the value is true if the invitation code is in the email. -However, the Subject message in the email-auth message masks characters for the invitation code. +However, a command in the email-auth message masks characters for the invitation code. **Consequently, no information beyond the existence of the invitation code is disclosed.** -### Subject Template -A subject template defines the expected format of the message in the Subject for each application. -**It allows developers to constrain that message to be in the application-specific format without new ZKP circuits.** +### Command Template +Each application defines expected formats for a command in the email body as command templates. +**To define the command templates, a developer does not need to write any ZKP circuits.** -Specifically, the subject template is an array of strings, each of which has some fixed strings without space and the following variable parts: +Specifically, the command template is an array of strings, each of which has some fixed strings without space and the following variable parts: - `"{string}"`: a string. Its Solidity type is `string`. - `"{uint}"`: a decimal string of the unsigned integer. Its Solidity type is `uint256`. - `"{int}"`: a decimal string of the signed integer. Its Solidity type is `int256`. - `"{decimals}"`: a decimal string of the decimals. Its Solidity type is `uint256`. Its decimal size is fixed to 18. E.g., “2.7” ⇒ `abi.encode(2.7 * (10**18))`. -- `"{ethAddr}"`: a hex string of the Ethereum address. Its Solidity type is `address`. Its value MUST satisfy the checksum of the Ethereum address. +- `"{ethAddr}"`: a hex string of the Ethereum address. Its Solidity type is `address`. Its value MUST be either 0x+lowercase, 0x+uppercase, or 0x+checksumed addresses. ## Package Components There are four significant packages in this repo: ### `circuits` Package -It has a main circom circuit for verifying the email along with its DKIM signature, revealing a Subject message that masks an email address and an invitation code, and deriving an account salt from the email address in the From field and the given account code, which should match with the invitation code if it exists in the email. -The circuit is agnostic to application contexts such as subject templates. +It has a main circom circuit for verifying the email along with its DKIM signature, revealing a command message that masks an email address and an invitation code, and deriving an account salt from the email address in the From field and the given account code, which should match with the invitation code if it exists in the email. +The circuit is agnostic to application specifications such as command templates. **Therefore, a developer does not need to make new circuits.** -In a nutshell, our circuit 1) verifies the given RSA signature for the given email header and the RSA public key, 2) exposes the string in the Subject field except for the invitation code and the email address that appears in the Subject, and 3) computes the account salt derived from the email address in the From field and the given account code, which must be the same as the invitation code if it exists. -In this way, it allows our on-chain verifier to authenticate the email sender and authorize the message in the Subject while protecting privacy. +In a nutshell, our circuit 1) verifies the given RSA signature for the given email header and the RSA public key, 2) exposes a substring between predefined prefix and suffix as a command from the email body while masking the invitation code and email address, and 3) computes the account salt derived from the email address in the From field and the given account code, which must match the invitation code, if present. +This allows our on-chain verifier to authenticate the email sender and authorize the command in the email body while protecting privacy. For detailed setup instructions, see [here](./packages/circuits/README.md). @@ -81,11 +81,11 @@ If you use the common trusted custodians for all users, you can deploy a new DKI If each user should be able to modify the registered public keys, a new DKIM registry contract needs to be deployed for each user. The email-auth contract in `EmailAuth.sol` is a contract for each email user. -Its contract Ethereum address is derived from 1) its initial owner address, 2) an address of a controller contract that can define the supported subject templates and 3) the account salt, i.e., the hash of the user's email address and one account code held by the user, through CREATE2. +Its contract Ethereum address is derived from 1) its initial owner address, 2) an address of a controller contract that can define the supported command templates and 3) the account salt, i.e., the hash of the user's email address and one account code held by the user, through CREATE2. It provides a function `authEmail` to verify the email-auth message by calling the verifier and the DKIM registry contracts. Your application contract can employ those contracts in the following manner: -1. For a new email user, the application contract deploys (a proxy of) the email-auth contract. Subsequently, the application contract sets the addresses of the verifier and the DKIM registry contracts and some subject templates for your application to the email-auth contract. Here, the email-auth contract registers the application contract as a controller contract that has permissions to modify the subject templates. +1. For a new email user, the application contract deploys (a proxy of) the email-auth contract. Subsequently, the application contract sets the addresses of the verifier and the DKIM registry contracts and some command templates for your application to the email-auth contract. Here, the email-auth contract registers the application contract as a controller contract that has permissions to modify the command templates. 2. Given a new email-auth message from the email user, the application contract calls the `authEmail` function in the email-auth contract for that user. If it returns no error, the application contract can execute any processes based on the message in the email-auth message. For detailed setup instructions, see [here](./packages/contracts/README.md). @@ -108,7 +108,7 @@ Our SDK only performs the verification of the email-auth message. Here, we present a list of security notes that you should check. - As described in the Subsection of "Invitation Code", for each email user, your application contract must ensure that the value of `isCodeExist` in the first email-auth message is true. -- The application contract can configure multiple subject templates for the same email-auth contract. However, the Relayer can choose any of the configured templates, as long as the message in the Subject matches with the chosen template. For example, if there are two templates "Send {decimals} {string}" and "Send {string}", the message "Send 1.23 ETH" matches with both templates. We recommend defining the subject templates without such ambiguities. +- The application contract can configure multiple command templates for the same email-auth contract. However, the Relayer can choose any of the configured templates, as long as the message in the command matches with the chosen template. For example, if there are two templates "Send {decimals} {string}" and "Send {string}", the message "Send 1.23 ETH" matches with both templates. We recommend defining the command templates without such ambiguities. - To protect the privacy of the users' email addresses, you should carefully design not only the contracts but also the Relayer server, which stores the users' account codes. For example, an adversary can breach that privacy by exploiting an API provided by the Relayer that returns the Ethereum address for the given email address and its stored account code. Additionally, if any Relayer's API returns an error when no account code is stored for the given email address, the adversary can learn which email addresses are registered. ## Application: Email-based Account Recovery @@ -147,16 +147,16 @@ Our SDK cannot ensure security and privacy in the entire process without your ca Specifically, you can integrate the email-based account recovery into your smart accounts in the following steps. 1. (Contracts 1/6) First, you build a new controller contract with imports of the `EmailAccountRecovery` abstract contract in `EmailAccountRecovery.sol`. Your Solidity compiler will require you to implement the following seven functions: `isActivated`, -`acceptanceSubjectTemplates`, `recoverySubjectTemplates`, `extractRecoveredAccountFromAcceptanceSubject`, `extractRecoveredAccountFromRecoverySubject`, `acceptGuardian`, `processRecovery`, and `completeRecovery`. -2. (Contracts 2/6) You define expected subject templates for two types of emails sent from guardians, one for accepting the role of the guardian, and the other for confirming the account recovery. You can implement the former and latter subject templates in the `acceptanceSubjectTemplates` and `recoverySubjectTemplates` functions, respectively. This is an example of the subject templates: - - Template in `acceptanceSubjectTemplates`: `"Accept guardian request for {ethAddr}"`, where the value of `"{ethAddr}"` represents the account address. - - Template in `recoverySubjectTemplates`: `"Set the new signer of {ethAddr} to {ethAddr}"`, where the values of the first and second `"{ethAddr}"`, respectively, represent the account address and the new owner address. -3. (Contracts 3/6) You also define how to extract an account address to be recovered from the subject parameters for the templates in `acceptanceSubjectTemplates` and `recoverySubjectTemplates`, respectively. +`acceptanceCommandTemplates`, `recoveryCommandTemplates`, `extractRecoveredAccountFromAcceptanceCommand`, `extractRecoveredAccountFromRecoveryCommand`, `acceptGuardian`, `processRecovery`, and `completeRecovery`. +2. (Contracts 2/6) You define expected command templates for two types of emails sent from guardians, one for accepting the role of the guardian, and the other for confirming the account recovery. You can implement the former and latter command templates in the `acceptanceCommandTemplates` and `recoveryCommandTemplates` functions, respectively. This is an example of the command templates: + - Template in `acceptanceCommandTemplates`: `"Accept guardian request for {ethAddr}"`, where the value of `"{ethAddr}"` represents the account address. + - Template in `recoveryCommandTemplates`: `"Set the new signer of {ethAddr} to {ethAddr}"`, where the values of the first and second `"{ethAddr}"`, respectively, represent the account address and the new owner address. +3. (Contracts 3/6) You also define how to extract an account address to be recovered from the command parameters for the templates in `acceptanceCommandTemplates` and `recoveryCommandTemplates`, respectively. 3. (Contracts 4/6) Before implementing the remaining functions in `EmailAccountRecovery`, you implement a requesting function into the controller that allows the account owner to request a guardian, which is expected to be called by the account owner directly. Our SDK **does not** specify any interface or implementation of this function. For example, the function can simply take as input a new guardian's email-auth contract address computed by CREATE2, and store it as a guardian candidate. If you want to set a timelock for each guardian, the requesting function can additionally take the timelock length as input. -4. (Contracts 5/6) You implement the `acceptGuardian` and `processRecovery` functions into the controller. These two functions are, respectively, called by the controller itself after verifying the email-auth messages for accepting a guardian and processing a recovery. Each of them takes as input the guardian's email-auth contract address, an index of the chosen subject template, the values for the variable parts of the message in the Subject, and the email nullifier. You can assume these arguments are already verified. For example, the `acceptGuardian` function stores the given guardian's address as the confirmed guardian, and the `processRecovery` function stores the given new owner's address or sets a timelock. +4. (Contracts 5/6) You implement the `acceptGuardian` and `processRecovery` functions into the controller. These two functions are, respectively, called by the controller itself after verifying the email-auth messages for accepting a guardian and processing a recovery. Each of them takes as input the guardian's email-auth contract address, an index of the chosen command template, the values for the variable parts of the message in the command, and the email nullifier. You can assume these arguments are already verified. For example, the `acceptGuardian` function stores the given guardian's address as the confirmed guardian, and the `processRecovery` function stores the given new owner's address or sets a timelock. 5. (Contracts 6/6) You finally implement the `completeRecovery` function into the controller. It should rotate the owner's address in the smart account if some required conditions hold. This function can be called by anyone, but is assumed to be called by the Relayer and can take as input arbitrary bytes. -6. (Frontend 1/3) Next, you build a frontend for the account recovery. You prepare a page where the account owner configures guardians. It requests the account owner to input the account address (`account_eth_addr`) and the guardian's email address (`guardian_email_addr`), generates a random account code (`account_code`), constructs an expected subject (`subject`) for the subject template whose index is `template_idx` in the output of the `acceptanceSubjectTemplates()` function. It then requests the account owner to call the requesting function in the controller contract. After that, it calls the Relayer's `acceptanceRequest` API with `guardian_email_addr`, `account_code`, `template_idx`, and the address of the controller contract `controller_eth_addr`. -7. (Frontend 2/3) You also prepare a page where the account owner requests guardians to recover the account. It requests the account owner to input the account address (`account_eth_addr`) and the guardian's email address (`guardian_email_addr`), and constructs an expected subject (`subject`) for the subject template whose index is `template_idx` in the output of the `recoverySubjectTemplates()` function. It calls the Relayer's `recoveryRequest` API with those data and `controller_eth_addr`. +6. (Frontend 1/3) Next, you build a frontend for the account recovery. You prepare a page where the account owner configures guardians. It requests the account owner to input the account address (`account_eth_addr`) and the guardian's email address (`guardian_email_addr`), generates a random account code (`account_code`), constructs an expected command (`command`) for the command template whose index is `template_idx` in the output of the `acceptanceCommandTemplates()` function. It then requests the account owner to call the requesting function in the controller contract. After that, it calls the Relayer's `acceptanceRequest` API with `guardian_email_addr`, `account_code`, `template_idx`, and the address of the controller contract `controller_eth_addr`. +7. (Frontend 2/3) You also prepare a page where the account owner requests guardians to recover the account. It requests the account owner to input the account address (`account_eth_addr`) and the guardian's email address (`guardian_email_addr`), and constructs an expected command (`command`) for the command template whose index is `template_idx` in the output of the `recoveryCommandTemplates()` function. It calls the Relayer's `recoveryRequest` API with those data and `controller_eth_addr`. 8. (Frontend 3/3) It simulates off-chain if the `completeRecovery` function in the smart account will return no error at regular time intervals. When it stands, the frontend calls the Relayer's `completeRequest` API with sending `account_eth_addr`, `controller_eth_addr`, and a calldata for the `completeRecovery` function `complete_calldata`. We show some important points to implement the email-based account recovery for your smart accounts securely. diff --git a/packages/circuits/README.md b/packages/circuits/README.md index 74f58d13..f76f3ac0 100644 --- a/packages/circuits/README.md +++ b/packages/circuits/README.md @@ -60,6 +60,14 @@ The `email_auth.circom` makes constraints and computes the public output as foll 12. Let `embedded_code` be an integer parsing `code_str` as a hex string. 13. If `is_code_exist` is 1, assert that `embedded_code` is equal to `account_code`. 14. Let `account_salt` be `PoseidonHash(from_addr|0..0, account_code, 0)`. -15. Let `masked_subject` be a string that removes `code_str`, the prefix of the invitation code, and one email address from `subject`, if they appear in `subject`. +15. Let `masked_subject` be a string that removes the invitation code with the prefix `code_str` and one email address from `subject`, if they appear in `subject`. -Note that the email address in the subject is assumbed not to overlap with the invitation code. \ No newline at end of file +Note that the email address in the subject is assumbed not to overlap with the invitation code. + + +#### `email_auth_with_body_parsing_with_qp_encoding.circom` +A circuit to verify that a message in the email body, called command, is authorized by a user of an account salt, derived from an email address in the From field and a random field value called account code. +This is basically the same as the `email_auth.circom` described above except for the following features: +- Instead of `subject_idx`, it additionally takes as a private input a padded email body `padded_cleaned_body` and an index of the command in the email body `command_idx`. +- It extracts a substring `command` between a prefix `(
]*>)"` and a suffix `
` from `padded_cleaned_body`. +- It outputs `masked_command` instead of `masked_subject`, which removes the invitation code with the prefix and one email address from `command`. \ No newline at end of file diff --git a/packages/circuits/input.json b/packages/circuits/input.json new file mode 100644 index 00000000..7aec4db2 --- /dev/null +++ b/packages/circuits/input.json @@ -0,0 +1,2136 @@ +{ + "padded_header": [ + 115, + 117, + 98, + 106, + 101, + 99, + 116, + 58, + 69, + 109, + 97, + 105, + 108, + 32, + 65, + 99, + 99, + 111, + 117, + 110, + 116, + 32, + 82, + 101, + 99, + 111, + 118, + 101, + 114, + 121, + 32, + 84, + 101, + 115, + 116, + 49, + 13, + 10, + 116, + 111, + 58, + 115, + 117, + 101, + 103, + 97, + 109, + 105, + 115, + 111, + 114, + 97, + 64, + 103, + 109, + 97, + 105, + 108, + 46, + 99, + 111, + 109, + 13, + 10, + 102, + 114, + 111, + 109, + 58, + 101, + 109, + 97, + 105, + 119, + 97, + 108, + 108, + 101, + 116, + 46, + 97, + 108, + 105, + 99, + 101, + 64, + 103, + 109, + 97, + 105, + 108, + 46, + 99, + 111, + 109, + 13, + 10, + 109, + 105, + 109, + 101, + 45, + 118, + 101, + 114, + 115, + 105, + 111, + 110, + 58, + 49, + 46, + 48, + 13, + 10, + 100, + 97, + 116, + 101, + 58, + 70, + 114, + 105, + 44, + 32, + 48, + 54, + 32, + 83, + 101, + 112, + 32, + 50, + 48, + 50, + 52, + 32, + 48, + 53, + 58, + 53, + 55, + 58, + 52, + 52, + 32, + 45, + 48, + 55, + 48, + 48, + 32, + 40, + 80, + 68, + 84, + 41, + 13, + 10, + 109, + 101, + 115, + 115, + 97, + 103, + 101, + 45, + 105, + 100, + 58, + 60, + 54, + 54, + 100, + 97, + 102, + 99, + 52, + 56, + 46, + 49, + 55, + 48, + 97, + 48, + 50, + 50, + 48, + 46, + 51, + 51, + 99, + 51, + 100, + 48, + 46, + 102, + 99, + 98, + 48, + 64, + 109, + 120, + 46, + 103, + 111, + 111, + 103, + 108, + 101, + 46, + 99, + 111, + 109, + 62, + 13, + 10, + 100, + 107, + 105, + 109, + 45, + 115, + 105, + 103, + 110, + 97, + 116, + 117, + 114, + 101, + 58, + 118, + 61, + 49, + 59, + 32, + 97, + 61, + 114, + 115, + 97, + 45, + 115, + 104, + 97, + 50, + 53, + 54, + 59, + 32, + 99, + 61, + 114, + 101, + 108, + 97, + 120, + 101, + 100, + 47, + 114, + 101, + 108, + 97, + 120, + 101, + 100, + 59, + 32, + 100, + 61, + 103, + 109, + 97, + 105, + 108, + 46, + 99, + 111, + 109, + 59, + 32, + 115, + 61, + 50, + 48, + 50, + 51, + 48, + 54, + 48, + 49, + 59, + 32, + 116, + 61, + 49, + 55, + 50, + 53, + 54, + 50, + 55, + 52, + 54, + 53, + 59, + 32, + 120, + 61, + 49, + 55, + 50, + 54, + 50, + 51, + 50, + 50, + 54, + 53, + 59, + 32, + 100, + 97, + 114, + 97, + 61, + 103, + 111, + 111, + 103, + 108, + 101, + 46, + 99, + 111, + 109, + 59, + 32, + 104, + 61, + 115, + 117, + 98, + 106, + 101, + 99, + 116, + 58, + 116, + 111, + 58, + 102, + 114, + 111, + 109, + 58, + 109, + 105, + 109, + 101, + 45, + 118, + 101, + 114, + 115, + 105, + 111, + 110, + 58, + 100, + 97, + 116, + 101, + 58, + 109, + 101, + 115, + 115, + 97, + 103, + 101, + 45, + 105, + 100, + 58, + 102, + 114, + 111, + 109, + 58, + 116, + 111, + 58, + 99, + 99, + 58, + 115, + 117, + 98, + 106, + 101, + 99, + 116, + 32, + 58, + 100, + 97, + 116, + 101, + 58, + 109, + 101, + 115, + 115, + 97, + 103, + 101, + 45, + 105, + 100, + 58, + 114, + 101, + 112, + 108, + 121, + 45, + 116, + 111, + 59, + 32, + 98, + 104, + 61, + 50, + 110, + 99, + 84, + 75, + 79, + 68, + 78, + 43, + 108, + 98, + 48, + 68, + 57, + 43, + 90, + 97, + 67, + 85, + 104, + 57, + 118, + 84, + 106, + 111, + 109, + 72, + 78, + 80, + 110, + 73, + 54, + 57, + 109, + 107, + 55, + 119, + 101, + 87, + 115, + 54, + 65, + 56, + 61, + 59, + 32, + 98, + 61, + 128, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 15, + 32, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "padded_body": [ + 45, + 45, + 61, + 61, + 61, + 61, + 61, + 61, + 61, + 61, + 61, + 61, + 61, + 61, + 61, + 61, + 61, + 54, + 48, + 53, + 48, + 48, + 49, + 54, + 56, + 53, + 53, + 54, + 56, + 48, + 56, + 51, + 55, + 50, + 54, + 57, + 61, + 61, + 13, + 10, + 67, + 111, + 110, + 116, + 101, + 110, + 116, + 45, + 84, + 121, + 112, + 101, + 58, + 32, + 116, + 101, + 120, + 116, + 47, + 104, + 116, + 109, + 108, + 59, + 32, + 99, + 104, + 97, + 114, + 115, + 101, + 116, + 61, + 34, + 117, + 116, + 102, + 45, + 56, + 34, + 13, + 10, + 77, + 73, + 77, + 69, + 45, + 86, + 101, + 114, + 115, + 105, + 111, + 110, + 58, + 32, + 49, + 46, + 48, + 13, + 10, + 67, + 111, + 110, + 116, + 101, + 110, + 116, + 45, + 84, + 114, + 97, + 110, + 115, + 102, + 101, + 114, + 45, + 69, + 110, + 99, + 111, + 100, + 105, + 110, + 103, + 58, + 32, + 113, + 117, + 111, + 116, + 101, + 100, + 45, + 112, + 114, + 105, + 110, + 116, + 97, + 98, + 108, + 101, + 13, + 10, + 67, + 111, + 110, + 116, + 101, + 110, + 116, + 45, + 84, + 121, + 112, + 101, + 58, + 32, + 116, + 101, + 120, + 116, + 47, + 104, + 116, + 109, + 108, + 59, + 32, + 99, + 104, + 97, + 114, + 115, + 101, + 116, + 61, + 117, + 116, + 102, + 45, + 56, + 13, + 10, + 13, + 10, + 13, + 10, + 32, + 60, + 104, + 116, + 109, + 108, + 62, + 13, + 10, + 32, + 60, + 98, + 111, + 100, + 121, + 62, + 13, + 10, + 32, + 60, + 104, + 49, + 62, + 72, + 101, + 108, + 108, + 111, + 33, + 60, + 47, + 104, + 49, + 62, + 13, + 10, + 32, + 60, + 112, + 62, + 84, + 104, + 105, + 115, + 32, + 105, + 115, + 32, + 97, + 32, + 116, + 101, + 115, + 116, + 32, + 101, + 109, + 97, + 105, + 108, + 32, + 119, + 105, + 116, + 104, + 32, + 97, + 32, + 98, + 97, + 115, + 105, + 99, + 32, + 72, + 84, + 77, + 76, + 32, + 98, + 111, + 100, + 121, + 46, + 60, + 47, + 112, + 62, + 13, + 10, + 32, + 60, + 100, + 105, + 118, + 32, + 105, + 100, + 61, + 51, + 68, + 34, + 122, + 107, + 101, + 109, + 97, + 105, + 108, + 34, + 62, + 65, + 99, + 99, + 101, + 112, + 116, + 32, + 103, + 117, + 97, + 114, + 100, + 105, + 97, + 110, + 32, + 114, + 101, + 113, + 117, + 101, + 115, + 116, + 32, + 102, + 111, + 114, + 32, + 48, + 120, + 48, + 67, + 48, + 54, + 54, + 56, + 56, + 101, + 54, + 49, + 67, + 48, + 54, + 52, + 54, + 54, + 69, + 61, + 13, + 10, + 50, + 97, + 53, + 67, + 54, + 102, + 69, + 52, + 69, + 49, + 53, + 99, + 51, + 53, + 57, + 50, + 54, + 48, + 97, + 51, + 51, + 102, + 51, + 32, + 67, + 111, + 100, + 101, + 32, + 49, + 49, + 54, + 50, + 101, + 98, + 102, + 102, + 52, + 48, + 57, + 49, + 56, + 97, + 102, + 101, + 53, + 51, + 48, + 53, + 101, + 54, + 56, + 51, + 57, + 54, + 102, + 48, + 50, + 56, + 51, + 101, + 98, + 54, + 55, + 53, + 57, + 48, + 49, + 100, + 48, + 51, + 56, + 55, + 102, + 57, + 61, + 13, + 10, + 55, + 100, + 50, + 49, + 57, + 50, + 56, + 100, + 52, + 50, + 51, + 97, + 97, + 97, + 48, + 98, + 53, + 52, + 60, + 47, + 100, + 105, + 118, + 62, + 61, + 50, + 48, + 13, + 10, + 32, + 60, + 47, + 98, + 111, + 100, + 121, + 62, + 13, + 10, + 32, + 60, + 47, + 104, + 116, + 109, + 108, + 62, + 13, + 10, + 32, + 61, + 50, + 48, + 13, + 10, + 45, + 45, + 61, + 61, + 61, + 61, + 61, + 61, + 61, + 61, + 61, + 61, + 61, + 61, + 61, + 61, + 61, + 54, + 48, + 53, + 48, + 48, + 49, + 54, + 56, + 53, + 53, + 54, + 56, + 48, + 56, + 51, + 55, + 50, + 54, + 57, + 61, + 61, + 45, + 45, + 13, + 10, + 128, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 16, + 112, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "body_hash_idx": 436, + "public_key": [ + "2107195391459410975264579855291297887", + "2562632063603354817278035230349645235", + "1868388447387859563289339873373526818", + "2159353473203648408714805618210333973", + "351789365378952303483249084740952389", + "659717315519250910761248850885776286", + "1321773785542335225811636767147612036", + "258646249156909342262859240016844424", + "644872192691135519287736182201377504", + "174898460680981733302111356557122107", + "1068744134187917319695255728151595132", + "1870792114609696396265442109963534232", + "8288818605536063568933922407756344", + "1446710439657393605686016190803199177", + "2256068140678002554491951090436701670", + "518946826903468667178458656376730744", + "3222036726675473160989497427257757" + ], + "signature": [ + "170161271844255892981997056109468295", + "2042410320678089637820651285407478756", + "2235307951907446725362990721960277744", + "2558650872283482274023232178928077002", + "1125115414447411231828809260942904609", + "2396701783176084287341878147443533109", + "2128856280301536390906877240389145121", + "2428098792522595894701475799989919597", + "1647552530172515032677576620955308208", + "2527537180972491287857094609185809092", + "2132950398601810533565118801188358141", + "160538878880934704009688085302112521", + "898519688023416454463467167922781011", + "258221678414411925992593903157944861", + "1423180960707426692015227448675294049", + "1156922850624474726553341869246641892", + "1773570027362757618569452574560009" + ], + "padded_header_len": 512, + "padded_body_len": 576, + "precomputed_sha": [ + 106, + 9, + 230, + 103, + 187, + 103, + 174, + 133, + 60, + 110, + 243, + 114, + 165, + 79, + 245, + 58, + 81, + 14, + 82, + 127, + 155, + 5, + 104, + 140, + 31, + 131, + 217, + 171, + 91, + 224, + 205, + 25 + ], + "account_code": "0x1162ebff40918afe5305e68396f0283eb675901d0387f97d21928d423aaa0b54", + "from_addr_idx": 69, + "domain_idx": 17, + "timestamp_idx": 297, + "code_idx": 380, + "command_idx": 0, + "padded_cleaned_body": null +} \ No newline at end of file diff --git a/packages/contracts/README.md b/packages/contracts/README.md index e3a36040..0df21735 100644 --- a/packages/contracts/README.md +++ b/packages/contracts/README.md @@ -27,10 +27,10 @@ Run integration tests Before running integration tests, you need to make a `packages/contracts/test/build_integration` directory, download the zip file from the following link, and place its unzipped directory under that directory. https://drive.google.com/file/d/1XDPFIL5YK8JzLGoTjmHLXO9zMDjSQcJH/view?usp=sharing -Then, move `email_auth.zkey` and `email_auth.wasm` in the unzipped directory `params` to `build_integration`. +Then, move `email_auth_with_body_parsing_with_qp_encoding.zkey` and `email_auth_with_body_parsing_with_qp_encoding.wasm` in the unzipped directory `params` to `build_integration`. -Run each integration tests **one by one** as each test will consume lot of memory. +Run each integration tests **one by one** as each test will consume a lot of memory. ```bash Eg: contracts % forge test --skip '*ZKSync*' --match-contract "IntegrationTest" -vvv --chain 8453 --ffi ``` @@ -48,20 +48,20 @@ After deploying common contracts, you can deploy a proxy contract of `SimpleWall ## Specification There are four main contracts that developers should understand: `IDKIMRegistry`, `Verifier`, `EmailAuth` and `EmailAccountRecovery`. -While the first three contracts are agnostic to usecases of our SDK, the last one is an abstract contract only for our email-based account recovery. +While the first three contracts are agnostic to use cases of our SDK, the last one is an abstract contract only for our email-based account recovery. ### `IDKIMRegistry` Contract It is an interface of the DKIM registry contract that traces public keys registered for each email domain in DNS. It is defined in [the zk-email library](https://github.com/zkemail/zk-email-verify/blob/main/packages/contracts/interfaces/IDKIMRegistry.sol). -It requires a function `isDKIMPublicKeyHashValid(string domainName, bytes32 publicKeyHash) view returns (bool)`: it returns true iff the given hash of the public key `publicKeyHash` is registered for the given email-domain name `domainName`. +It requires a function `isDKIMPublicKeyHashValid(string domainName, bytes32 publicKeyHash) view returns (bool)`: it returns true if the given hash of the public key `publicKeyHash` is registered for the given email-domain name `domainName`. One of its implementations is [`ECDSAOwnedDKIMRegistry`](https://github.com/zkemail/ether-email-auth/blob/main/packages/contracts/src/utils/ECDSAOwnedDKIMRegistry.sol). It stores the Ethereum address `signer` who can update the registry. -We also provide another implementation called [`ForwardDKIMRegistry`](https://github.com/zkemail/ether-email-auth/blob/main/packages/contracts/src/utils/ForwardDKIMRegistry.sol). It stores an address of any internal DKIM registry and forwards its outputs. We can use it to upgrade a proxy of the ECDSAOwnedDKIMRegistry registry to a new DKIM registry with a different storage slots design by 1) upgrading its implementation into ForwardDKIMRegistry and 2) calling resetStorageForUpgradeFromECDSAOwnedDKIMRegistry function with an address of the internal DKIM registry. +We also provide another implementation called [`ForwardDKIMRegistry`](https://github.com/zkemail/ether-email-auth/blob/main/packages/contracts/src/utils/ForwardDKIMRegistry.sol). It stores an address of any internal DKIM registry and forwards its outputs. We can use it to upgrade a proxy of the ECDSAOwnedDKIMRegistry registry to a new DKIM registry with a different storage slots design by 1) upgrading its implementation into ForwardDKIMRegistry and 2) calling `resetStorageForUpgradeFromECDSAOwnedDKIMRegistry` function with an address of the internal DKIM registry. ### `Verifier` Contract -It has a responsibility to verify a ZK proof for the [`email_auth.circom` circuit](https://github.com/zkemail/ether-email-auth/blob/main/packages/circuits/src/email_auth.circom). +It has the responsibility to verify a ZK proof for the [`email_auth_with_body_parsing_with_qp_encoding.circom` circuit](https://github.com/zkemail/ether-email-auth/blob/main/packages/circuits/src/email_auth_with_body_parsing_with_qp_encoding.circom). It is implemented in [`utils/Verifier.sol`](https://github.com/zkemail/ether-email-auth/blob/main/packages/contracts/src/utils/Verifier.sol). It defines a structure `EmailProof` consisting of the ZK proof and data of the instances necessary for proof verification as follows: @@ -70,34 +70,34 @@ struct EmailProof { string domainName; // Domain name of the sender's email bytes32 publicKeyHash; // Hash of the DKIM public key used in email/proof uint timestamp; // Timestamp of the email - string maskedSubject; // Masked subject of the email + string maskedCommand; // Masked command of the email bytes32 emailNullifier; // Nullifier of the email to prevent its reuse. bytes32 accountSalt; // Create2 salt of the account - bool isCodeExist; // Check if the account code is exist + bool isCodeExist; // Check if the account code exists bytes proof; // ZK Proof of Email } ``` -Using that, it provides a function `function verifyEmailProof(EmailProof memory proof) public view returns (bool)`: it takes as input the `EmailProof proof` and returns true iff the proof is valid. Notably, it internally calls [`Groth16Verifier.sol`](https://github.com/zkemail/ether-email-auth/blob/main/packages/contracts/src/utils/Groth16Verifier.sol) generated by snarkjs from the verifying key of the [`email_auth.circom` circuit](https://github.com/zkemail/ether-email-auth/blob/main/packages/circuits/src/email_auth.circom). +Using that, it provides a function `function verifyEmailProof(EmailProof memory proof) public view returns (bool)`: it takes as input the `EmailProof proof` and returns true if the proof is valid. Notably, it internally calls [`Groth16Verifier.sol`](https://github.com/zkemail/ether-email-auth/blob/main/packages/contracts/src/utils/Groth16Verifier.sol) generated by snarkjs from the verifying key of the [`email_auth_with_body_parsing_with_qp_encoding.circom` circuit](https://github.com/zkemail/ether-email-auth/blob/main/packages/circuits/src/email_auth_with_body_parsing_with_qp_encoding.circom). ### `EmailAuth` Contract It is a contract deployed for each email user to verify an email-auth message from that user. The structure of the email-auth message is defined as follows: ``` struct EmailAuthMsg { - uint templateId; // The ID of the subject template that the email subject should satisfy. - bytes[] subjectParams; // The parameters in the email subject, which should be taken according to the specified subject template. - uint skipedSubjectPrefix; // The number of skiiped bytes in the email subject. + uint templateId; // The ID of the command template that the email command should satisfy. + bytes[] commandParams; // The parameters in the email command, which should be taken according to the specified command template. + uint skippedCommandPrefix; // The number of skipped bytes in the email command. EmailProof proof; // The email proof containing the zk proof and other necessary information for the email verification by the verifier contract. -} -``` +} +``` It has the following storage variables. - `address owner`: an address of the contract owner. - `bytes32 accountSalt`: an `accountSalt` used for the CREATE2 salt of this contract. - `DKIMRegistry dkim`: an instance of the DKIM registry contract. - `Verifier verifier`: an instance of the Verifier contract. -- `address controller`: an address of a controller contract, defining the subject templates supported by this contract. -- `mapping(uint=>string[]) subjectTemplates`: a mapping of the supported subject templates associated with its ID. +- `address controller`: an address of a controller contract, defining the command templates supported by this contract. +- `mapping(uint=>string[]) commandTemplates`: a mapping of the supported command templates associated with its ID. - `mapping(bytes32⇒bytes32) authedHash`: a mapping of the hash of the authorized message associated with its `emailNullifier`. - `uint lastTimestamp`: the latest `timestamp` in the verified `EmailAuthMsg`. - `mapping(bytes32=>bool) usedNullifiers`: a mapping storing the used `emailNullifier` bytes. @@ -137,33 +137,33 @@ It provides the following functions. 1. Assert `msg.sender==owner` . 2. Assert `_dkimRegistryAddr!=0`. 3. Update `dkim` to `DKIMRegistry(_dkimRegistryAddr)`. -- `getSubjectTemplate(uint _templateId) public view returns (string[] memory)` - 1. Assert that the template for `_templateId` exists, i.e., `subjectTemplates[_templateId].length >0` holds. - 2. Return `subjectTemplates[_templateId]`. -- `insertSubjectTemplate(uint _templateId, string[] _subjectTemplate)` - 1. Assert `_subjectTemplate.length>0` . +- `getCommandTemplate(uint _templateId) public view returns (string[] memory)` + 1. Assert that the template for `_templateId` exists, i.e., `commandTemplates[_templateId].length >0` holds. + 2. Return `commandTemplates[_templateId]`. +- `insertCommandTemplate(uint _templateId, string[] _commandTemplate)` + 1. Assert `_commandTemplate.length>0` . 2. Assert `msg.sender==controller`. - 3. Assert `subjectTemplates[_templateId].length == 0`, i.e., no template has not been registered with `_templateId`. - 4. Set `subjectTemplates[_templateId]=_subjectTemplate`. -- `updateSubjectTemplate(uint _templateId, string[] _subjectTemplate)` - 1. Assert `_subjectTemplate.length>0` . + 3. Assert `commandTemplates[_templateId].length == 0`, i.e., no template has not been registered with `_templateId`. + 4. Set `commandTemplates[_templateId]=_commandTemplate`. +- `updateCommandTemplate(uint _templateId, string[] _commandTemplate)` + 1. Assert `_commandTemplate.length>0` . 2. Assert `msg.sender==controller`. - 3. Assert `subjectTemplates[_templateId].length != 0` , i.e., any template has been already registered with `_templateId`. - 4. Set `subjectTemplates[_templateId]=_subjectTemplate`. -- `deleteSubjectTemplate(uint _templateId)` + 3. Assert `commandTemplates[_templateId].length != 0` , i.e., any template has been already registered with `_templateId`. + 4. Set `commandTemplates[_templateId]=_commandTemplate`. +- `deleteCommandTemplate(uint _templateId)` 1. Assert `msg.sender==controller`. - 2. Assert `subjectTemplates[_templateId].length > 0`, i.e., any template has been already registered with `_templateId`. - 3. `delete subjectTemplates[_templateId]`. + 2. Assert `commandTemplates[_templateId].length > 0`, i.e., any template has been already registered with `_templateId`. + 3. `delete commandTemplates[_templateId]`. - `authEmail(EmailAuthMsg emailAuthMsg) returns (bytes32)` 1. Assert `msg.sender==controller`. - 2. Let `string[] memory template = subjectTemplates[emailAuthMsg.templateId]`. + 2. Let `string[] memory template = commandTemplates[emailAuthMsg.templateId]`. 3. Assert `template.length > 0`. 4. Assert `dkim.isDKIMPublicKeyHashValid(emailAuthMsg.proof.domain, emailAuthMsg.proof.publicKeyHash)==true`. 5. Assert `usedNullifiers[emailAuthMsg.proof.emailNullifier]==false` and set `usedNullifiers[emailAuthMsg.proof.emailNullifier]` to `true`. 6. Assert `accountSalt==emailAuthMsg.proof.accountSalt`. 7. If `timestampCheckEnabled` is true, assert that `emailAuthMsg.proof.timestamp` is zero OR `lastTimestamp < emailAuthMsg.proof.timestamp`, and update `lastTimestamp` to `emailAuthMsg.proof.timestamp`. - 8. Construct an expected subject `expectedSubject` from `template` and the values of `emailAuthMsg.subjectParams`. - 9. Assert that `expectedSubject` is equal to `emailAuthMsg.proof.maskedSubject[skipedSubjectPrefix:]` , i.e., the string of `emailAuthMsg.proof.maskedSubject` from the `skipedSubjectPrefix`-th byte. + 8. Construct an expected command `expectedCommand` from `template` and the values of `emailAuthMsg.commandParams`. + 9. Assert that `expectedCommand` is equal to `emailAuthMsg.proof.maskedCommand[skippedCommandPrefix:]` , i.e., the string of `emailAuthMsg.proof.maskedCommand` from the `skippedCommandPrefix`-th byte. 10. Assert `verifier.verifyEmailProof(emailAuthMsg.proof)==true`. - `isValidSignature(bytes32 _hash, bytes memory _signature) public view returns (bytes4 magicValue)` 1. Parse `_signature` as `(bytes32 emailNullifier)`. @@ -173,20 +173,20 @@ It provides the following functions. 2. Set `timestampCheckEnabled` to `enabled`. ### `EmailAccountRecovery` Contract -It is an abstract contract for each smart account brand to implement the email-based account recovery. **Each smart account provider only needs to implement the following functions in a new contract called controller.** In the following, the `templateIdx` is different from `templateId` in the email-auth contract in the sense that the `templateIdx` is an incremental index defined for each of the subject templates in `acceptanceSubjectTemplates()` and `recoverySubjectTemplates()`. +It is an abstract contract for each smart account brand to implement the email-based account recovery. **Each smart account provider only needs to implement the following functions in a new contract called controller.** In the following, the `templateIdx` is different from `templateId` in the email-auth contract in the sense that the `templateIdx` is an incremental index defined for each of the command templates in `acceptanceCommandTemplates()` and `recoveryCommandTemplates()`. - `isActivated(address recoveredAccount) public view virtual returns (bool)`: it returns if the account to be recovered has already activated the controller (the contract implementing `EmailAccountRecovery`). -- `acceptanceSubjectTemplates() public view virtual returns (string[][])`: it returns multiple subject templates for an email to accept becoming a guardian (acceptance email). -- `recoverySubjectTemplates() public view virtual returns (string[][])`: it returns multiple subject templates for an email to confirm the account recovery (recovery email). -- `extractRecoveredAccountFromAcceptanceSubject(bytes[] memory subjectParams, uint templateIdx) public view virtual returns (address)`: it takes as input the parameters `subjectParams` and the index of the chosen subject template `templateIdx` in those for acceptance emails. -- `extractRecoveredAccountFromRecoverySubject(bytes[] memory subjectParams, uint templateIdx) public view virtual returns (address)`: it takes as input the parameters `subjectParams` and the index of the chosen subject template `templateIdx` in those for recovery emails. -- `acceptGuardian(address guardian, uint templateIdx, bytes[] subjectParams, bytes32 emailNullifier) internal virtual`: it takes as input the Ethereum address `guardian` corresponding to the guardian's email address, the index `templateIdx` of the subject template in the output of `acceptanceSubjectTemplates()`, the parameter values of the variable parts `subjectParams` in the template `acceptanceSubjectTemplates()[templateIdx]`, and an email nullifier `emailNullifier`. It is called after verifying the email-auth message to accept the role of the guardian; thus you can assume the arguments are already verified. -- `processRecovery(address guardian, uint templateIdx, bytes[] subjectParams, bytes32 emailNullifier) internal virtual`: it takes as input the Ethereum address `guardian` corresponding to the guardian's email address, the index `templateIdx` of the subject template in the output of `recoverySubjectTemplates()`, the parameter values of the variable parts `subjectParams` in the template `recoverySubjectTemplates()[templateIdx]`, and an email nullifier `emailNullifier`. It is called after verifying the email-auth message to confirm the recovery; thus you can assume the arguments are already verified. +- `acceptanceCommandTemplates() public view virtual returns (string[][])`: it returns multiple command templates for an email to accept becoming a guardian (acceptance email). +- `recoveryCommandTemplates() public view virtual returns (string[][])`: it returns multiple command templates for an email to confirm the account recovery (recovery email). +- `extractRecoveredAccountFromAcceptanceCommand(bytes[] memory commandParams, uint templateIdx) public view virtual returns (address)`: it takes as input the parameters `commandParams` and the index of the chosen command template `templateIdx` in those for acceptance emails. +- `extractRecoveredAccountFromRecoveryCommand(bytes[] memory commandParams, uint templateIdx) public view virtual returns (address)`: it takes as input the parameters `commandParams` and the index of the chosen command template `templateIdx` in those for recovery emails. +- `acceptGuardian(address guardian, uint templateIdx, bytes[] commandParams, bytes32 emailNullifier) internal virtual`: it takes as input the Ethereum address `guardian` corresponding to the guardian's email address, the index `templateIdx` of the command template in the output of `acceptanceCommandTemplates()`, the parameter values of the variable parts `commandParams` in the template `acceptanceCommandTemplates()[templateIdx]`, and an email nullifier `emailNullifier`. It is called after verifying the email-auth message to accept the role of the guardian; thus you can assume the arguments are already verified. +- `processRecovery(address guardian, uint templateIdx, bytes[] commandParams, bytes32 emailNullifier) internal virtual`: it takes as input the Ethereum address `guardian` corresponding to the guardian's email address, the index `templateIdx` of the command template in the output of `recoveryCommandTemplates()`, the parameter values of the variable parts `commandParams` in the template `recoveryCommandTemplates()[templateIdx]`, and an email nullifier `emailNullifier`. It is called after verifying the email-auth message to confirm the recovery; thus you can assume the arguments are already verified. - `completeRecovery(address account, bytes memory completeCalldata) external virtual`: it can be called by anyone, in particular a Relayer, when completing the account recovery. It should first check if the condition for the recovery of `account` holds and then update its owner's address in the wallet contract. It also provides the following entry functions with their default implementations, called by the Relayer. - `handleAcceptance(EmailAuthMsg emailAuthMsg, uint templateIdx) external` - 1. Extract an account address to be recovered `recoveredAccount` by calling `extractRecoveredAccountFromAcceptanceSubject`. + 1. Extract an account address to be recovered `recoveredAccount` by calling `extractRecoveredAccountFromAcceptanceCommand`. 2. Let `address guardian = CREATE2(emailAuthMsg.proof.accountSalt, ERC1967Proxy.creationCode, emailAuthImplementation(), (emailAuthMsg.proof.accountSalt))`. 3. Let `uint templateId = keccak256(EMAIL_ACCOUNT_RECOVERY_VERSION_ID, "ACCEPTANCE", templateIdx)`. 4. Assert that `templateId` is equal to `emailAuthMsg.templateId`. @@ -194,19 +194,19 @@ It also provides the following entry functions with their default implementation 6. If the `EmailAuth` contract of `guardian` has not been deployed, deploy the proxy contract of `emailAuthImplementation()`. Its salt is `emailAuthMsg.proof.accountSalt` and its initialization parameter is `recoveredAccount`, `emailAuthMsg.proof.accountSalt`, and `address(this)`, which is a controller of the deployed contract. 7. If the `EmailAuth` contract of `guardian` has not been deployed, call `EmailAuth(guardian).initDKIMRegistry(dkim())`. 8. If the `EmailAuth` contract of `guardian` has not been deployed, call `EmailAuth(guardian).initVerifier(verifier())`. - 9. If the `EmailAuth` contract of `guardian` has not been deployed, for each `template` in `acceptanceSubjectTemplates()` along with its index `idx`, call `EmailAuth(guardian).insertSubjectTemplate(keccak256(EMAIL_ACCOUNT_RECOVERY_VERSION_ID, "ACCEPTANCE", idx), template)`. - 10. If the `EmailAuth` contract of `guardian` has not been deployed, for each `template` in `recoverySubjectTemplates()` along with its index `idx`, call `EmailAuth(guardian).insertSubjectTemplate(keccak256(EMAIL_ACCOUNT_RECOVERY_VERSION_ID, "RECOVERY", idx), template)`. + 9. If the `EmailAuth` contract of `guardian` has not been deployed, for each `template` in `acceptanceCommandTemplates()` along with its index `idx`, call `EmailAuth(guardian).insertCommandTemplate(keccak256(EMAIL_ACCOUNT_RECOVERY_VERSION_ID, "ACCEPTANCE", idx), template)`. + 10. If the `EmailAuth` contract of `guardian` has not been deployed, for each `template` in `recoveryCommandTemplates()` along with its index `idx`, call `EmailAuth(guardian).insertCommandTemplate(keccak256(EMAIL_ACCOUNT_RECOVERY_VERSION_ID, "RECOVERY", idx), template)`. 11. If the `EmailAuth` contract of `guardian` has been already deployed, assert that its `controller` is equal to `address(this)`. - 11. Assert that `EmailAuth(guardian).authEmail(1emailAuthMsg)` returns no error. - 12. Call `acceptGuardian(guardian, templateIdx, emailAuthMsg.subjectParams, emailAuthMsg.proof.emailNullifier)`. + 11. Assert that `EmailAuth(guardian).authEmail(emailAuthMsg)` returns no error. + 12. Call `acceptGuardian(guardian, templateIdx, emailAuthMsg.commandParams, emailAuthMsg.proof.emailNullifier)`. - `handleRecovery(EmailAuthMsg emailAuthMsg, uint templateIdx) external` - 1. Extract an account address to be recovered `recoveredAccount` by calling `extractRecoveredAccountFromRecoverySubject`. + 1. Extract an account address to be recovered `recoveredAccount` by calling `extractRecoveredAccountFromRecoveryCommand`. 1. Let `address guardian = CREATE2(emailAuthMsg.proof.accountSalt, ERC1967Proxy.creationCode, emailAuthImplementation(), (emailAuthMsg.proof.accountSalt))`. 2. Assert that the contract of `guardian` has been already deployed. 3. Let `uint templateId=keccak256(EMAIL_ACCOUNT_RECOVERY_VERSION_ID, "RECOVERY", templateIdx)`. 4. Assert that `templateId` is equal to `emailAuthMsg.templateId`. 5. Assert that `EmailAuth(guardian).authEmail(emailAuthMsg)` returns no error. - 6. Call `processRecovery(guardian, templateIdx, emailAuthMsg.subjectParams, emailAuthMsg.proof.emailNullifier)`. + 6. Call `processRecovery(guardian, templateIdx, emailAuthMsg.commandParams, emailAuthMsg.proof.emailNullifier)`. # For zkSync @@ -239,11 +239,11 @@ chmod a+x {BINARY_NAME} mv {BINARY_NAME} ~/.zksync/. ``` -In addition, there are the problem with foundy-zksync. Currently they can't resolve contracts in monorepo's node_modules. +In addition, there are problems with foundry-zksync. Currently, they can't resolve contracts in monorepo's node_modules. https://github.com/matter-labs/foundry-zksync/issues/411 -To fix this, you should copy `node_modules` in the project root dir to `packages/contracts/node_modules`. And then you should replace `libs = ["../../node_modules", "lib"]` to `libs = ["node_modules", "lib"]` in `foundry.toml`. At the end, you should replace `../../node_modules` to `node_modules` in `remappings.txt`. +To fix this, you should copy `node_modules` in the project root dir to `packages/contracts/node_modules`. And then you should replace `libs = ["../../node_modules", "lib"]` with `libs = ["node_modules", "lib"]` in `foundry.toml`. At the end, you should replace `../../node_modules` with `node_modules` in `remappings.txt`. Next, you should uncomment the following lines in `foundry.toml`. @@ -292,7 +292,121 @@ Incidentally, the above line already exists in `foundy.toml` with it commented o About Create2, `L2ContractHelper.computeCreate2Address` should be used. And `type(ERC1967Proxy).creationCode` doesn't work correctly in zkSync. We need to hardcode the `type(ERC1967Proxy).creationCode` to bytecodeHash. -Perhaps that is different value in each compiler version. +Perhaps that is a different value in each compiler version. + +You should replace the following line to the correct hash. +packages/contracts/src/EmailAccountRecovery.sol:L111 + +See, test/ComputeCreate2Address.t.sol + +# For zkSync testing + +Run `yarn zktest`. + +Current foundry-zksync overrides the foundry behavior. If you installed foundry-zksync, some EVM code will be different and some test cases will fail. If you want to test on other EVM, please install foundry. + +Even if the contract size is fine for EVM, it may exceed the bytecode size limit for zksync, and the test may not be executed. +Therefore, EmailAccountRecovery.t.sol has been split. + +Currently, some test cases are not working correctly because there is an issue about missing libraries. + +https://github.com/matter-labs/foundry-zksync/issues/382 + +Failing test cases are here. + +DKIMRegistryUpgrade.t.sol + +- testAuthEmail() + +EmailAuth.t.sol + +- testAuthEmail() +- testExpectRevertAuthEmailEmailNullifierAlreadyUsed() +- testExpectRevertAuthEmailInvalidEmailProof() +- testExpectRevertAuthEmailInvalidCommand() +- testExpectRevertAuthEmailInvalidTimestamp() + +EmailAuthWithUserOverrideableDkim.t.sol + +- testAuthEmail() + +# For integration testing + +To pass the integration testing, you should use era-test-node. +See the following URL and install it. +https://github.com/matter-labs/era-test-node + +Run the era-test-node + +``` +era_test_node fork https://sepolia.era.zksync.dev +``` + +You remove .zksolc-libraries-cache directory, and run the following command. + +``` +forge build --zksync --zk-detect-missing-libraries +``` + +As you saw before, you need to deploy missing libraries. +You can deploy them by the following command for example. + +``` +Missing libraries detected: src/libraries/CommandUtils.sol:CommandUtils, src/libraries/DecimalUtils.sol:DecimalUtils + +Run the following command in order to deploy each missing library: + +forge create src/libraries/DecimalUtils.sol:DecimalUtils --private-key {YOUR_PRIVATE_KEY} --rpc-url http://127.0.0.1:8011 --chain 260 --zksync +forge create src/libraries/CommandUtils.sol:CommandUtils --private-key {YOUR_PRIVATE_KEY} --rpc-url http://127.0.0.1:8011 --chain 260 --zksync --libraries src/libraries/DecimalUtils.sol:DecimalUtils:{DECIMAL_UTILS_DEPLOYED_ADDRESS} +``` + +Set the libraries in foundry.toml using the above deployed address. + +Due to this change in the address of the missing libraries, the value of the proxyBytecodeHash must also be changed: change the value of the proxyBytecodeHash in E-mailAccountRecoveryZKSync.sol. + +And then, run the integration testing. + +``` +forge test --match-contract "IntegrationZKSyncTest" --system-mode=true --zksync --gas-limit 1000000000 --chain 300 -vvv --ffi +``` + +# For zkSync deployment (For test net) + +You need to edit .env at first. +Second, just run the following commands with `--zksync` + +``` +source .env +forge script script/DeployRecoveryControllerZKSync.s.sol:Deploy --zksync --rpc-url $RPC_URL --broadcast --slow --via-ir --system-mode true -vvvv +``` + +As you saw before, you need to deploy missing libraries. +You can deploy them by the following command for example. + +``` +Missing libraries detected: src/libraries/CommandUtils.sol:CommandUtils, src/libraries/DecimalUtils.sol:DecimalUtils + +Run the following command in order to deploy each missing library: + +forge create src/libraries/DecimalUtils.sol:DecimalUtils --private-key {YOUR_PRIVATE_KEY} --rpc-url https://sepolia.era.zksync.dev --chain 300 --zksync +forge create src/libraries/CommandUtils.sol:CommandUtils --private-key {YOUR_PRIVATE_KEY} --rpc-url https://sepolia.era.zksync.dev --chain 300 --zksync --libraries src/libraries/DecimalUtils.sol:DecimalUtils:{DECIMAL_UTILS_DEPLOYED_ADDRESS} +``` + +After that, you can see the following line in foundry.toml. +Also, this line is needed only for foundry-zksync, if you use foundry, please remove this line. Otherwise, the test will fail. + +``` +libraries = [ + "{PROJECT_DIR}/packages/contracts/src/libraries/DecimalUtils.sol:DecimalUtils:{DEPLOYED_ADDRESS}", + "{PROJECT_DIR}/packages/contracts/src/libraries/CommandUtils.sol:CommandUtils:{DEPLOYED_ADDRESS}"] +``` + +Incidentally, the above line already exists in `foundy.toml` with it commented out, if you uncomment it by replacing `{PROJECT_DIR}` with the appropriate path, it will also work. + +About Create2, `L2ContractHelper.computeCreate2Address` should be used. +And `type(ERC1967Proxy).creationCode` doesn't work correctly in zkSync. +We need to hardcode the `type(ERC1967Proxy).creationCode` to bytecodeHash. +Perhaps that is a different value in each compiler version. You should replace the following line to the correct hash. packages/contracts/src/EmailAccountRecovery.sol:L111 @@ -303,12 +417,12 @@ See, test/ComputeCreate2Address.t.sol Run `yarn zktest`. -Current foundry-zksync overrides the foundry behavior. If you installed foundry-zksync, some EVM code will be different and some test cases will be failed. If you want to test on other EVM, please install foundry. +Current foundry-zksync overrides the foundry behavior. If you installed foundry-zksync, some EVM code will be different and some test cases will fail. If you want to test on other EVM, please install foundry. Even if the contract size is fine for EVM, it may exceed the bytecode size limit for zksync, and the test may not be executed. -Therefore, EmailAccountRecovery.t.sol has been splited. +Therefore, EmailAccountRecovery.t.sol has been split. -Currently some test cases are not work correctly because there is a issue about missing libraries. +Currently, some test cases are not working correctly because there is an issue about missing libraries. https://github.com/matter-labs/foundry-zksync/issues/382 @@ -323,7 +437,7 @@ EmailAuth.t.sol - testAuthEmail() - testExpectRevertAuthEmailEmailNullifierAlreadyUsed() - testExpectRevertAuthEmailInvalidEmailProof() -- testExpectRevertAuthEmailInvalidSubject() +- testExpectRevertAuthEmailInvalidCommand() - testExpectRevertAuthEmailInvalidTimestamp() EmailAuthWithUserOverrideableDkim.t.sol @@ -332,7 +446,7 @@ EmailAuthWithUserOverrideableDkim.t.sol # For integration testing -To pass the instegration testing, you should use era-test-node. +To pass the integration testing, you should use era-test-node. See the following URL and install it. https://github.com/matter-labs/era-test-node