Create a multi sig (signature) wallet in Solidity. Most cryptocurrency wallets need one signature (private key) to sign and process a transaction. A multi sig (signature) wallets requires one or more signatures to sign a transaction for processing. These wallets can mitigate the risk of losing your private key or sending a transaction by accident.
Multi Sig wallet security
Multi signature crypto currency wallets work in a similar way to a bank vault. They require more then one person to unlock the door to access the funds. Similarly owners can configure the minimum number of keys needed to unlock a wallet. As an example you could have a 2-of-3 multi sig wallet configuration. A 2-of-3 wallet configuration requires two out of three private keys to send a transaction.
Crypto exchanges, brokers, investment funds and other crypto companies use multi sig wallets to secure their funds and diversify their risk of loss. As an example if hackers want to access a companies reserves, they are going to need several keys to sign a transaction to move money. Multi sig wallets ensures no one person is able to unilaterally withdraw funds from an account. The more signatures required to execute a transaction, the more distributed the decision-making process can be.
Multi Sig wallet Solidity smart contract
Below is an example of a multi sig wallet written in the solidity smart contract language. The example below is for educational purposes only. This smart contract has not had a security audit so do not use in any production capacity.
Read the comments in the code to understand how the process of a multi sig wallet works.
pragma solidity ^0.4.0;
contract MultiSigWallet {
address private _owner;
//create a mapping so other addresses can interact with this wallet. Uint8 is used to determine is the address enabled or disabled
mapping(address => uint8) private _owners;
//this is the number of signatures we need to sign the contract
//Since this is a constant we give it a value of 2 (2 people to sign the transaction).
uint constant MIN_SIGNATURES = 2;
//A dynamic uint called _transactionIdx that increments
uint private _transactionIdx;
//create a struct to represent a transaction that is submitted for others to approve.
//we need to capture how many people signed the Transaction
//we need to keep track of who signed (which accounts) the transition
struct Transaction {
address from;
address to;
uint amount;
uint8 signatureCount;
mapping (address => uint8) signatures;
}
//This is a mapping of transaction ID to a transaction.
//you can not call a map to get a list of pending Transactions so we need to create an array below
// this is private and we are calling this map _transactions
mapping (uint => Transaction) private _transactions;
//create a dynamic array called _pendingTransactions
//this will contain the list of pending transactions that need to be processed
uint[] private _pendingTransactions;
//in order to interact with the wallet you need to be the owner so added a require statement then execute the function _;
modifier isOwner() {
require(msg.sender == _owner);
_;
}
//require the msg.sender/the owner OR || Or an owner with a 1 which means an enabled owner
modifier validOwner() {
require(msg.sender == _owner || _owners[msg.sender] == 1);
_;
}
event DepositFunds(address from, uint amount);
event WithdrawFunds(address from, uint amount);
event TransferFunds(address from, address to, uint amount);
event TransactionSigned(address by, uint transactionId);
//the creator of the contract is the owner of the wallet
function MultiSigWallet()
public {
_owner = msg.sender;
}
//this function is used to add owners of the wallet. Only the isOwner can add addresses. 1 means enabled
function addOwner(address owner)
isOwner
public {
_owners[owner] = 1;
}
//remove an owner from the wallet. 0 means disabled
function removeOwner(address owner)
isOwner
public {
_owners[owner] = 0;
}
//anyone can deposit funds into the wallet and emit an event called depositfunds
function ()
public
payable {
DepositFunds(msg.sender, msg.value);
}
function transferTo(address to, uint amount)
validOwner
public {
//make sure the balance is >= the amount of the transaction
require(address(this).balance >= amount);
//each Transaction needs a transactionId
//system will create a transactionId by adding a number to the last id created (hence the use of ++)
uint transactionId = _transactionIdx++;
//create a transaction using the struct and put in memory
//then add the information to the Transaction in memory
//set the signature count to 0 which means it has not been signed
Transaction memory transaction;
transaction.from = msg.sender;
transaction.to = to;
transaction.amount = amount;
transaction.signatureCount = 0;
//add the transaction to the _transactions data structure (transaction map)
//Transaction ID to the actual transaction
//Add this transaction to the dynamic array using the push mechanism using the transactionId
_transactions[transactionId] = transaction;
_pendingTransactions.push(transactionId);
//create an event that the transaction was created
TransactionCreated(msg.sender, to, amount, transactionId);
}
//get a list of pending transactions
//you need to be an owner
//returns the array of pending transactions
function getPendingTransactions()
validOwner
public
returns (uint[]) {
return _pendingTransactions;
}
//Sign and if meets minimum required signatures then execute the transaction
function signTransaction(uint transactionId)
validOwner
public {
//because the transaction was in "memory" to reference it we use storage
//go to _transactions and get the transactionId and give it the variable name transaction
Transaction storage transaction = _transactions[transactionId];
//Transaction must exist
require(0x0 != transaction.from);
//creator cannot sign the transaction
require(msg.sender != transaction.from);
//cannot sign the transaction more then once
require(transaction.signatures[msg.sender] != 1);
//sign the tranaction
transaction.signatures[msg.sender] = 1;
//increment the signatureCount by 1
transaction.signatureCount++;
//emit an event
TransactionSigned(msg.sender, transactionId);
//if the transaction has a signature count >= the minimum signatures we can process the transaction
//then we need to validate the transaction
If (transaction.signatureCount >= MIN_SIGNATURES) {
//check balance
require(address(this).balance >= transaction.amount);
transaction.to.transfer(transaction.amount);
//emit an event
transactionCompleted(transaction.from, transaction.to, transaction.amount, transaction.Id);
//delete the transaction id
deleteTransaction(transactionId);
}
function deleteTransaction(uint transactionId)
validOwner
public {
//to delete from a dynamic array we need to delete the element from array and reshuffle the array
uint8 replace = 0;
for(uint i = 0; i < _pendingTransactions[i];
//find the transaction
_pendingTransactions[i-1] = _pendingTransactions[i];
} elseif (transactionId == _pendingTransactions[i]) {
replace = 1;
}
}
//delete the final element in the array
delete _pendingTransactions[_pendingTransactions.length -1];
//and decrement the array by 1
_pendingTransactions.length--;
//now delete the transaction from the map
delete _transactions[transactionId];
}
//
function walletBalance()
constant
public
returns (uint) {
return address(this).balance;
}
}
Try it in Remix
This code is for learning and entertainment purposes only. The code has not been audited and use at your own risk. Remember smart contracts are experimental and could contain bugs.
Click here for more information about how to use the Ethereum test network and how to obtain test ETH.
1 thought on “Create a multi sig (signature) wallet in Solidity”