Multi-signature wallets are smart contracts that require the agreement of multiple people to perform an action. They can be useful for protecting assets (using separation of duties) or to ensure that certain actions are only taken in accordance with the wishes of the multisig’s owner or a majority of owners.
This article focuses on how to make the best design choices when setting up a multisig and how to avoid common mistakes. We’ll walk through demos of several different multisig configurations. Once you’ve set up your multi-signature wallet, you can add it to your application.
Jump ahead:
- Why do we need multisigs?
- How does a multisig work?
- What are some use cases for multisigs?
- Types of multisigs
- Demo: creating a multisig wallet
- Potential problems with multisigs
- Demo: creating a shared multisig
- Warning about smart contract development
- When is a multisig the right solution?
Why do we need multisigs?
There are many cases in which we want actions to be approved by multiple people. Here are a few examples:
Divided ownership
If an asset is owned by multiple people in common, especially an on-chain asset, a smart contract can verify that it is used only in accordance with the wishes of the owners. The blockchain also provides an audit trail that shows which owners approved any action, so it is impossible for owners to later pretend they did not approve.
Separation of duties
Even when an asset is owned by a single entity, multisigs can be useful for implementing separation of duties. When multiple people are required to sign off on an action both fraud and innocent mistakes are a lot less likely. In these cases, the tradeoff is between security (more signers mean you’re safer) and speed (more signers mean it takes longer to do anything).
Audit trail
There are cases where multiple people are allowed to perform an action, and we just want to know who executed the action. By using a multisig that requires only a single signature, we can cover this use case without the security risks associated with a shared account.
How does a multisig work?
Entities on the blockchain, such as a multisig contract, can only directly affect other blockchain entities. The actions that a multisig can control are therefore those that can be accomplished by calling a smart contract, such as transferring ERC-20 tokens or an NFT.
Multisigs have multiple signature addresses that are authorized to perform an action, either individually or when approved by a group of a specific size. Every signature address is a different Ethereum address, typically derived from a different recovery phrase and owned by a different person. Later in this article, we’ll discuss circumstances in which you may want to give a single person control of more than one signer address.
Types of multisigs
Most multisigs implement an M-of-N requirement. This means that there are N total signers, of which M have to approve and sign before the action occurs. This is called an M/N multisig; the ratio of M to N is called the quorum quotient. For example, a 3/5 multisig would have five signers, three of whom would need to agree to or approve an action.
The tradeoffs in setting up the parameters of a multisig come down to tradeoffs between security on one hand and ease and availability on the other.
- The more signers you have (high N) and the fewer signers that are needed (low M), the easier it is to find the necessary people to perform an action
- If you have fewer signers (low N), the chance of a mistake or an outright hack being approved should be reduced
- Requiring more signers (high M) could translate to more oversight and improved security, but if M is too high you’ll get diffusion of responsibility; key players may assume that someone else is handling whether an approval should be made
Demo: Creating a multisig wallet
To learn more about the multisig quorum quotient and compare different cases, let’s create a wallet for a company with four managers. In our example, the multisig will need to be accessed to change a greeting. We’ll look at three configurations: no multisig, 1/3 multisig, and 2/4 multisig.
Of course, the purpose of this example is simply to demonstrate the multisig, not the contract it controls. In real-world applications, contracts generally perform more valuable functions than changing a greeting and they generally limit the number of individuals who can make a change.
No multisig
Before we actually get to the multisig we should set up our lab environment and target contract (the contract the multisig controls). The lab environment runs on top of the Goerli test network. If you need Goerli test ETH, you can get it at this faucet.
For our demo, we’ll use a simple smart contract called Greeter.sol
, which I deployed with Hardhat. You can see it here.
To see the current greeting, open Contract > Read Contract and then expand greet.
To modify the current greeting, open Contract > Write Contract. Then, click Connect to Web3 to connect to the wallet. After selecting a wallet from the listed options, click setGreeting and type the new greeting. Then, click Write and approve the contract in the wallet.
Note that due to caching, after you change the greeting you may need to reload the contract a few times before you’re able to see the new greeting.
1/4 multisig (one signature required)
The demo multisig was created with Gnosis Safe, which is probably the most common multisig platform.
The addresses that are authorized to use the multisig are all derived from the passphrase: “dumb cart rally entry iron flock man demise record moon erode green”
The addresses are as follows:
- 0x3646468082813B33BF7aab1b8333Aa01fEe8a386
- 0x8c262b009B05e94D3ffF1Ce4cEa8Da0ba450c793
- 0x126FE1acDB5A5101B80Dc68A0b0dc882BFeEe5A6
- 0x0C48dfb3FAaFBCECF21f0D1F4e75E1fE6e731Ad6
- 0x934003bC77b9D427c4a441eBef2086AA089Ed0C5
- 0x9D5f666b29D0dd2397fDbc093fdaCAa0EF6e7377
In real-world scenarios, the addresses come from unique passphrases when they belong to different people. However, doing that here would require you (as the reader) to continually log out of one passphrase and into another or to use multiple devices. For this training, I’ve decided that convenience outweighs security, so we’ll omit the unique passphrases in this demo.
Now, let’s look at an example in which only the owners can change the greeting. In this example, just one signature is required to make a change.
We are going to use the same Greeter.sol
contract. In a real-world application, we’d probably implement Ownable
and set the owner to the multisig, but the purpose here is to make things as simple as possible, not as secure as possible.
When a single signer is required, you need to propose and then confirm the transaction.
- Browse here with a browser that has a wallet with the passphrase specified above and connect with one of the first four addresses listed above
- Click New Transaction and Contract interaction
- Paste the address for the contract with which you are trying to interact:
0x8A470A36a1BDE8B18949599a061892f6B2c4fFAb
- Notice that the ABI with the definitions of how to contact the contract is imported automatically; the contract’s code is available on Etherscan, so Gnosis Safe can retrieve the code
- Select the
setGreeting
method and type a new greeting - Click Review and Submit; next, approve the transaction in the wallet
- Wait
- Once the transaction is executed, go to the contract and expand greet to see the greeting has changed
2/4 multisig (two signatures required)
Next, let’s look at an example in which two of the four owners must sign. For this demo, we’ll need to pretend to be a second manager and approve the transaction in order to have the two signatures needed for the transaction to occur.
First, follow the steps in the previous example, but use this safe.
- Switch to a different address in the wallet (one of the three other approvers)
- Browse here again; you might need to disconnect and reconnect in the app to get the correct address to show up
- Click the transaction under Transaction Queue
- Expand the transaction, click Confirm to approve the transaction, and then click Submit
- Approve the transaction in the wallet
Now, view the transaction, and then verify that the requested action occurred (that the greeting really did change):
- Browse here and expand greet to see that the greeting really has changed
- To see the transaction, click Internal Txns and find the latest transaction between the multisig (0x8f760d2fd9999d407b3c4b67555bf037ed5eb832) and the greeter (0x8a470a36a1bde8b18949599a061892f6b2c4ffab)
- Click the Parent Txn Hash to see the transaction that changed the greeting
- Notice that the second signer is listed as the source
Potential problems with multisigs
Multisig wallets are meant to provide additional security, but issues can still arise. Let’s look at some examples.
Locked assets
The great advantage of the blockchain is that there is no central authority. In the example above, no one can approve a transaction from the multisig except for at least two of those four manager addresses.
The great disadvantage of the blockchain is that there is no central authority to override contracts in times when it is justified. For example, in the case of the death of three signers of a 2/4 multisig, there would be no way for the multisig to release any of its assets. The wallet’s assets would remain locked forever.
One option to provide a backup for this type of scenario is to have someone the company trusts completely (e.g., the owner) generate two additional addresses and store their passphrases in tamper-resistant envelopes in a secure location. An off-premise location, such as the safe of the company’s attorney or account, is often a good option.
Owner override
In a multisig, all signers are equal. The problem is that sometimes we want signers who are more equal than others. For example, we might want the business managers to be able to do something with an additional signature, but for the owner to be able to do anything.
One solution would be to allow the owner’s address to access the target contract directly, without going through the multisig. This solution has the best usability, but it means we cannot fully rely on the multisig for auditing.
A second option is for the owner to generate two addresses from the passphrase and use both addresses as signers. This solution has more limited usability but could be a better option if part of the purpose of the multisig is to reduce the chance of a careless mistake and if owner overrides are to be used as an emergency measure, rather than part of daily processing.
Demo: Creating a shared multisig wallet
Now, let’s look at a more complex scenario, one in which two companies collaborate and the wallet’s function requires approval from at least one manager from each company.
Because all signers are equal in a multisig, we need to write some logic into the contract in order to achieve this goal. Click here to see the Solidity contract.
Let’s see what happens when company A proposes a new greeting.
- Go to the contract and check the current greeting
- Switch the wallet to one of the group A addresses:
- 0x3646468082813B33BF7aab1b8333Aa01fEe8a386
- 0x8c262b009B05e94D3ffF1Ce4cEa8Da0ba450c793
- 0x126FE1acDB5A5101B80Dc68A0b0dc882BFeEe5A6
- Browse to the group A multisig
- Click New Transaction > Contract interaction
- Type the contract address:
0x3e55E2DBDE169Fbf91B17e337343D55a7E0D728e
- Click proposeGreetingA and propose a greeting
- Click Review and then Submit
- Confirm the transaction in the wallet
- Go to the contract again and see that the greeting is unchanged
Next, let’s see what happens when company B proposes a different greeting. This step is necessary because it’s not enough to see that the smart contract behaves correctly when people follow proper procedures. It is just as important to ensure that the contract remains secure when people do not follow a proper procedure.
- Switch the wallet to one of the group B addresses:
- 0x0C48dfb3FAaFBCECF21f0D1F4e75E1fE6e731Ad6
- 0x934003bC77b9D427c4a441eBef2086AA089Ed0C5
- 0x9D5f666b29D0dd2397fDbc093fdaCAa0EF6e7377
- Browse to the group B multisig
- Click New Transaction > Contract interaction
- Type the contract address:
0x3e55E2DBDE169Fbf91B17e337343D55a7E0D728e
- Click proposeGreetingA and propose a greeting
- See that the review tells you the transaction will fail (because you’re not a member of the correct group); click Back
- Select the proper choice for your current address, proposeGreetingB, and propose a greeting (be sure to select a different greeting from that proposed by company A)
- Click Review and then Submit
- Confirm the transaction in the wallet
- Go to the contract yet again and see that the greeting is still unchanged
Now, let’s see what happens when company B proposes the same greeting that was proposed by company A.
- Try proposeGreetingB again, this time with the same greeting you proposed as a member of group A
- Go back to the contract, for the final time, and see if the greeting has finally changed
Let’s look at the Solidity code to see how this works:
/** *Submitted for verification at Etherscan.io on 2022-05-08 */ //SPDX-License-Identifier: Unlicense pragma solidity ^0.8.0; contract AB_Greeter { string greeting;
Here are the addresses of the multisigs:
address multisigA; address multisigB;
These variables hold the hashes of the proposed greetings.
Using the hashes has two advantages.
- Ethereum storage is an expensive resource, this way we use less of it
- When we store the hash we only need to write one 32byte word per proposal
If we were to store strings they could be much longer and more expensive. Also, Solidity does not have an inbuilt expression to compare strings, so the easiest way to compare two strings is to compare their hashes. By using hashes, we only calculate the hash once for every time we call proposeGreeting[AB]
.
bytes32 proposedGreetingA = 0; bytes32 proposedGreetingB = 0;
To get started, we need the greeting, as well as the addresses of the two multisigs:
constructor(string memory _greeting, address _multisigA, address _multisigB) { greeting = _greeting; multisigA = _multisigA; multisigB = _multisigB; }
The functions greet
and setGreeting
are the same as in the Greeter.sol
contract we used earlier.
function greet() public view returns (string memory) { return greeting; } function setGreeting(string memory _greeting) internal { greeting = _greeting; }
This is the function to propose a new greeting.
function proposeGreetingA(string calldata _greeting) public {
Only multisigA
is allowed to propose greetings as company A; any other source will be rejected.
require(msg.sender == multisigA, "Only for use by multisig A"); bytes32 _hashedProposal = keccak256(abi.encode(_greeting));
If company B has already proposed what company A is proposing now, we update the greeting like so:
if(_hashedProposal == proposedGreetingB) setGreeting(_greeting);
Otherwise, we register this as company A’s proposed greeting:
else proposedGreetingA = _hashedProposal; }
It’s important to realize that this isn’t the ideal way to accomplish this goal because multisigA
is a 1/3, so any of company A’s managers could switch the multisig and take away the other two signers’ ability to propose or approve anything.
A more sensible policy would be to have another multisig, maybe a 2/3, for this type of sensitive operation. However, the purpose of this example is to teach, so we’ll opt for simplicity over security.
In the code below we specify that multisigA
can switch to a new multisig if that is ever needed.
function changeMultisigA(address _newMultiA) public { require(msg.sender == multisigA, "Only for use by multisig A"); multisigA = _newMultiA; }
Company B’s functions are the mirror image of those of company A.
function proposeGreetingB(string calldata _greeting) public { . . . } function changeMultisigB(address _newMultiB) public { . . . } }
Warning about smart contract development
Smart contract development is relatively easy, but secure smart contract development is not. Unless you have a lot of security expertise it is highly recommended that you have someone knowledgeable review your logic and code before trusting it in a mission-critical application.
For example, when I wrote the AB_Greeter
contract, I first used just a single variable for the proposed greeting, and my code looked like this:
function proposeGreetingA(string calldata _greeting) public { require(msg.sender == multisigA, "Only for use by multisig A"); bytes32 _hashedProposal = keccak256(abi.encode(_greeting)); if(_hashedProposal == proposedGreeting) { setGreeting(_greeting); } else { proposedGreeting = _hashedProposal; } }
Can you spot the problem?
Two approvals are indeed required to change the greeting. However, company A can just call proposeGreetingA
twice with the same greeting. The first call puts the hash of the new greeting as the proposal. The second call sees that the new greeting’s hash is identical to the proposal and updates the greeting.
If the proposal had come from company B this would have been fine, but here the proposal came from company A, so this is a violation of the terms.
To resolve this issue, I decided to use two separate proposals, one under company A’s control and the other under company B’s control.
I am not saying that the logic in the current contract is 100 percent secure. If I were to use this in production I’d ask some other people to look at it first. Smart contracts exist to enable trustless cooperation. When you write them, you have to assume they will be used in a hostile environment. The expense of running a smart contract instead of a more conventional program is only justified if the environment is potentially hostile.
Conclusion (when are multisigs the right solution?)
Multisigs are a simple solution to a simple problem – how to get permissions from a group when all group members are equal and group membership rarely changes.
In this article, we reviewed some mechanisms to extend this functionality, either by using the multisig in an unusual way (the owner with two signers) or by adding our own logic in a separate smart contract (the two company scenario).
If your signer population is dynamic, or if you have many different roles, each with its own permissions, a multisig may not be the ideal solution. Instead, a decentralized autonomous organization may be a better option.
However, if the business requirements you need to implement are such that a multisig is sufficient, this is a much simpler solution than creating a DAO. Notice in our first example we didn’t need to write any code. You can also integrate multisigs into your own applications using the SDK.
The post Security choices and multi-signature wallets appeared first on LogRocket Blog.
from LogRocket Blog https://ift.tt/uZo57bY
via Read more