From 353833e1785b6fba99ad0454fa499834ccf6b97f Mon Sep 17 00:00:00 2001
From: zkbenny <benny@zklink.org>
Date: Sun, 1 Sep 2024 19:37:34 +0800
Subject: [PATCH] feat: add PasskeyBinder

---
 .../scripts/upgrade-consistency-checker.ts    |   8 +-
 system-contracts/SystemContractsHashes.json   |  11 +-
 system-contracts/contracts/DefaultAccount.sol |   5 +-
 system-contracts/contracts/PasskeyBinder.sol  | 127 ++++++++++++++++++
 .../contracts/interfaces/IPasskeyBinder.sol   |  11 ++
 system-contracts/scripts/constants.ts         |   5 +
 6 files changed, 160 insertions(+), 7 deletions(-)
 create mode 100644 system-contracts/contracts/PasskeyBinder.sol
 create mode 100644 system-contracts/contracts/interfaces/IPasskeyBinder.sol

diff --git a/l1-contracts/scripts/upgrade-consistency-checker.ts b/l1-contracts/scripts/upgrade-consistency-checker.ts
index 3ccb9db64..932c68ea0 100644
--- a/l1-contracts/scripts/upgrade-consistency-checker.ts
+++ b/l1-contracts/scripts/upgrade-consistency-checker.ts
@@ -58,16 +58,16 @@ const maxNumberOfHyperchains = 100;
 const expectedStoredBatchHashZero = "0x1574fa776dec8da2071e5f20d71840bfcbd82c2bca9ad68680edfedde1710bc4";
 const expectedL2BridgeAddress = "0x11f943b2c77b743AB90f4A0Ae7d5A4e7FCA3E102";
 const expectedL1LegacyBridge = "0x57891966931Eb4Bb6FB81430E6cE0A03AAbDe063";
-const expectedGenesisBatchCommitment = "0x2d00e5f8d77afcebf58a6b82ae56ba967566fe7dfbcb6760319fb0d215d18ffd";
-const expectedIndexRepeatedStorageChanges = BigNumber.from(54);
+const expectedGenesisBatchCommitment = "0x667177606c5d72ce5988172b151b0a97e6cd67de002f86ec66c3899cd9ce7d4c";
+const expectedIndexRepeatedStorageChanges = BigNumber.from(56);
 const expectedProtocolVersion = BigNumber.from(2).pow(32).mul(24);
 
-const expectedGenesisRoot = "0xabdb766b18a479a5c783a4b80e12686bc8ea3cc2d8a3050491b701d72370ebb5";
+const expectedGenesisRoot = "0x2e86468e2aa39e313daed4f4ea1865ef11876cc700fea35a1695de22af99915b";
 const expectedRecursionNodeLevelVkHash = "0xf520cd5b37e74e19fdb369c8d676a04dce8a19457497ac6686d2bb95d94109c8";
 const expectedRecursionLeafLevelVkHash = "0xf9664f4324c1400fa5c3822d667f30e873f53f1b8033180cd15fe41c1e2355c6";
 const expectedRecursionCircuitsSetVksHash = "0x0000000000000000000000000000000000000000000000000000000000000000";
 const expectedBootloaderHash = "0x010008e742608b21bf7eb23c1a9d0602047e3618b464c9b59c0fba3b3d7ab66e";
-const expectedDefaultAccountHash = "0x01000563374c277a2c1e34659a2a1e87371bb6d852ce142022d497bfb50b9e32";
+const expectedDefaultAccountHash = "0x01000567eb1d0eac3e32d1a5b5a0ececcbaf7a0b38b3fd7ce1eb8ff8296ef544";
 
 const validatorOne = process.env.ETH_SENDER_SENDER_OPERATOR_COMMIT_ETH_ADDR!;
 
diff --git a/system-contracts/SystemContractsHashes.json b/system-contracts/SystemContractsHashes.json
index 7cb1c5b9c..d70a272a8 100644
--- a/system-contracts/SystemContractsHashes.json
+++ b/system-contracts/SystemContractsHashes.json
@@ -45,8 +45,8 @@
     "contractName": "DefaultAccount",
     "bytecodePath": "artifacts-zk/contracts-preprocessed/DefaultAccount.sol/DefaultAccount.json",
     "sourceCodePath": "contracts-preprocessed/DefaultAccount.sol",
-    "bytecodeHash": "0x01000563374c277a2c1e34659a2a1e87371bb6d852ce142022d497bfb50b9e32",
-    "sourceCodeHash": "0xa42423712ddaa8f357d26e46825fda80a9a870d0ac7ff52c98884355f1173ec7"
+    "bytecodeHash": "0x01000567eb1d0eac3e32d1a5b5a0ececcbaf7a0b38b3fd7ce1eb8ff8296ef544",
+    "sourceCodeHash": "0x1a601a1c617c81daf95a03933b436987a03d6984ed6a49e71310dc706449e2fc"
   },
   {
     "contractName": "EmptyContract",
@@ -97,6 +97,13 @@
     "bytecodeHash": "0x010000e563d4ad7b4822cc19d8f74f2c41ee3d3153379be4b02b27d4498d52b6",
     "sourceCodeHash": "0x91847512344ac5026e9fd396189c23ad9e253f22cb6e2fe65805c20c915797d4"
   },
+  {
+    "contractName": "PasskeyBinder",
+    "bytecodePath": "artifacts-zk/contracts-preprocessed/PasskeyBinder.sol/PasskeyBinder.json",
+    "sourceCodePath": "contracts-preprocessed/PasskeyBinder.sol",
+    "bytecodeHash": "0x010001ddd2d9e5935a6fff10d41e305a9ff33340cdcf15536546332efa3e3c68",
+    "sourceCodeHash": "0xa331e26de173330a95f7f6eec7a2ad66379d783c996d12eea3d5d6bb9efce6a4"
+  },
   {
     "contractName": "PubdataChunkPublisher",
     "bytecodePath": "artifacts-zk/contracts-preprocessed/PubdataChunkPublisher.sol/PubdataChunkPublisher.json",
diff --git a/system-contracts/contracts/DefaultAccount.sol b/system-contracts/contracts/DefaultAccount.sol
index 4c7356dd8..6f78ad3ef 100644
--- a/system-contracts/contracts/DefaultAccount.sol
+++ b/system-contracts/contracts/DefaultAccount.sol
@@ -3,11 +3,12 @@
 pragma solidity 0.8.20;
 
 import {IAccount, ACCOUNT_VALIDATION_SUCCESS_MAGIC} from "./interfaces/IAccount.sol";
+import {IPasskeyBinder} from "./interfaces/IPasskeyBinder.sol";
 import {TransactionHelper, Transaction} from "./libraries/TransactionHelper.sol";
 import {SystemContractsCaller} from "./libraries/SystemContractsCaller.sol";
 import {SystemContractHelper} from "./libraries/SystemContractHelper.sol";
 import {EfficientCall} from "./libraries/EfficientCall.sol";
-import {BOOTLOADER_FORMAL_ADDRESS, NONCE_HOLDER_SYSTEM_CONTRACT, DEPLOYER_SYSTEM_CONTRACT, INonceHolder} from "./Constants.sol";
+import {BOOTLOADER_FORMAL_ADDRESS, NONCE_HOLDER_SYSTEM_CONTRACT, DEPLOYER_SYSTEM_CONTRACT, INonceHolder, SYSTEM_CONTRACTS_OFFSET} from "./Constants.sol";
 import {Utils} from "./libraries/Utils.sol";
 
 /**
@@ -21,6 +22,8 @@ import {Utils} from "./libraries/Utils.sol";
 contract DefaultAccount is IAccount {
     using TransactionHelper for *;
 
+    IPasskeyBinder public constant PASSKEY_BINDER = IPasskeyBinder(address(SYSTEM_CONTRACTS_OFFSET + 0xff));
+
     /**
      * @dev Simulate the behavior of the EOA if the caller is not the bootloader.
      * Essentially, for all non-bootloader callers halt the execution with empty return data.
diff --git a/system-contracts/contracts/PasskeyBinder.sol b/system-contracts/contracts/PasskeyBinder.sol
new file mode 100644
index 000000000..301453cde
--- /dev/null
+++ b/system-contracts/contracts/PasskeyBinder.sol
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity 0.8.20;
+
+contract PasskeyBinder {
+    //curve prime field modulus
+    uint256 private constant p = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF;
+    //short weierstrass second coefficient
+    uint256 private constant b = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B;
+    //short weierstrass first coefficient
+    uint256 private constant a = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC;
+
+    struct P256PublicKey {
+        uint256 x;
+        uint256 y;
+    }
+
+    mapping(bytes32 keyIdHash => P256PublicKey) private authorizedKeys;
+    mapping(bytes32 keyIdHash => address account) private keyIdHashToAccount;
+    mapping(address account => string[] keyIds) private accountToKeyIdList;
+
+    /// @dev Event emitted when a P256 key is added
+    event AddedP256Key(bytes32 indexed keyIdHash, string keyId, uint256 x, uint256 y);
+
+    /// @dev Event emitted when a P256 key is removed
+    event RemovedP256Key(bytes32 indexed keyIdHash, uint256 x, uint256 y);
+
+    /// @dev Error emitted when a P256 key is not on the curve
+    error KeyNotOnCurve(uint256 x, uint256 y);
+    /// @dev Error emitted when an empty key is attempted to be added
+    error InvalidEmptyKey();
+    /// @dev Error emitted when a P256 key is already stored and attempted to be added
+    error KeyAlreadyExists(string keyId);
+    /// @dev Error emitted when a P256 key is not stored and attempted to be removed
+    error KeyDoesNotExist(string keyId);
+    /// @dev Error emitted when a P256 key is not owned by the caller
+    error DoesNotOwner(string keyId);
+
+    function addKey(string calldata _keyId, uint256 _x, uint256 _y) external {
+        // slither-disable-next-line tx-origin
+        require(msg.sender == tx.origin, "Not authorized");
+        _addKey(_keyId, _x, _y);
+    }
+
+    function _addKey(string calldata _keyId, uint256 _x, uint256 _y) internal {
+        if (!isValidPublicKey(_x, _y)) revert KeyNotOnCurve(_x, _y);
+        bytes32 keyIdHash_ = keccak256(abi.encodePacked(_keyId));
+
+        if (bytes(_keyId).length == 0) revert InvalidEmptyKey();
+
+        P256PublicKey storage publicKey_ = authorizedKeys[keyIdHash_];
+
+        // update key
+        if (publicKey_.x != 0 || publicKey_.y != 0) {
+            revert KeyAlreadyExists(_keyId);
+        }
+
+        authorizedKeys[keyIdHash_] = P256PublicKey(_x, _y);
+        keyIdHashToAccount[keyIdHash_] = msg.sender;
+        accountToKeyIdList[msg.sender].push(_keyId);
+
+        emit AddedP256Key(keyIdHash_, _keyId, _x, _y);
+    }
+
+    function removeKey(string calldata _keyId) external {
+        bytes32 keyIdHash_ = keccak256(abi.encodePacked(_keyId));
+        if (keyIdHashToAccount[keyIdHash_] != msg.sender) revert DoesNotOwner(_keyId);
+        P256PublicKey memory publicKey_ = authorizedKeys[keyIdHash_];
+        uint256 x_ = publicKey_.x;
+        uint256 y_ = publicKey_.y;
+
+        if (x_ == 0 && y_ == 0) revert KeyDoesNotExist(_keyId);
+
+        delete authorizedKeys[keyIdHash_];
+        delete keyIdHashToAccount[keyIdHash_];
+        uint256 length = accountToKeyIdList[msg.sender].length;
+        for (uint256 i = 0; i < length; i++) {
+            if (keccak256(abi.encodePacked(accountToKeyIdList[msg.sender][i])) == keyIdHash_) {
+                accountToKeyIdList[msg.sender][i] = accountToKeyIdList[msg.sender][length - 1];
+                accountToKeyIdList[msg.sender].pop();
+                break;
+            }
+        }
+
+        emit RemovedP256Key(keyIdHash_, x_, y_);
+    }
+    /**
+     * @notice Returns the P256 public key coordinates of a given key ID if it is a signer
+     * @param keyIdHash The ID Hash of the key to get
+     * @return x_ The X value of the public key
+     * @return y_ The Y value of the public key
+     */
+    function getKey(bytes32 keyIdHash) external view returns (uint256 x_, uint256 y_) {
+        P256PublicKey memory publicKey_ = authorizedKeys[keyIdHash];
+        x_ = publicKey_.x;
+        y_ = publicKey_.y;
+    }
+
+    function getKeyIdLength(address _account) external view returns (uint256) {
+        return accountToKeyIdList[_account].length;
+    }
+
+    function getKeyIdByIndex(address _account, uint256 _index) external view returns (string memory) {
+        return accountToKeyIdList[_account][_index];
+    }
+
+    function getAccountByKeyIdHash(bytes32 keyIdHash) external view returns (address) {
+        return keyIdHashToAccount[keyIdHash];
+    }
+
+    function getP256PublicKey(bytes32 keyIdHash) external view returns (P256PublicKey memory) {
+        return authorizedKeys[keyIdHash];
+    }
+
+    function isValidPublicKey(uint256 x, uint256 y) internal pure returns (bool) {
+        if (x >= p || y >= p || ((x == 0) && (y == 0))) {
+            return false;
+        }
+        unchecked {
+            uint256 LHS = mulmod(y, y, p); // y^2
+            uint256 RHS = addmod(mulmod(mulmod(x, x, p), x, p), mulmod(x, a, p), p); // x^3+ax
+            RHS = addmod(RHS, b, p); // x^3 + a*x + b
+
+            return LHS == RHS;
+        }
+    }
+}
diff --git a/system-contracts/contracts/interfaces/IPasskeyBinder.sol b/system-contracts/contracts/interfaces/IPasskeyBinder.sol
new file mode 100644
index 000000000..accf6adaa
--- /dev/null
+++ b/system-contracts/contracts/interfaces/IPasskeyBinder.sol
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity 0.8.20;
+
+interface IPasskeyBinder {
+    /// @dev Returns the public key associated with the given keyIdHash.
+    /// @param keyIdHash The hash of the keyId.
+    /// @return x_ The x-coordinate of the public key.
+    /// @return y_ The y-coordinate of the public key.
+    function getKey(bytes32 keyIdHash) external view returns (uint256 x_, uint256 y_);
+}
diff --git a/system-contracts/scripts/constants.ts b/system-contracts/scripts/constants.ts
index 406b4cb6e..84a7e0d7c 100644
--- a/system-contracts/scripts/constants.ts
+++ b/system-contracts/scripts/constants.ts
@@ -166,6 +166,11 @@ export const SYSTEM_CONTRACTS: ISystemContracts = {
     codeName: "PubdataChunkPublisher",
     lang: Language.Solidity,
   },
+  passkeyBinder: {
+    address: "0x00000000000000000000000000000000000080ff",
+    codeName: "PasskeyBinder",
+    lang: Language.Solidity,
+  },
   create2Factory: {
     // This is explicitly a non-system-contract address.
     // We do not use the same address as create2 factories on EVM, since