Skip to main content

How to Verify Message Signatures on Ethereum

Created on
Updated on
Nov 26, 2024

5 min read

Overview

In a world where data is the new gold and digital transactions are everyday norms, how do we ensure that the person on the other side of the screen is truly who they claim to be? This is where cryptography comes in and helps us to verify it. In this guide, we'll learn more about how cryptography and user signatures play a fundamental role on Ethereum, and later on, show you how you can verify message signatures on Ethereum using JavaScript.

What You Will Do


  • Learn about signature generation and verification
  • Create a Node.js script to generate and verify message signatures with Ethers.js and plain JavaScript
  • Review different security considerations
DependencyVersion
Node.js18.18.0
elliptic^6.5.4
keccak256^1.0.6
secp256k^5.0.0
ethers^6.8.0

What You Will Need


  • Basic understanding of Ethereum
  • Node.js and npm installed

This guide was inspired by the following video: AI Makes Everything Easy to Fake—Crypto Makes it Hard Again | Balaji Srinivasan at SmartCon 2023. Feel free to check it out for more on this topic.

Private and Public Keys

There are two types of Accounts on Ethereum: Externally Owned Accounts (EOA) and Smart Contract Accounts.

Every EOA is associated with a pair of cryptographic keys: a private key and a public key. The Ethereum address is derived from the public key.


  • Private Key: A random 256-bit number. It's crucial to keep it secret because whoever has access to it can control the activity associated with the account.
  • Public Key: Derived from the private key, and it's a point on an elliptic curve. Using Elliptic Curve Digital Signature Algorithm (ECDSA), Ethereum public keys are 512-bit long.

Signature Generation

When you sign a message, you are producing a cryptographic proof that you possess the private key associated with a given Ethereum address, without revealing the private key itself. This is achieved using ECDSA.

Ethereum signatures are used to:


  • Authenticate the sender of a transaction.
  • Ensure the integrity of the transaction data.
  • Provide Non-repudiation: Once signed, a sender can't deny having sent the transaction.

To sign a message:


  • Hash the message with Keccak-256, which produces a 256-bit hash.
  • Use this hash and the private key to produce a signature using ECDSA.

The signature consists of three components: r, s, and v:


  • r and s are outputs of the ECDSA algorithm.
  • v is the recovery id, which helps in extracting the public key from the signature.

Signature Verification

To verify a signature:


  • You need the message, the signature (r, s, v), and the public key or Ethereum address of the signer.
  • Extract the public key from the signature using the recovery id (v).
  • Verify that the signature corresponds to the given message hash and extracted public key.
  • If you only have an Ethereum address, derive it from the extracted public key and compare it to the given address.

Generate a Signature & Verify using JavaScript

We'll be learning how to first generate and verify a signature with a more manual approach with basic JavaScript, and in the next section, cover a simpler solution with a popular SDK, Ethers.js.

First, let's create a project folder and install the required dependencies:

mkdir verifySignature
cd verifySignature
echo > index.js && echo > generate.js && echo > verify.js
npm init --y
npm i keccak256 secp256k1 elliptic

Then, open the file index.js and input the code snippets below sequentially:

Imports

//Import Libraries
const EC = require('elliptic').ec;
const keccak256 = require('keccak256')
const ec = new EC('secp256k1');

Import the required libraries (elliptic library for ECDSA and the keccak library for the Ethereum-standard SHA-3 hashing).

Generate a Key Pair

//Generate a Key Pair
const key = ec.genKeyPair();
const publicKey = key.getPublic('hex');
const privateKey = key.getPrivate('hex');

Generate a key pair with the elliptic library and store each key respectively.

Generate a Signature

//Generate a Signature
const message = "Hello, Ethereum!";
const msgHash = keccak256(Buffer.from(message));
const signature = key.sign(msgHash, 'hex');

Verify the Signature

// Verify the signature
const isValid = ec.verify(msgHash, signature, publicKey, 'hex');
console.log(isValid);

Output:

true // Outputs true if the signature matches the corresponding private key it was generated from

Upon output, the log should show true in the case the signature matches the message hashed and public key. To reiterate, the msgHash in this case would be the hashed version of the original message. While signature refers to the actual signature object produced by the sign function. It contains the v, r, and s values of the ECDSA signature. Lastly, the publicKey which is the public key corresponding to the private key that was used to create the signature. In the context of signature verification, this public key is used to ensure that the signature was indeed generated by the corresponding private key.

Generate a Signature & Verify it using an SDK (Ethers.js)

To conduct our signature generation and verification easier, alternatively, we can use a handy SDK called ethers.js to help us.

Installation

npm install ethers

Then, to generate a signature, open the generate.js file, and input the following code:

const { Wallet } = require('ethers');

async function signMessageWithEthers(privateKey, message) {
const wallet = new Wallet(privateKey);
const signature = await wallet.signMessage(message);
return signature;
}

const message = "Hello, Ethereum!";
const privateKey = 'YOUR_PRIVATE_KEY_HERE'; // Make sure to provide your private key with or without the '0x' prefix
signMessageWithEthers(privateKey, message).then(signature => {
console.log(`Signature: ${signature}`);
});

Make sure to update the privateKey with your own, and then execute the script:

node generate.js

You'll see an output similar to:

Signature: 0x020d671b80fbd20466d8cb65cef79a24e3bca3fdf82e9dd89d78e7a4c4c045bd72944c20bb1d839e76ee6bb69fed61f64376c37799598b40b8c49148f3cdd88a1b

To verify the signature, input the following code in your verify.js file:

const { Wallet, verifyMessage } = require('ethers');

const message = "Hello, Ethereum!"
const signature = "0x020d671b80fbd20466d8cb65cef79a24e3bca3fdf82e9dd89d78e7a4c4c045bd72944c20bb1d839e76ee6bb69fed61f64376c37799598b40b8c49148f3cdd88a1b"

function verifyMessageWithEthers(message, signature) {
const signerAddress = verifyMessage(message, signature);
return signerAddress;
}

const signerAddress = verifyMessageWithEthers(message, signature);
console.log(`Message signed by: ${signerAddress}`);

Execute the script:

node verify.js

You'll see an output similar to:

Message signed by: 0xe0f2b924422909626D539b0FBd96239B31767400

There you have it, double-check the message is signed by the corresponding public key.

Sending Transactions with QuickNode

Ethereum provides mechanisms, like eth_sign, for signing arbitrary messages. This is not just for transactions but for any data. Message signing is useful for off-chain authentication, proving ownership, or other cryptographic proofs. QuickNode supports the method eth_sendRawTransaction which takes a signed transaction object and sends it to the blockchain.

Note to create a signed transaction object, you'll want to use an SDK such as Ethers.js (check out the signer.signTransaction function); Leave some feedback at the bottom of the guide if you'd like to see a guide on that as well!

Libraries and Tools

There are several libraries and tools that facilitate message signing and verification:


  • web3.js, ethers.js and web3.py: JavaScript libraries commonly used with Ethereum. Both provide functions for message signing and signature verification.
  • MetaMask: A popular Ethereum wallet that allows users to sign messages.
  • MyCrypto and MyEtherWallet: They provide UIs for users to sign arbitrary messages.

Security Considerations


  • Replay Attacks: Ensure signed messages have some form of nonce or timestamp to prevent them from being reused. Repay attacks consist of taking one confirmed transaction and maliciously trying to execute it again.
  • Phishing: Users should be careful about what they sign. Malicious DApps could trick users into signing a transaction rather than an innocent message.

Test Your Knowledge

Try attempting the short quiz to solidify your understanding!

🧠Knowledge Check
What are the two types of Accounts on Ethereum?

Wrap Up

There you have it! You now know a bit more about signature generation and verification on Ethereum.

We would love to hear more about what you are building. Drop us a line in Discord, or give us a follow on Twitter to stay up to date on all the latest information!

We ❤️ Feedback!

Let us know if you have any feedback or requests for new topics. We'd love to hear from you.

Share this guide