How to Save Your Ethereum DApp Users’ From Paying Gas for Transactions
And Instead, You as the DApp Owner Pays For It
Crosspost: This post was originally published, here. And was posted with the author’, Mahesh Murthy’s permission. [Learn more at Zastrin.com. Bonus: Use code “BlockChannel” to save 50% off all courses.]
One of the friction points for Ethereum dapp adoption is that the users have to a pay gas (transaction (txn) fee) to get their transactions recorded on the blockchain. For example, I have a simple voting dapp that lets anyone vote for candidates, and the votes are stored on the blockchain. A user who wants to record her vote on the blockchain has to pay a transaction/gas fee. This is not ideal because as a dapp owner, you are expecting your application users to have Ether to pay for gas when all they want to do is perform a simple action which has nothing to do with transferring money. But if the transaction needs to be executed on the blockchain, there is no other option but to pay the fee. What if there was a way for users to execute transactions securely (vote for a candidate, as in our example) and let someone else (potentially the contract owner) record the transaction on the blockchain and pay for it themselves?
Thanks to this tweet from John Backus, I had just enough information to help me implement such a solution for my voting dapp.
I wanted to share details on how I implemented this solution for my simple dapp so more people can adopt this technique in their own dapps and could hopefully improve upon it. This post covers the following:
- A very high level overview of the public key cryptography and digital signatures, which are key to understanding this solution.
- The solution details and the new application flow.
- Implementation details (frontend js and Solidity contract code).
- Discuss potential issues and enhancements.
For this solution to make sense, you will need a basic understanding of how digital signatures work in cryptography. Feel free to skip this section if you know public key cryptography. I will try to explain the concept of public/private keys and digital signatures at a very high level but I highly recommend learning more in detail — wikipedia is a good place to start.
Public key cryptography is a cryptographic system where you have 2 keys — public key (Pu) and a private key(Pr). You give out your public key to the entire world and keep the private key to yourself. Ex: Your Ethereum address is a public key (It’s actually derived from public key, but for this exercise, let’s just think of it as public key) and your private key is stored either in your browser, or on your phone/computer. As you know, for someone to send you Ether, they just need to know your public (account) address. However, only you can access the funds you own because you are the only one who knows your private key.
Public key cryptography has algorithms that let you encrypt, decrypt, sign and verify messages using your pair of keys.
Let’s see what signing and verifying a message means through an example. Let’s say user Kim has a pair of public/private keys
Pu = “0x44ac12c1e3dfd8edaf83b6f65918229d5279a6f5”
Pr = “dbc226043e390cf39280e5edfd418d7ad61931c76509270867d300f110c46506”
To sign a message, Kim executes a function sign(“Vote for Alice”, Pr) which outputs an alpha-numeric string
signature = 0x9127112de0033555c7f6508d963d484965a953844dfcff092712102c236467a25af57edc53b63880ea39af8ce7334f6d77a8206e805305e7c6ad919d12bfae5c1b
This is the digital signature of the message “Vote for Alice” signed by Kim using her private key Pr.
Now anyone can verify that message “Vote for Alice” was signed by Kim by executing the verify function, verify(“Vote for Alice”, signature) which outputs “0x44ac12c1e3dfd8edaf83b6f65918229d5279a6f5”. If you notice, that output is Kim’s public key Pu (remember, everyone knows it’s Kim’s public key) which means the message was definitely signed by Kim. If you tamper with the signature or message (by changing even one character), the verify algorithm outputs a completely different public key and you will know that the message was tampered with, because the public key will be different from Pu.
If you understand digital signatures, the solution is extremely trivial. Let’s see how it can be used in our voting application to save users from paying gas fee without compromising their vote. You can see below all the users of dapp and the actions they perform.
- A voter indicates their intention to vote for a candidate by signing a message using their private key. They won’t submit their transaction to the blockchain, so no txn fee is paid. The message queue in the diagram above is just an off chain location where all the vote details are stored.
- Anyone willing to pay the txn fee (usually the contract owner) takes the signature, candidate name and voter’s account address and submits them to the blockchain.
- The smart contract uses the verify function to derive the public key (Ethereum account address) based on the candidate name and signature. If the derived public key matches the address of the user who signed the message, it records the vote or else fails the transaction.
Let’s now look at the actual implementation and how all the pieces fit together.
Step 1: Sign the message
The first step is to sign the message as a voter. We will use eth_signTypedData function to sign our message. This function has been implemented in Metamask which makes it really easy to sign messages. You can find more details and discussion about this proposal here: https://github.com/ethereum/EIPs/pull/712. You can find the code to sign the message below.
One really important thing to note is, internally eth_signTypedData hashes the message and the hashed message is what gets signed. You can refer to typedSignatureHash function here for more details on hashing.
Step 2: Submit the signed vote to the blockchain
Since this is just a demo application, we don’t store the signature and other details anywhere. It is directly displayed on the page after the message is signed. Anyone can take these details and submit to the blockchain. Here is the code that submits the vote to the blockchain:
Step 3: Verify the vote details in the smart contract
We now verify in the smart contract if the submitted vote info is valid and we then record the vote.
Zeppelin has a handy library called ECRecovery we can use to verify the signed message. The voteForCandidate function verifies the signed message (recover function) and updates the vote count if the verification succeeds.
If you remember, I mentioned earlier that eth_signTypedData hashes the message (“Vote for Alice”) before signing it? The solidity recover function doesn’t have any knowledge of the hashing function used within eth_signTypedData and so it can’t verify the message “Vote for Alice”. It has to generate the hash of the message “Vote for Alice” and then verify it. Instead of generating the hash inside the contract, we pre-hash all the messages beforehand and pass it in the constructor so it is easy to lookup when verifying. The code to generate the hash is in the migration file below
That is all the code you need to get the new application working!
I created a quick demo to show how this application works
The entire working code is here: https://github.com/maheshmurthy/ethereum_voting_dapp/tree/master/chapter4
The demo application is here: https://www.zastrin.com/voting-dapp-without-paying-gas.html
Potential issues to address
There are a few issues to consider when building a real dapp using this technique. Some of them are listed below:
- Where are the signed messages stored? You can use some kind of a queuing system to store these messages.
- What is the guarantee that the signed message eventually is submitted to the blockchain?
- Storing the hash of all the messages in the blockchain is not ideal, so what is the best solution for it?
If you have thoughts on how to address these issues or if you see any flaws in this solution, please leave a comment!
Note: Apparently, eth_signTypedData API is still not stable and has been implemented by only metamask. Please beware of this if you are planning to use this technique in mainnet/production.
If you are interested in learning to build Ethereum decentralized applications, I have a few interesting courses at www.zastrin.com