Christian Lundkvist's SimpleMultiSig: an m-of-n wallet that holds no pending-transaction state because every signature is collected off-chain and submitted in one call.
Historical Significance
Most multisig wallets of this period kept every proposed transaction on chain and charged each owner a transaction to confirm it. This design moves all of that off chain and pays for exactly one transaction, which made it markedly cheaper to operate and removed a large amount of contract state that could go wrong. The strictly-increasing-address trick, used in both the constructor and the signature loop, is the small idea that makes the whole thing fit in so little code.
Key Facts
Description
The owner set and the threshold are fixed in the constructor and never change. Owners must be supplied in strictly increasing address order, which lets the constructor reject duplicates with a single comparison per entry instead of a nested scan.
execute takes three parallel arrays holding the v, r and s of each signature, plus the destination, value and calldata. It rebuilds the transaction hash from the contract's own address, the call parameters and the current nonce, following the ERC-191 prefix scheme, then recovers each signer in turn. The recovered addresses must also be strictly increasing, which is what stops the same owner's signature being counted twice. Only after every signature checks out does the nonce advance and the call go out.
The result is a multisig with no proposal storage at all. There is no submit step, no confirmation step and no transaction registry; the only thing in mutable storage is the nonce.
Source Verified
Heuristic Analysis
The following characteristics were detected through bytecode analysis and may not be accurate.
Byzantium Era
First Metropolis hard fork. Added zk-SNARK precompiles, REVERT opcode, and staticcall.
Bytecode Overview
Verified Source Available
This contract has verified source code.
View Verification ProofShow source code (Solidity)
// Submitted by EthereumHistory (ethereumhistory.com)
pragma solidity ^0.4.15;
contract SimpleMultiSig {
uint public nonce; // (only) mutable state
uint public threshold; // immutable state
mapping (address => bool) isOwner; // immutable state
address[] public ownersArr; // immutable state
function SimpleMultiSig(uint threshold_, address[] owners_) public {
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_;
}
// Note that address recovered from signatures must be strictly increasing
function execute(uint8[] sigV, bytes32[] sigR, bytes32[] sigS, address destination, uint value, bytes data) public {
require(sigR.length == threshold);
require(sigR.length == sigS.length && sigR.length == sigV.length);
// Follows ERC191 signature scheme: https://github.com/ethereum/EIPs/issues/191
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));
}
function () public payable {}
}External Links
Related contracts
SimpleMultiSig
Same deployerChristian Lundkvist's SimpleMultiSig: an m-of-n wallet that holds no pending-transaction state because every signature is collected off-chain and submitted in one call.
0x859e02...b2995dSeptember 17, 2018Msg
Same eraMinimal contract that stores a single public string in state, exposed via the auto-generated m() getter. First of 281 identical siblings. Source verified by EthereumHistory.
0x2c8f58...37b173January 13, 2018Wallet
Same eraToken collection wallet that moves an entire token balance to a chosen address, without the ERC223 callback handler.
0x002204...531195January 19, 2018Wallet
Same eraOwner wallet that forwards ether to its owner, logs any call from a stranger with the full calldata, and lets the owner execute arbitrary calls.
0x001feb...9e7a4fJanuary 20, 2018Forwarder
Same eraNinety-three bytes with one hardcoded destination: every payment received is transferred straight back out to the same address.
0x458353...a562deJanuary 20, 2018Sweeper
Same eraMinimal ETH sweeper, first of 1,900 identical deployments in a 2-day burst in January 2018. All sweep to a single hardcoded destination. Source verified by EthereumHistory.
0xeb15f6...6cb3e1January 23, 2018