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

Digital Signatures

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: You 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 say changing even one character), the verify algorithm outputs a completely different public key and you will know that the message was tampered because the public key will be different from Pu.

Solution Details

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 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.

Implementation Details

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 record the vote.

Zeppelin has a handy library called ECRecovery we 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.

Further Reading

https://en.wikipedia.org/wiki/Public-key_cryptography

https://en.wikipedia.org/wiki/Digital_signature

https://github.com/danfinlay/js-eth-personal-sign-examples/

https://danfinlay.github.io/js-eth-personal-sign-examples/

https://github.com/ethereum/EIPs/pull/712

Thanks Chris Whinfrey and Febin John James for reviewing drafts of this article.