Skip to content

Commit 01fde9d

Browse files
melancianijatZama
andauthored
docs: add runbook for confidential transfer with multisig (#29)
* docs: add runbook for confidential transfer with multisig * feat: new generalized helper contract * feat: updated helper contract implementation * feat: allow both PRIVATE_KEY and MNEMONIC as env vars, PRIVATE_KEY taking precedence * chore: update env variables names * feat: update runbook for Safe and Aragon, and quick workaround * docs: replace wrapper by confidential token terminology * docs: minor improvement * chore: fix indentation in helper contract * chore: add prettier formatting for the helper contract * chore: redeploy helper contracts and improved docs * docs: minor improvement, advising against second transfer method --------- Co-authored-by: jat <153528475+jatZama@users.noreply.github.com> Co-authored-by: jatZama <josephandre.turk@zama.ai>
1 parent aa530a2 commit 01fde9d

File tree

15 files changed

+1082
-460
lines changed

15 files changed

+1082
-460
lines changed

.prettierrc.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ overrides:
1212
- files: "*.sol"
1313
options:
1414
compiler: "0.8.24"
15-
parser: "solidity-parse"
1615
tabWidth: 4
1716
- files: "*.md"
1817
options:

contracts/fhevm-cli/.env.example

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# At least one of PRIVATE_KEY or MNEMONIC must be set.
2+
# PRIVATE_KEY takes precedence if both are provided.
13
PRIVATE_KEY=
2-
MAINNET_RPC_URL=
3-
TESTNET_RPC_URL=
4+
MNEMONIC=
5+
MAINNET_ETHEREUM_RPC_URL=
6+
TESTNET_ETHEREUM_RPC_URL=
7+
ETHERSCAN_API_KEY=

contracts/fhevm-cli/.prettierrc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"plugins": ["prettier-plugin-solidity"],
3+
"overrides": [
4+
{
5+
"files": "*.sol",
6+
"options": {
7+
"printWidth": 120,
8+
"tabWidth": 4
9+
}
10+
}
11+
]
12+
}

contracts/fhevm-cli/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ npx hardhat vars set ZAMA_FHEVM_API_KEY <your-api-key>
2020

2121
# Request a user decryption
2222

23-
Before running this command, ensure you have filled needed environement variables values inside your `.env` file: i.e `PRIVATE_KEY` for the user's private key requesting the user decryption, as well as `MAINNET_RPC_URL` (for mainnet) or `TESTNET_RPC_URL` (for testnet).
23+
Before running this command, ensure you have filled needed environement variables values inside your `.env` file: i.e `PRIVATE_KEY` for the user's private key (or alternatively, `MNEMONIC`) requesting the user decryption, as well as `MAINNET_ETHEREUM_RPC_URL` (for mainnet) or `TESTNET_ETHEREUM_RPC_URL` (for testnet).
2424

2525
To request a user decryption, use a command similar to this example:
2626

@@ -32,7 +32,7 @@ Replace the `handle` flag value from previous command by a handle you are allowe
3232

3333
# Request an encryption
3434

35-
Before running this command, ensure you have filled needed environement variables values inside your `.env` file: i.e just `MAINNET_RPC_URL` (for mainnet) or `TESTNET_RPC_URL` (for testnet).
35+
Before running this command, ensure you have filled needed environement variables values inside your `.env` file: i.e just `MAINNET_ETHEREUM_RPC_URL` (for mainnet) or `TESTNET_ETHEREUM_RPC_URL` (for testnet).
3636

3737
To request an input encryption, use a command similar to this example:
3838

@@ -44,7 +44,7 @@ Replace the `input-value` flag value from previous command by the custom value y
4444

4545
# Request a public decryption
4646

47-
Before running this command, ensure you have filled needed environement variables values inside your `.env` file: i.e just `MAINNET_RPC_URL` (for mainnet) or `TESTNET_RPC_URL` (for testnet).
47+
Before running this command, ensure you have filled needed environement variables values inside your `.env` file: i.e just `MAINNET_ETHEREUM_RPC_URL` (for mainnet) or `TESTNET_ETHEREUM_RPC_URL` (for testnet).
4848

4949
To request a public decryption, use a command similar to this example:
5050

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// SPDX-License-Identifier: BSD-3-Clause-Clear
2+
pragma solidity ^0.8.24;
3+
4+
import "@fhevm/solidity/lib/Impl.sol";
5+
import "@fhevm/solidity/config/ZamaConfig.sol";
6+
7+
interface ISafe {
8+
function getOwners() external view returns (address[] memory);
9+
}
10+
11+
/// Helper contract to facilitate usage of fhevm with multisig accounts
12+
contract FHEVMMultiSigHelper {
13+
/// @notice Returned if the list of input handles is empty
14+
error EmptyInputHandles();
15+
16+
/// @notice Returned if the list of owners is empty
17+
error EmptyOwners();
18+
19+
/// @notice Returned if the multisig address is null
20+
error NullMultisig();
21+
22+
/// @notice Returned if one of the handles is null
23+
error UninitializedHandle();
24+
25+
constructor() {
26+
FHE.setCoprocessor(ZamaConfig.getEthereumCoprocessorConfig());
27+
}
28+
29+
/// @notice Helper method to allow a list of handles to the owners of a Safe multisig contract and to the Safe itself
30+
/// @param safeMultisig The Safe account address
31+
/// @param inputHandles The list of initialized handles that we want to allow to the Safe and its owners, must be non-empty
32+
/// @param inputProof The input proof corresponding to the list of inputHandles, must be non-empty
33+
/// @dev It is possible to use this method with any (initialized, non-null) handle type
34+
function allowForSafeMultiSig(
35+
address safeMultisig,
36+
bytes32[] memory inputHandles,
37+
bytes memory inputProof
38+
) external {
39+
address[] memory owners = ISafe(safeMultisig).getOwners();
40+
uint256 numOwners = owners.length;
41+
_allowHandlesForMultiSigAndOwners(safeMultisig, inputHandles, inputProof, owners, numOwners);
42+
}
43+
44+
/// @notice More general helper method to allow a list of handles to a custom list of owners and a multisig,
45+
/// @notice but requires the user to manually input correct list of owners as argument
46+
/// @notice WARNING: the user is responsible to enter the correct list of owners corresponding to the multisig
47+
/// @notice Can also be used with non-Safe multisigs (eg Aragon, CoinbaseSmartWallet, etc)
48+
/// @param multisig The multisig account address (could be a non-Safe account)
49+
/// @param owners The list of owners, must be non-empty
50+
/// @param inputHandles The list of initialized handles that we want to allow to the owners, must be non-empty
51+
/// @param inputProof The input proof corresponding to the list of inputHandles, must be non-empty
52+
/// @dev It is possible to use this same function with any (initialized, non-null) handle type
53+
function allowForCustomMultiSigOwners(
54+
address multisig,
55+
address[] memory owners,
56+
bytes32[] memory inputHandles,
57+
bytes memory inputProof
58+
) external {
59+
uint256 numOwners = owners.length;
60+
if (numOwners == 0) revert EmptyOwners();
61+
_allowHandlesForMultiSigAndOwners(multisig, inputHandles, inputProof, owners, numOwners);
62+
}
63+
64+
/// @notice Internal function to allow list of input handles to the multisig account and its owners
65+
/// @param multisig The multisig account address (could be a non-Safe account)
66+
/// @param inputHandles The list of initialized handles that we want to allow to the owners, must be non-empty
67+
/// @param inputProof The input proof corresponding to the list of inputHandles, must be non-empty
68+
/// @param owners The list of owners, must be non-empty
69+
/// @param numOwners The number of owners
70+
function _allowHandlesForMultiSigAndOwners(
71+
address multisig,
72+
bytes32[] memory inputHandles,
73+
bytes memory inputProof,
74+
address[] memory owners,
75+
uint256 numOwners
76+
) internal {
77+
if (multisig == address(0)) revert NullMultisig();
78+
uint256 inputHandlesLength = inputHandles.length;
79+
if (inputHandlesLength == 0) revert EmptyInputHandles();
80+
for (uint256 idxHandle = 0; idxHandle < inputHandlesLength; idxHandle++) {
81+
bytes32 inputHandle = inputHandles[idxHandle];
82+
if (inputHandle == bytes32(0)) revert UninitializedHandle();
83+
Impl.verify(inputHandle, inputProof, FheType(uint8(inputHandle[30])));
84+
Impl.allow(inputHandle, multisig);
85+
for (uint256 idxOwner; idxOwner < numOwners; idxOwner++) {
86+
Impl.allow(inputHandle, owners[idxOwner]);
87+
}
88+
}
89+
}
90+
}
Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,35 @@
11
import "@fhevm/hardhat-plugin";
22
import "@nomicfoundation/hardhat-chai-matchers";
33
import "@nomicfoundation/hardhat-ethers";
4-
import "dotenv/config";
4+
import "@nomicfoundation/hardhat-verify";
5+
import dotenv from "dotenv";
56
import { HardhatUserConfig } from "hardhat/config";
7+
import { HttpNetworkAccountsUserConfig } from "hardhat/types/config";
8+
import { resolve } from "path";
69

10+
import "./tasks/allow";
11+
import "./tasks/deployment/multisig";
12+
import "./tasks/deployment/verify";
713
import "./tasks/encrypt";
814
import "./tasks/publicDecrypt";
915
import "./tasks/userDecrypt";
1016

17+
const NUM_ACCOUNTS = 10;
18+
19+
const dotenvConfigPath: string = process.env.DOTENV_CONFIG_PATH || "./.env";
20+
dotenv.config({ path: resolve(__dirname, dotenvConfigPath) });
21+
22+
const privateKey = process.env.PRIVATE_KEY;
23+
24+
const mnemonic = process.env.MNEMONIC;
25+
if (!privateKey && !mnemonic) {
26+
throw new Error("Please set either PRIVATE_KEY or MNEMONIC in your .env file");
27+
}
28+
29+
const accounts: HttpNetworkAccountsUserConfig = privateKey
30+
? [privateKey]
31+
: { count: NUM_ACCOUNTS, mnemonic: mnemonic!, path: "m/44'/60'/0'/0" };
32+
1133
const config: HardhatUserConfig = {
1234
solidity: {
1335
version: "0.8.28",
@@ -18,15 +40,20 @@ const config: HardhatUserConfig = {
1840
networks: {
1941
// ChainID must be specified in order to be able to verify contracts using the fhevm hardhat plugin
2042
mainnet: {
21-
url: process.env.MAINNET_RPC_URL || "",
43+
url: process.env.MAINNET_ETHEREUM_RPC_URL || "",
2244
chainId: 1,
45+
accounts,
2346
},
2447
// ChainID must be specified in order to be able to verify contracts using the fhevm hardhat plugin
2548
testnet: {
26-
url: process.env.TESTNET_RPC_URL || "",
49+
url: process.env.TESTNET_ETHEREUM_RPC_URL || "",
2750
chainId: 11155111,
51+
accounts,
2852
},
2953
},
54+
etherscan: {
55+
apiKey: process.env.ETHERSCAN_API_KEY!,
56+
},
3057
};
3158

3259
export default config;

0 commit comments

Comments
 (0)