Skip to content

Commit

Permalink
eth contract: try the minimized approach (#178)
Browse files Browse the repository at this point in the history
* audit: disable all initialization for implementation contract and add keyVersion variable

* address comments

* try the minimized eth contract approach

* add withdraw function with AccessControl

* add to .gitignore

* finalize eth contract

* tune down the gas for respond

* Update chain-signatures/contract-eth/contracts/ChainSignatures.sol

Co-authored-by: Phuong Nguyen <[email protected]>

* added event Withdraw event

* format eth contract

* etch contract refactoring, parameters and requestId tuned

* pass initial deposit in constructor

* remove unused String import, simlify naming

* renamed admin parameter

* remove request id calculations from eth contract

* remove timestemp

* Added check of payload to indexer eth

* add indexer changes to support the contract change

* add configs for mainnet deploy

* fix mainnet key

* add script for generating ethereum account keys

* dummy sk for testing

* remove Gas Optimization section

---------

Co-authored-by: Xiangyi Zheng <[email protected]>
Co-authored-by: Serhii Volovyk <[email protected]>
Co-authored-by: Phuong Nguyen <[email protected]>
Co-authored-by: Phuong N <[email protected]>
  • Loading branch information
5 people authored Feb 12, 2025
1 parent ca53f62 commit f565b57
Show file tree
Hide file tree
Showing 24 changed files with 4,157 additions and 1,652 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ integration-tests/.project
node_modules/
chain-signatures/contract-eth/artifacts
chain-signatures/contract-eth/cache
chain-signatures/contract-eth/ignition/deployments/chain-31337
chain-signatures/contract-eth/deployments/localhost.json
chain-signatures/contract-eth/scripts/params.json
chain-signatures/contract-eth/scripts/params.json
177 changes: 0 additions & 177 deletions chain-signatures/contract-eth/.openzeppelin/sepolia.json

This file was deleted.

79 changes: 44 additions & 35 deletions chain-signatures/contract-eth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,61 +33,74 @@ npx hardhat test
To do a local end-to-end test, following these steps.

1. Run the following command to spin up a local Ethereum node:

```bash
npx hardhat node
```

2. Figure out the contract address. Run and copy the "Future contract address" from the output:
2. Figure out the contract address, admin address and receiver address(receiver of contract balance). Run and copy the "Future contract address" from the output:

```bash
npx hardhat --network localhost run scripts/findAddress.js
```

3. Make sure you have docker daemon running. Open another terminal window, go to `integration-tests` and run the following command to start the MPC cluster:

```bash
cargo run -- setup-env --eth-contract-address <future-contract-address>
cargo run -- setup-env --eth-contract-address <eth-contract-address-without-0x-prefix>
```

4. In MPC cluster log, search for log `voting for public key public_key=secp256k1:` and copy the public key after `secp256k1:`
4. Populate the ignition/params.json with admin address:

5. Open another terminal window, Then run the following command to config the mpc public key for deploying ethereum contract:
```bash
node scripts/convertPk.js <public_key>
```
For example:
```bash
node scripts/convertPk.js 37xNKgg4LvhuaMBPThHEZNp6VJHu8KsATkPrCKrsfbwQEas1erep8otiB37F99tvY5aM3s78uzix49t5BjxuBYzD
node scripts/populateParams.js <adminAddress> <deposit required in wei>
```

6. Then run the following command to deploy the contract:
5. Then run the following command to deploy the contract:

```bash
npx hardhat run scripts/initialDeploy.js --network localhost
npx hardhat ignition deploy ignition/modules/chainSignatures.js --parameters ignition/params.json --network localhost
```

7. Then run the following command to request a signature from MPC:
6. Then run the following command to request a signature from MPC:

```bash
npx hardhat run scripts/requestSign.js --network localhost
```

In a few seconds,you should see the signature response from MPC printed by requestSign.js.

## Run it on Ethereum Sepolia Testnet from end to end

1. Get a Sepolia testnet ETH wallet with some ETH and a infura (MetaMask Developer) API key. You can also ping Bo for using his keys.
2. Config hardhat with the infura api key and ethereum account secret key, enter as is and it will ask you the key interactively:

```bash
npx hardhat vars set INFURA_API_KEY
npx hardhat vars set SEPOLIA_PRIVATE_KEY
npx hardhat vars set MAINNET_PRIVATE_KEY
```
3. Deploy the contract with right public key configured in `params.json`.

3. Populate the ignition/params.json with admin address:

```bash
npx hardhat run scripts/initialDeploy.js --network sepolia
node scripts/populateParams.js <adminAddress> <deposit required in wei>
```

4. Make sure you have docker daemon running. Go to `integration-tests` and run the following command to start the MPC cluster connected to Ethereum Sepolia Testnet:
4. Deploy the contract with right public key configured in `params.json`.

```bash
cargo run -- setup-env --eth-rpc-url https://sepolia.infura.io/v3/<infura-api-key> --eth-account-sk <eth-account-secret-key-without-0x-prefix> --eth-contract-address <eth-proxy-contract-address>
npx hardhat ignition deploy ignition/modules/chainSignatures.js --parameters ignition/params.json --network sepolia
```

5. Then run the following command to request a signature from MPC:
5. Make sure you have docker daemon running. Go to `integration-tests` and run the following command to start the MPC cluster connected to Ethereum Sepolia Testnet:

```bash
cargo run -- setup-env --eth-rpc-ws-url wss://sepolia.infura.io/ws/v3/<api-key> --eth-rpc-http-url https://sepolia.infura.io/v3/<api-key> --eth-account-sk <eth-account-sk> --eth-contract-address <eth-contract-address-without-0x-prefix>
```

6. Then run the following command to request a signature from MPC:

```bash
npx hardhat run scripts/requestSign.js --network sepolia
```
Expand All @@ -96,29 +109,25 @@ Wait a moment, you should see the signature response from MPC printed by request

Note that everything is slower on Sepolia compared to local. Expect more than 10 seconds to deploy the contract and request a signature.

## Gas Optimization

Contract before [this commit](https://github.com/sig-net/mpc/pull/58/commits/b4fab2a22195efef8c86dd7e620a130b76d6708c) uses 3.8M unit of gas to respond, which is too high, about 10x of a uniswap swap. It was using the most gas efficient [implementation](https://github.com/witnet/elliptic-curve-solidity) of Secp256k1 curve and strictly follows the verification logic of the NEAR contract.

After [this commit](https://github.com/sig-net/mpc/pull/58/commits/f2308fe3c7352aa0fb6cec6eb868895e6c5bd4ed), the gas cost is reduced to only 53k, this is only 1/7 of a uniswap swap, or only 2.5x of a eth transfer. (Uniswap swap is about 356k gas, eth transfer is about 21k gas). The optimizations are making the contract not the same logic and not the same interface as the NEAR version, described below:
## Deploy on Ethereum Mainnet

1. Use eth precompiled ecrecover instead of library implemented ecrecover. The precompiled ecrecover is considered as native version and use very little gas. However, it doesn't recover a public key, but an address, which you can consider as a hash of the public key. Previously we compare recovered public key with expected public key. Now we verify the recover by comparing the recovered addresswith the expected public key's hash address.

2. Use ecrecover to hack the ECMUL operation. Ethereum has a ECMUL precompiled contract, but it is not for Secp256k1 curve. Based on [this calculation](https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384), we can verify a ECMUL on Secp256k1 curve cheaply by using ecrecover. Note that it is a verify of ECMUL, not ECMUL itself, therefore user has to calculate the ECMUL result and pass to the contract.

After 1, the respond gas cost is reduced to 1.2M. After 2, the respond gas cost is reduced to 260K. Then we can further reduce the respond gas cost by moving the verify ECMUL operation to sign, so sign gas cost increased from 140k to 350k, and respond gas cost reduced to 53k. The sign gas cost, paid by user, is similar to a uniswap swap and the respond gas, which is paid by the mpc node runner to a very cheap level.
1. Get a mainnet ETH wallet with some ETH and a infura (MetaMask Developer) API key. You can also ping Bo for using his keys.
2. Config hardhat with the infura api key and ethereum account secret key, enter as is and it will ask you the key interactively:

There are certain small gas optimizations possible to reduce a little more gas, but at this stage I think it is not a priority.
```bash
npx hardhat vars set INFURA_API_KEY
npx hardhat vars set SEPOLIA_PRIVATE_KEY
npx hardhat vars set MAINNET_PRIVATE_KEY
```

## Upgrade contract
3. Populate the ignition/params.json with admin address:

To upgrade the contract, after change to ChainSignatures.sol, you can use the following command:
```bash
npx hardhat run scripts/upgradeContract.js --network <network>
node scripts/populateParams.js <adminAddress> <deposit required in wei>
```

It will upgrade the implementation contract to the new version and keep the proxy contract address the same. Make sure you always extends the storage and never override it. For more details, please refer to [Writing Upgradeable Contracts](https://docs.openzeppelin.com/upgrades-plugins/writing-upgradeable).
4. Deploy the contract with right public key configured in `params.json`.

## Update the public key

To update the public key, you also need to update the contract. Add a `reinitialize` function to the contract that set the public key to new one, and upgrade the contract as above.
```bash
npx hardhat ignition deploy ignition/modules/chainSignatures.js --parameters ignition/params.json --network mainnet
```
Loading

0 comments on commit f565b57

Please sign in to comment.