4 min read

[EthersJS - 4] Assert your Identity, sign messages and send transactions

Using the private key to sign messages and send transactions.
[EthersJS - 4] Assert your Identity, sign messages and send transactions
Photo by Ben Sweet / Unsplash

So, you can generate and import wallets. What can you do with it? Well, that is the topic of this article. We will teach you to prove you are who you say you are and assert your identity. Read on!

Signing messages - I am, who I say I am


In the world of ethereum and crypto in general, if you are the owner of an account (wallet),  you own the private key.

You can use that private key to "sign" a message using that key and send the hash to whoever you want to send the message to.  Other people can verify that the message came from you using the public key.  This is how identity works in ethereum ecosystem.

Using the classic metaphor of Alice and Bob of the cryptographic community:

Alice can sign a message with her pivate key, and send the message along with the signature to Bob. Bob can then easily verify that the signature came from Alice with the public key of Alice's address.

It is very much like "signing" a check in real world, except more secure because it is nearly impossible to fake a signature without the private key.

Well, secure to a certain extent  - this scheme works until someone beats you with a wrench until you give them the private key ....

xkcd - comic 538

Okay, so let's sign a message with ethers js

Once you have the wallet generated or imported, you can simply sign the message with one command - wallet.signMessage(message) , which of-course takes some time, so we use the await keyword and wrap it in a async function.

Our script should look something like this:

const ethers = require("ethers");

// creating random wallet 
const wallet = ethers.Wallet.createRandom()
console.log("Random Wallet address", wallet.address)
console.log("Random wallet private key", wallet.privateKey)

const message = "who dares, wins!"

const sign = async () => {
    const signature =  await wallet.signMessage(message)
    console.log(`Signature: ${signature}`)
}


sign();

Running this script, we get:

So, you have now successfully signed the message with your private key. You can now transmit this signature along with our message in the wild.

The next challenge then is how will someone be certain  that  the message was signed by you - the owner of the private key of the address 0x04Dc34977f4Ec835eec672C26b442D5f7f8F0124 and that the signature 0x5fdc88ef046a3af0b2bc2af0d30b85b4348b8b4a8186cee9f456467bfd7874925fcff08161d49031d0dc73ba4f650790a22b2c48b6a08ebce9a8db2b979495071c is valid ?

For that we get to next part - verifying the message. Ethers JS  makes this very easy for us. We can use the utility function verifyMessage() and pass in the message and the signature as params. The function outputs the signer's address which is a public information.  If the output of the verifySignature is same as the signer, then we can be sure that the signature was from the person who owns the private key of that wallet. Let's code it up:

const ethers = require("ethers");

const signature = "0x5fdc88ef046a3af0b2bc2af0d30b85b4348b8b4a8186cee9f456467bfd7874925fcff08161d49031d0dc73ba4f650790a22b2c48b6a08ebce9a8db2b979495071c"
const signer = "0x04Dc34977f4Ec835eec672C26b442D5f7f8F0124"
const message = "who dares, wins!"

const verify = async () => {
    const recoveredSigner = ethers.utils.verifyMessage( msg, signature );
    console.log("Recovered signer:", recoveredSigner);
    console.log("---comparing signer to recovered signer ------")
    if (signer == recoveredSigner) {
        console.log("message was signed by the owner of", signer)
    } else {
        console.log("message was NOT signed by the owner of", signer)
    }
}

verify(); 

Running this, we get:

Now, we can test this script and sign with some other address, and then try to verify the signature. We will get something like:

Signing Transactions and sending Ether


Ethereum would not be very useful if the only thing we could do is sending verified messages to each other. We can send verified transactions too. In fact, this forms the basis of the trustless web.

To do this, we will need two accounts generated by metamask. We will be sending ETH between these two accounts. We will do this on the Goerli network, and  therefore, will need some Test ETH on Goerli. We can use any one of  the following faucets:

Now, let's explore two ways of sending transactions

  1. Sending raw signed transactions
  2. Using ethersJs's easy send_transaction() method that takes care of everything for us.

First Approach

Since we are trying to get to the fun shit as fast as possible, we will code up the easier second approach. However, it is important to understand how it works, so instead of re-inventing (or in this case re-writing) the wheel, I will forward you to this excellent tutorial by etherscan:

Signing Raw Transactions - Etherscan

As an optional homework, try to construct a raw signed and serialized transaction and broadcast it to Goerli network.

Second Approach

const ethers = require("ethers");

const sender = '0xd25d2FBF90c6A810361448cDd4fF397A9bE5a703'
const receiver = '0x7f4e3F3178B884F6A9290e08a899306649F5bFf1' 


// Private key of sender
const senderPk = '[sender-private-key-here]'
const provider = new ethers.providers.InfuraProvider(
    "goerli",        
    "[infura-api]"
)

const wallet = new ethers.Wallet(senderPk, provider)

const main = async () => {

    const senderBalanceBefore = await provider.getBalance(sender)
    const receiverBalanceBefore = await provider.getBalance(receiver)

    console.log("Sender Balance",ethers.utils.formatEther(senderBalanceBefore))
    console.log("Receiver Balance",ethers.utils.formatEther(receiverBalanceBefore))

    const tx = await wallet.sendTransaction({
        to: receiver,
        value: ethers.utils.parseEther("0.002")
    })

    await tx.wait()
    console.log(tx)

    const senderBalanceAfter = await provider.getBalance(sender)
    const recieverBalanceAfter = await provider.getBalance(receiver)

    console.log("-----------After Transaction ------------")

    console.log("Sender Balance",ethers.utils.formatEther(senderBalanceAfter))
    console.log("Receiver Balance",ethers.utils.formatEther(recieverBalanceAfter))

}

main(); 

Running this, we get:

So, in Summary, we learned:

  1. How signing messages work in Ethereum
  2. How to Sign messages
  3. How to verify messages
  4. How to send transactions in Ethereum