EIP1271 Message Signing with a DAO
How Moloch DAOs can sign arbitrary messages using minion contracts
A few weeks ago a group of devs from DAOHaus, Raidguild, Rarible, and Minty got together to build tools for DAOs to interact directly with NFT marketplaces without trusted delegates. During this process we came up with a new way for DAOs to effectively sign messages as if the DAO was one Ethereum wallet.
How It Works
First, a few important definitions
A MolochDAO is a smart contract that enables communities to pool funds, allocate voting and non-voting shares, and enable individuals to exit gracefully with their fair share of community funds.
A Minion is a smart contract that ties arbitrary code execution to a MolochDAO. Minions can be configured to store funds outside of the MolochDAO’s treasury, manage a collection of NFTs, or interact with other applications as directed by proposals.
EIP1271 is a contract signature standard that enables smart contract wallets to behave like EOAs (externally owned accounts). Signatures are critical when interacting with various types of decentralized applications. Developers use signatures for authentication, meta-transactions, order books, and anything that requires delegated permission or proof of ownership of an address. EIP1271 has widespread adoption and is used in applications like Argent and Snapshot.
Why would a DAO need to sign a message?
Communities are increasingly using DAOs to collectively invest, collect, and fund grants. If a DAO wants to collectively buy an NFT from a marketplace like Rarible, they currently have to elect a delegate, send them funds, and trust them to acquire the NFT, then hold it on behalf of the DAO.
For example…
(self promotion disclaimer — I am a member of a few of these communities)
Metacartel Ventures is a for-profit DAO created by the MetaCartel community for the purposes of making investments into early-stage Decentralized Applications (DApps).
The LAO is a distributed VC started by OpenLaw for pooled investments in Decentralized Applications.
Flamingo is an NFT collecting DAO with a huge collection.
Neptune is an upcoming pooled yield farming DAO.
PleasrDAO is a community that formed around a specific NFT auction then continued to organize around new drops, especially those with historical or charitable impact.
Minty (my project) is a platform for launching pools to manage group collections of physical collectibles.
Pool Party is a platform for launching pools to manage group investments of crypto.
Bankless DAO is a community driving adoption and awareness of bankless money systems like Ethereum, Bitcoin and DeFi through media, culture, and education.
Peerion is a DeFi liquidity provider and staking service, owned and operated by a DAO.
SAIA DAO is a special purpose crowdfunding DAO to buy upcoming Star Atlas assets.
Party DAO is building Party Bid, which is a platform for crowdfunding around NFT drops and splitting ownership.
Syndicate is an upcoming platform for launching community operated investment funds DAOs.
How DAO proposals usually work
Signatures go through the same governance process as a typical MolochDAO proposal.
- A member submits the proposal (usually related to spending the treasury or allocating new shares)
- A members sponsors the proposal by locking some funds
- All members can vote on the proposal until the voting period ends. There is no quorum required, it’s a simple time based system. Proposals pass if there are more ‘yes’ than ‘no’ votes.
- After voting ends, the proposal enters a grace period during which any members who disagree with the proposal can quit before it is processed.
- The proposal is processed.
Remember Minions from above, which tie arbitrary code execution to a DAO proposal. For minions, the proposal flow is:
- A member deploys a Minion contract which associates itself with a parent Moloch
- A member makes a proposal via the Minion with an embedded ‘action’ to take if the proposal passes
- Same voting process above…
- If the proposal passes, a member can trigger the Minion to execute the stored action
A typical use case is to have a Minion store NFTs on behalf of the DAO. Moloch treasuries are not capable of storing ERC721 tokens. So the Minion stores them on behalf of the DAO, and members can propose actions like transferring the NFT via the Minion through the DAO governance process.
So how would a DAO actually sign something?
We have to adapt the Minion slightly to make signatures work. Instead of storing actions on behalf of the DAO, we store signature proposals.
In our case, let’s make a proposal for the DAO to sign the canonical EIP712 signTypedData example.
const value = {
from: {
name: 'Cow',
wallet: '0x3717Ca790C2eB4aE2C9de43E48A910A64770A283'
},
to: {
name: 'Bob',
wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB'
},
contents: 'Hello, Bob!'
};
The ‘from’ address in the message above is our Minion which is deployed to Rinkeby here: https://rinkeby.etherscan.io/address/0x3717Ca790C2eB4aE2C9de43E48A910A64770A283
We can calculate the digest of this message using eth-sig-util
const domain = {
name: 'Ether Mail',
version: '1',
chainId: 1,
verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC'
};// The named list of all type definitions
const types = {
Person: [
{ name: 'name', type: 'string' },
{ name: 'wallet', type: 'address' }
],
Mail: [
{ name: 'from', type: 'Person' },
{ name: 'to', type: 'Person' },
{ name: 'contents', type: 'string' }
]
};// The data to sign
const value = {
from: {
name: 'Cow',
wallet: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'
// wallet: '0x3717Ca790C2eB4aE2C9de43E48A910A64770A283'
},
to: {
name: 'Bob',
wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB'
},
contents: 'Hello, Bob!'
}; const msgHash = util.bufferToHex(sig.TypedDataUtils.sign(createTypeData(domain, 'Mail', value, types)))
The digest turns out to be
0x0f7e7790206c7eacbc7d67f8820d58f4ee73438cde1e0e9aaedfb768182a2b1c
Walkthrough of the proposal process
Our minion has a method called proposeSignature where we can specify the digest above. We also need to pass in an arbitrary signatureHash. This value does not matter because we are not actually recovering the signer of the message, but it needs to look like a signature.
Our proposal has not passed yet, so if we check if the signature is valid using the EIP1271 interface, the transaction will revert.
5 — Check if the signature is valid again
The proposal has passed, so the isValidSignature method now returns the expected value!
What actually happened here?
We extended the EIP1271 interface for the isValidSignature function to respect DAO votes, rather than using the cryptographic recovery function ECRecover.
Most implementations of EIP1271 just take the signature, perform ECRecover to see who signed it, and check if the signer is an authorized user for the contract. For more information check the EIP1271 issue thread
Our isValidSignature method checks the proposal flags associated with the message hash. If the proposal has passed, the method returns the associated magic value. Otherwise it throws.
Why a magic value and not just a bool? It just gives extra assurance that we are calling the right interface and we didn’t stumble into a function that returns a true bool.
Where do we go from here?
Integrating with order books
We are using this functionality in the Rarible Boost on DAOHaus, which you can learn more about here: Proposal.
Here is an example transaction where we used this new feature to match an order on the Rarible order book that was signed by a DAO! Source code here, based on a fork of Austin Griffith’s Scaffold-Eth.
We are also continuing to extend Minion functionality to make it so DAOs can have even more control through governance proposals, and enable collector DAO platforms like Minty.
Integrating with Snapshot
Snapshot proposals and votes are simple signed messages stored in IPFS and processed off-chain using token based weighting. Until now, DAOs could not directly create proposals on Snapshot and instead relied on a trusted delegate. DAOs could now use the proposal minion demonstrated above to integrate in a more decentralized way.
Here is a link to Snapshot’s implementation of EIP1271. It uses the standard isValidSignature interface so it should work without any changes on their end.
References
The source code for our EIP1271 Minion can be found here (forked from DAOHaus): https://github.com/ipatka/MinionSummoner/tree/ipatka-eip1271
EIP1271 — https://github.com/ethereum/EIPs/issues/1271
Moloch DAOs — https://github.com/HausDAO/Molochv2.1
Minions — https://github.com/HausDAO/MinionSummoner
The cover image for this article was taken from here — https://github.com/MolochVentures/moloch
What did I miss?
Identify any security issues? Or just reach out if you want to hack on this stuff with us.
Discord: izkp#1401
TG: @isaacpatka