Recent events have brought Ethereum multi-signature wallets (a.k.a. “multisigs”) into the spotlight. Twice this year, hackers or general troublemakers have exploited vulnerabilities in the Parity multisig smart contract. Critics have been quick to cite these incidents when suggesting Ethereum can’t work due to a large attack surface, but the reality is much more nuanced.

Grid+ is currently holding a token sale (ending soon) and is stashing its funds in a secure offline setup. When weighing our storage options, we immediately decided against all multisig smart contracts with which we were familiar, specifically those made by: Parity, Ethereum Foundation, ConsenSys, and Gnosis. This is not to say that all these wallets are exploitable (Gnosis’ wallet, for example, has held $200M for over a year without a breach), but the decision came from general prudence regarding complexity. None of these wallets met our needs, but that is not to say such a wallet cannot exist on Ethereum.

As a means of comparison, we can look at Bitcoin’s P2SH-based multisignature scheme, which has zero reported hacks since its first use in 2012. The difference in security indeed comes from a reduced attack surface, though this is largely by necessity in Bitcoin’s constrained Script scripting language.

In this article I will draw parallels between Bitcoin’s multisigs and a proposed Ethereum multisig. I hope this will help guide the discussion toward a common, standardized, simple Ethereum multisig contract.

Bitcoin Multisig Wallets

Before exploring Ethereum, it is instructive to first understand how Bitcoin’s pay to script hash (P2SH) scheme works and how it applies to a multisig scheme. The formal P2SH definition can be found in BIP16 and a 2-of-3 multisig example can be found here. The steps are roughly as follows:

Generate a multisignature address based on a set of public keys and a “threshold” parameter, which is the minimum number of signatures required to trigger a spend. Fund the new multisig address. This will produce a UTXO (unspent transaction output) owned by the multisig address. Create a new, raw offline transaction spending the multisig’s UTXO. Sign the raw transaction with one private key. This will return a hex string. Sign the hex string with another private key. This will return a new hex string. Continue step 5 until the threshold is met (e.g. 3 signatures out of 5). Send the result in a script with the UTXO to spend. The bitcoins will transfer to the desired recipient.

State and Transitions

Let’s analyze the above procedure. First, a UTXO was selected and a raw transaction was formed. This was operated on by one private key. The output was then operated on by another private key and so on. Thus, there are exactly N operations on the raw transaction, where N is the threshold number of signatures required by the multisig wallet.

However, it is important to note that the outcome here is binary: either N signatures are made (and the UTXO is spent), or nothing happens. There is no in-between and there are zero state transitions on-chain (excluding the UTXO spend). It is also important to note that the above multisig wallet can do only one thing — spend UTXOs.

Extending to Ethereum

If the goal is to emulate Bitcoin’s multisig scheme on Ethereum, we can learn a lot from the previous section. We need to create a multisignature scheme with no extraneous functionality and no in-between state. In other words, there can only be two outcomes to our multisig execution: make a transaction or do nothing.

Note that Ethereum’s transactions are account-based rather than UTXO based, so they are more complex. By “make a transaction” I mean sign an arbitrary set of data to a specified address (which can be a contract address) with a specified value (i.e. amount of ether). In Solidity, this is represented as:

destination.call.value(value)(data)

Another important property of Bitcoin multisigs is their instantiation. Once created, multisigs cannot be changed. This means that the owners of the contracts and the threshold parameter are frozen forever. The Ethereum parallel would be to create an immutable state upon instantiation.

To summarize, we want the following attributes in our Ethereum multisig:

Binary outcome — either accept the transaction or fail immediately. Restricted functionality — the wallet can make transactions, but it can’t do anything else. Creation finality — parameters are locked once the wallet is created.

A Simple Proposal

Christian Lundkvist has proposed a multisig scheme that adheres to the above properties: here is his corresponding writeup.

The setup phase looks like this:

require(owners_.length <= 10 && threshold_ <= owners_.length && threshold_ != 0);

address lastAdd = address(0);

for (uint i=0; i<owners_.length; i++) {

require(owners_[i] > lastAdd);

isOwner[owners_[i]] = true;

lastAdd = owners_[i];

}

ownersArr = owners_;

threshold = threshold_;

This ensures owners are input in a sorted order and publishes them to the contract state. This is the only time ownersArr and threshold can be changed.

Once parameterized, the contract can be used for one and only one purpose: to execute a transaction (defined by to , value , and data parameters). This execution looks like this:

require(sigR.length == threshold);

require(sigR.length == sigS.length && sigR.length == sigV.length); // Follows ERC191 signature scheme

bytes32 txHash = keccak256(byte(0x19), byte(0), this, destination, value, data, nonce); address lastAdd = address(0); // cannot have address(0) as an owner for (uint i = 0; i < threshold; i++) {

address recovered = ecrecover(txHash, sigV[i], sigR[i], sigS[i]);

require(recovered > lastAdd && isOwner[recovered]);

lastAdd = recovered;

}

// If we make it here all signatures are accounted for

nonce = nonce + 1;

require(destination.call.value(value)(data));

This checks that threshold signatures* are provided and forms a hash** based on transaction parameters. It then goes through each of the signatures and verifies that it was made by an owner that evaluates to greater than the previous owner. Note that the comparison is somewhat arbitrary — it is a simple hex string comparison. This constraint further restricts the attack surface, but may not be necessary.

*Signatures here are represented as r , s , and v — these are generated from an elliptic curve signature of a hash; read more here.

**The hash here adheres to the proposed ERC191 standard.

If all checks pass, the provided parameters ( to , value , and data ) are used to make the transaction call within the contract. A nonce is incremented to prevent replay attacks. Note that this is the only state change that occurs. In theory, this contract’s attack surface is constrained to this nonce value incrementing, which is trivially small.

Ethereum Advantages

This scheme is exactly what the Grid+ team was looking for. At 43 lines of code, it is about as simple as a multisig contract can be. It has one and only one function, which is to create a transaction on Ethereum. Because any arbitrarily complex function call can be executed, this multisig functions exactly as a standard wallet, but requires multiple signatures.

Although this wallet is pending audits by ConsenSys Diligence and possibly formal verification by Phil Daian, I would like to propose that the community get comfortable with the simplicity of this implementation and approach a standard multisig scheme. Christian has a repo and I have started my own to further test the contract. Furthermore, I have published the code to EPM (the Ethereum package manager — note this is in beta!): package here, code here.

Multisignature wallets should be the de-facto standard for storing large amounts of cryptocurrency. They should be as safe and user friendly as possible so that they can become common tools used even by individuals who want to secure their funds against a single point of failure. Let’s make a standard that people won’t think twice about using.

— — — —

If you liked this article, follow Grid+ on twitter, join our slack, and sign up for our mailing list on gridplus.io!