Bitcoiner Guide to Ethereum Multisig

Ilya Evdokimov
8 min readApr 17, 2019
Image by Carmen Kong https://flic.kr/p/3WYY3

Trigger Alert: true bitcoiner very likely will be disappointed by some parts of this post just because all things we are discussing here are already solved in Bitcoin on a more convenient, robust, trustless and safe manner. The whole purpose of the work is to develop additional security on the top level exclusively, for the people responsible for fund management and financial decisions. I do not promote Ethereum. Moreover, it is not good at all in my point of view, but all I need someday was proper documentation and working pieces of code. I couldn’t find them, so I decided to write this post. Don’t be angry at me. Even Jameson Lopp had talked on BitGo’s Ethereum multisignature wallet.

To Ethereum fans: I’m very sorry, I do not call Ethereum contracts “smart contracts” because I’m convinced this definition is wildly misinterpreted.

Running Ethereum Node

It is reasonably hard to synchronize the Ethereum node on time. I mean, if you have a well-defined timeline for your project, you must care about spare time exclusively for installing a suitable client and finding working hardware configuration. There are two working clients and several mythical alternative implementations of the Ethereum protocol enumerated by Andreas M. Antonopoulos and Dr. Gavin Wood in their book “Mastering Ethereum” (referring “ME” next time).

Current hardware setup (no matter if it is an AWS instance or bare-metal) for Ethereum Archival node is Core i7, 32GB RAM, 4TB SSD, and Full node demands less space, but it must be SSD. This configuration is derived from various sources, including go-ethereum github issues page. The problem is that even if you have a powerful computer and good Internet connection, it doesn’t guarantee enough peers for bootstrapping your node. “ME” also explains validation slowdown at certain block heights due to past DDOS attacks on Ethereum network.

In my case, I failed to sync mainnet and testnet nodes on time (two weeks or so), both in fast and light mode (March 2019, still experimental). Finally, I started to use Infura. If the reader has more time for his project, he can prepare the Ethereum node for the work. I just pushed trust minimization further when main work would be completed and the only thing left to do is to replace Infura API calls with sovereign full node RPC. I had two small Ethereum projects with an about 6-month interval in between, and both projects ended up with using Infura. First one was Golang Ethereum wallet capable of preparing transactions online and sign offline with an interface suited slightly better than MyEtherWallet for separate signing procedure and additional Shamir Secret Sharing Scheme keystore encryption for kind of multisignature signing. Current (second) project is about implementing multisig at service level when one signature is always in the vault, and the process is semi-automated.

Ethereum Multisig Contracts
Choosing a Contract

There are several open source contracts developed by various companies:
1. BitGo

2. Unchained Capital

3. Gnosis

Unchained Capital explains very well their motivation behind developing a new contract in their blog. I chose their contract for two reasons:

1. Simplicity
2. Python scripts for operation in the command line are available

MultiSig contract by Unchained is simple and small. These properties ensure lower operational cost and transform the real-life task into a kind of regular tutorial for beginners “How to issue a new token on Ethereum blockchain” alike.

Available Python scripts allow not only obvious code reuse but the additional self-explanation about how things work without any noisy graphical interfaces. I’m not a JavaScript developer, and I experienced significant failure when I tried to compile all contracts using ‘npm’ manager. Besides, I was pretty scared by the number of external libraries involved in the build process (more than 1000).

Deploying Contracts On Ethereum

Schematic workflow for contract deployment (useful source)

At the very first stage, we have a file with *.sol extension which means that we have a program written in Solidity. It is crucial to determine the correct Solidity version because we need to compile *.sol file before deployment on Ethereum blockchain. For using Unchained MultiSigt contract we need precisely v0.4.24.

The reader is free to try several recipes for installing solc compiler. For me, the only way to get things work was installing binaries using python module.

I’ve used command:

python -m solc.install v0.4.24

And then only assign the full solc path to alias. The Python module solc theoretically allows to dynamically compile code on the fly:

from solc import compile_files
contracts = compile_files([‘some_contract.sol’])

But building contract in python scripts wasn’t working for me, and I quickly figured out that wrapper only calls solc via command line and reads stdout stream (how smart!). Everything we need to prepare a contract before deployment could be accomplished via a single command in bash:

solc --bin --abi --output-dir=./build ./contracts/MultiSig2of3.sol

This command saves one text file with Application Binary Interface and one text file with *.bin extension which contains hex-encoded string representing byte-code to run on the Ethereum Virtual Machine (you can also read about it here). The idea that *.bin file isn’t a binary file actually cost me couple of hours of work.

For the memo: I wasn’t able to compile solc from sources because broken dependency inside ‘make’ step after successful ‘cmake’ run actually stopped me from trying to compile it. The dependency was a json-library and I considered all process as a bad software engineering practice.

Working with Multisig via Python Scripts

1. Setting Up Environment

Luckily, it is possible to manage the whole process of deploying and working with chosen multisig contract via command line and Python scripts. It helps us to understand the nuances of the whole scheme depicted above.

To start working in Python we need a proper environment.

make python-dependencies

It creates .virtualenv subdirectory in the project root. The environment peis usually activated via

source .virtualenv/bin/activate

Now we are ready to use scripts for 1) contract creation, 2) depositing funds onto contract from Ethereum Externally Owned Account for demonstration of the multisig capabilities, 3) generating an unsigned transaction, 4) separate message signing, 5) calling contract function, assembling, serializing and sending transaction to the network.

A simple script my_eth_util.py contains a function to create and set up the client wrapper.

#!/usr/bin/env python
from web3 import Web3, HTTPProvider, middleware
from web3.gas_strategies.time_based import medium_gas_price_strategy


all_ethereum_networks = ['local', 'mainnet', 'ropsten', 'rinkeby']


def create_node(network_type=0):
network = all_ethereum_networks[network_type]
if network == 'local':
LOCAL_NODE = 'http://localhost:8545'
else:
LOCAL_NODE = 'https://{}.infura.io/v3/af13ffcce8c64f6796185cc860609fd0'.format(network)
print("Creating w3 instance using {}".format(LOCAL_NODE))
w3 = Web3(HTTPProvider(LOCAL_NODE, request_kwargs={'timeout': 60}))
print("Chain ID {}, latest block {}".format(w3.net.chainId, w3.eth.blockNumber))
w3.eth.setGasPriceStrategy(medium_gas_price_strategy)
print("Caching gas price info...")
w3.middleware_stack.add(middleware.time_based_cache_middleware)
w3.middleware_stack.add(middleware.latest_block_based_cache_middleware)
w3.middleware_stack.add(middleware.simple_cache_middleware)
return w3

2. Creating a Contract

Before setting up our first multisig contract we shall create three keystore files and obtain three addresses of Ethereum EOA which can generate valid signatures of the multisig contract messages. The reader shall do it via geth or parity clients or at least use one of the available wallets. It is also possible to create accounts via web3-python. On the next steps, we assume that we already have theree independent accounts for multisig. In the original Unchained contract demo, the user completes signing via 3 Ledgers.

The Ethereum network doesn’t know about our contract while we don’t deploy it and for deployment we need an EOA with non-zero ETH balance. Script create_ethereum_multisig consumes:

  1. ABI file ../build/MultiSig2of3.abi
  2. File with hex-encoded Ethereum EVM byte string
    ../build/MultiSig2of3.bin
  3. File with deployment account keystore
    ~/.ethereum/testnet/keystore/UTC — 2019–03–20T11–03–41.110235724Z — 742a44df2e86eaf6f595dda3f108a501344149f0
  4. Three MultiSig controlling addresses:
    0x742a44df2e86eaf6f595dda3f108a501344149f0 0xbb06c14e5d851b67f3567c0d668ca0ccd7253c78 0x42339ed7a9f1b3da32f089e9a643727bf657fe23

The deployment account should have enough ETH tokes to send contract creation transaction onchain. Actual gas price and nonce for deployment account 0x742a44df2e86eaf6f595dda3f108a501344149f0 are used in contract creation transaction.

contract_tx = {‘from’: w3_keys.eth.defaultAccount, ‘gas’: 1300000, ‘gasPrice’: int(gas_price), ‘nonce’: nonce}

Three multisig addresses are passed into contract constructor and the final deployment transaction is prepared via buildTransaction method. The same method will be used when we will spend MultiSig contract funds.
Successful execution of the contract creation script results in pulling transaction receipt to the terminal where you can find the onchain contract address, like this 0x38f2fa996576430D6Bb89E9Cd7fBE9bFbA084613, ready to deposit funds. Otherwise, you should copy the transaction hash and paste it in blockchain explorer. When contract creation transaction goes into block, the contract address can be copied from explorer web-page manually.

3. Depositing Funds

We can use any wallet to send funds to our contract 0x38f2fa996576430D6Bb89E9Cd7fBE9bFbA084613. However, I developed yet another script just for exercise.
The script deposit_ethereum_multisig consumes:

  1. Original EOA keystore file with funds. It will be used as a sender ~/.ethereum/testnet/keystore/UTC — 2019–03–20T11–03–41.110235724Z — 742a44df2e86eaf6f595dda3f108a501344149f0
  2. Amount in ETH, for example “0.2
  3. Contract address or recipient of our ETH token transaction. In our example 0x38f2fa996576430D6Bb89E9Cd7fBE9bFbA084613
    After contract balance become non zero, we can spend from it.

4. Spending Funds

Preparing unsigned contract transactions is relatively easy.

At the first step, the user should execute ethereum_multisig_spend_unsigned_data with contract address, destination address and amount. The script returns a message from our contract which should be signed separately by any 2 of 3 MultiSig EOA.

On the next step, a very simple script opens keystore and utilizes standard function signHash. For example, we copied from terminal contract message

400ea3fa4e049c8327ad38b6148b1f4ff7b303c0bc797ea27258b12fc46c061b

, then we execute twice script sign_multisig_with_keystore with arguments:

  1. message ,
  2. path to one of the keystores containing keys enabled to spend from out contract (keystores must be different).

Otherwords, these keystores are backing addresses we sent into contract creation function before deployment. The script outputs dictionary containing one important field ‘signature’ which user need to proceed for the sending funds from contract.

At the end of this stage we collected two signatures from two different MultiSig accounts, for example:

0xb9228ec2c0a03f939c03ded8ea39ddc0938f4ca73651946ab028b9043c499a66119656e1404ac374b7c2b76a4f6e1cfd669cdd3f1b0413ec4d5cff2c6d8380561c0xe575c3cde86f1772034d50e2f65fbd2a2fb5e2e3bed4d05c2236bbfffecf441c21f5813160ce8f383f217f4de646eeec3f21448aee87d13168f8e44422dbe6751c

5. Signing and Broadcasting Transaction

Finally, spend_ethereum_multisig needs all collected signatures to work correctly since it demands Unchained capital contract function ‘spend’. One can open the file contracts/MultiSig2of3.sol and see what arguments are consumed by ‘spend’ function of the contract.

function spend(
address destination,
uint256 value,
uint8 v1,
bytes32 r1,
bytes32 s1,
uint8 v2,
bytes32 r2,
bytes32 s2
)

So, spend_ethereum_multisig consumes:

  1. Path to account keystore which will pay for contract transaction,
  2. Contract address
  3. Destination address
  4. Amount
  5. The first signature without leading “0x”
  6. The second signature without leading “0x”

I’m sorry, I was lazy and didn’t modify signature parsing function of the original Unchained script for throwing out first symbols “0x” of the signatures indicating about hex-encoding (see explanation here). After a couple of minutes one can observe successful execution of the contract function ‘send’ in the explorer. The raw transaction signing process also explained well here.

Conclusion

I used probably the smallest contract which is easy to manage and has lower transaction costs. My experience is quite similar to what Jameson Lopp said about in his presentation: working with multisignature schemes will be quite expensive on Ethereum blockchain.

I successfully avoided working with Graphical User Interfaces in my tutorial. Also, all basic functions were executed without NodeJS environment which I was not able to install it without problems in any project I intended to look at, including several multisig demos. I’m happy I was able to avoid using npm in my workflow.

My local Ethereum community was unhelpful when I ask several people in the group chat about handling multisig contracts. They laugh at me saying that it is unreliable in real life. However, in my opinion, multisignature spending schemes are critical for almost any business logic.

--

--