Ethereum 2/3-multisig smart contract & dApp for cold storage, hardware wallets.
This project is no longer supported or upkept by Unchained Capital. Please use this project at your own risk.
This repository implements an Ethereum smart contract
(Multisig2of3
) which requires signed messages from 2/3 members
of a multisig cold wallet with individual keys stored on
Trezor or Ledger hardware wallets.
This repository also contains a simple dApp which lets you create and spend from an instance of the smart contract using your own quorum of Trezors. You can run the dApp locally or via our hosted copy at https://ethereum-multisig.unchained-capital.com.
Command-line scripts are also provided (see the scripts
directory)
for developers who want to create and spend from the smart contract
programatically.
The smart contract is fully unit tested.
For complete details on why we wrote this contract, please read our blog post.
In short, we needed an Ethereum multisig smart contract with the following properties:
In Bitcoin, all these requirements are trivially met by the standard implementation of a 2/3 P2SH address. Ethereum suffers from an embarassment of riches and we could not find a single smart contract implementation that we were happy with. So we wrote this.
The MultiSig2of3 contract was audited by Hosho.
We have included the
full audit report.
The audit was of the contract at commit f40143e00a378addfc5559ff743f1c8a7ca7fae3
with a fingerprint of 4F436F84AB192BB664AEE206EB7ED80138481B46C9BBA7EC5C70C2774752CEDF
.
The simplest way to interact with the smart contract is through the dApp bundled with this repository. The dApp assumes you have a locally running Ethereum client (Parity, MetaMask, Geth, &c.)
You can access a hosted version of the dApp at https://ethereum-multisig.unchained-capital.com. Depending on which Ethereum node you are using, you may have to do some additional configuration before the dApp can communicate:
--jsonrpc-cors https://ethereum-multisig.unchained-capital.com
when starting Parity--rpccorsdomain https://ethereum-multisig.unchained-capital.com
when starting geth.To access the dApp locally, download or clone this repository.
Once downloaded you can use access the dApp in two ways:
public/index.html
inside
this repository. This option is easier but somewhat less secure.Which you choose will depend on which Ethereum node you are using and how you've configured it:
public/index.html
you will need to set --jsonrpc-cors null
(this is why this option is less secure).https://localhost:8435
you will need to set --jsonrpc-cors https://localhost:8435
.public/index.html
you will need to set --rpccorsdomain null
(this is why this option is less secure).https://localhost:8435
you will need to set --rpccorsdomain https://localhost:8435
.This repository also comes with a simple webserver which provides a
more secure alternative to directly browsing to the
public/index.html
file. (This is required for MetaMask and Ledger).
To launch the local webserver you will need NPM to be installed locally as well as the make program.
To install dependencies and launch the server, open a shell in this repository's directory and run:
$ make dependencies
$ make server
Now browse to https://localhost:8435 to see the dApp. You will have to accept an insecure connection warning.
If you want to develop against the smart contract or the DAPP you'll need:
You may also have to install some system dependencies:
brew install libusb leveldb
apt install python3-dev libusb-dev
To install all development dependencies, open a shell in this repository's directory and run:
$ make dependencies-all
If you aren't using the python scripts or mythril, you can skip the python dependencies by running:
$ make js-dependencies-all
Once dependencies are installed, you can compile the contract.
$ make contract
and run its unit tests
$ make test
We are using the Truffle framework so
truffle
commands are available. To make them easier to run, you
should update your PATH
variable or set a shell alias. You can also just run
$ source environment.sh
to do all of this for you. Now you can run the truffle
commands, e.g. -
$ truffle compile
We are using the Mythril tool for static analysis. Once dependencies are installed, you can run:
$ make myth
to run a check. You can also run the myth
program directly
(assuming you have run source environment.sh
).
We use Solium for linting and more security checks. Run with:
$ make solium
The contract emits the following events:
Funded (uint256 newBalance)
-- whenever the contract receives a new deposit
(topic: 0xc4c14883ae9fd8e26d5d59e3485ed29fd126d781d7e498a4ca5c54c8268e4936
)Spent (address to, uint256 transfer)
-- whenever the contract is spent from
(topic: 0xd3eec71143c45f28685b24760ea218d476917aa0ac0392a55e5304cef40bd2b6
)This repository also comes with the command-line Python and Node scripts for interacting with the smart contract:
scripts/export_ethereum_address
--
exports an Ethereum address from a local Trezor.scripts/ledger/ledger-address.js
--
exports an Ethereum address from a local Ledger.scripts/create_ethereum_multisig
--
creates a new instance of the smart contract.scripts/ethereum_multisig_spend_unsigned_data
--
return the unsigned data used for a spendscripts/export_ethereum_multisig_spend_signature
--
export a signed spend from a local Trezor.scripts/ledger/ledger-sign-message.js
--
export a signed spend from a local Ledger.scripts/spend_ethereum_multisig
-- broadcast a spend transactionOnce dependencies are installed, you will be able to run these scripts.
The following example illustrates how to collectively use these scripts and a single Trezor or Ledger to create an instance of the smart contract and spend from it. Using a single wallet is convenient for testing, but for real usage you would obviously use distinct wallets.
.virtualenv/bin/activate
directly or just source the environment setup script:$ source environment.sh
$ owner_path_1="m/44'/60'/600'/0/0"
$ owner_path_2="m/44'/60'/600'/0/1"
$ owner_path_3="m/44'/60'/600'/0/2"
For Trezor:
$ owner_address_1=$(./scripts/export_ethereum_address $owner_path_1)
$ owner_address_2=$(./scripts/export_ethereum_address $owner_path_2)
$ owner_address_3=$(./scripts/export_ethereum_address $owner_path_3)
For Ledger:
To use ledger with node scripts (assuming you are using the standard ethereum app) you will need to set 'Browser Support' to 'No' in the 'Settings' menu in the Ethereum app on the Ledger device.
$ owner_address_1=$(node scripts/ledger/ledger-address.js $owner_path_1)
$ owner_address_2=$(node scripts/ledger/ledger-address.js $owner_path_2)
$ owner_address_3=$(node scripts/ledger/ledger-address.js $owner_path_3)
$ contract_address=$(./scripts/create_ethereum_multisig $owner_address_1 $owner_address_2 $owner_address_3)
Now you can fund the address using any Ethereum wallet.
To spend from the address, first compute the unsigned message
required for the spend (sending 0.1 ETH to address
0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
in this example):
$ destination_address=0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
$ amount=0.1
$ unsigned_data=$(./scripts/ethereum_multisig_spend_unsigned_data $contract_address $destination_address $amount)
For Trezor:
$ signature_1=$(./scripts/export_ethereum_multisig_spend_signature $unsigned_data $owner_path_1)
$ signature_2=$(./scripts/export_ethereum_multisig_spend_signature $unsigned_data $owner_path_2)
For Ledger:
You can/should preview the message that will displayed:
$ node scripts/ledger/preview-ledger-display.js $unsigned_data
And then sign:
$ signature_1=$(node scripts/ledger/ledger-sign-message.js $owner_path_1 $unsigned_data)
$ signature_2=$(node scripts/ledger/ledger-sign-message.js $owner_path_2 $unsigned_data)
Note: The Ledger signature produced by this script has been altered to match the same format as the Trezor signature. If using the 'manual input' fields in the dapp with this signature, select 'Trezor' from the wallet drop-down.
$ ./scripts/spend_ethereum_multisig $contract_address $destination_address $amount $signature_1 $signature_2
We welcome contributions to this repository either in form of GitHub issues or pull requests.
When filing an issue, please include the context under which you encountered the issue, e.g. -
When contemplating a pull request, please consider fixing just a single bug or implement just a single feature. Before you submit the pull request, ensure you have run the unit tests and run an "integration test" using the dApp locally with a Trezor and/or Ledger.
This application is in “alpha” state and is presented for evaluation and testing only. It is provided “as is,” and any express or implied warranties, including but not limited to the implied warranties of merchantability and fitness for a particular purpose, are disclaimed. By using this application, you accept all risks of such use, including full responsibility for any direct or indirect loss of any kind resulting from the use of this application, which may involve complete loss of any ETH or other coins associated with addresses used with this application. In no event shall Unchained Capital, Inc., its employees and affiliates, or developers of this application be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this application, even if advised of the possibility of such damage.