A common scenario: I want to send data to a recipient, and the recipient needs a way to be certain that the data was indeed sent by me and has not been tampered with in transit.
Now, one might ask, why not just authenticate the sender? While authentication verifies the sender's identity, there remains a risk of the data being intercepted or altered during transmission. To address such concerns, cryptographic signatures come into play.
In this article, we will explore the use of the Elliptic Curve algorithm, specifically with the ED25519
curve.
To begin, let's install an npm package that supports elliptic curves:
$ npm i @noble/curves
Now generate a public-private key pair:
import { ed25519 } from '@noble/curves/ed25519';
// generate private key
const privKey = ed25519.utils.randomPrivateKey();
// generate public key from the private key
const pubKey = ed25519.getPublicKey(privKey);
console.log(privKey);
/*
Uint8Array(32) [
82, 125, 137, 158, 68, 226, 62, 59,
198, 198, 92, 127, 197, 58, 93, 78,
184, 5, 81, 174, 69, 7, 41, 211,
2, 61, 56, 3, 239, 206, 144, 108
]
*/
console.log(pubKey);
/*
Uint8Array(32) [
116, 35, 200, 212, 232, 205, 176, 174,
56, 57, 49, 12, 10, 12, 34, 83,
137, 210, 239, 100, 138, 89, 24, 222,
19, 23, 46, 182, 79, 231, 225, 129
]
*/
Here both the privateKey and publicKey are Uint8Array
. Better convert them to hex
strings as it will be easy to use.
const privateKey = Buffer.from(privKey).toString('hex');
console.log(privateKey);
// 527d899e44e23e3bc6c65c7fc53a5d4eb80551ae450729d3023d3803efce906c
const publicKey = Buffer.from(pubKey).toString('hex');
console.log(publicKey);
// 7423c8d4e8cdb0ae3839310c0a0c225389d2ef648a5918de13172eb64fe7e181
A message is signed always with a private key, and the reciever of the message verifies the signature with the public key (from the same public-private pair).
Let's proceed to create a signature of a message.
const message = "Hey there! I am learning to code.";
// convert the message to Unit8Array
const encodedMessage = new TextEncoder().encode(message);
// private key here is a hex string, not Uint8Array
const encodedSign = ed25519.sign(encodedMessage, privateKey);
The encodedSign
generated above is in Uint8Array
, let's convert it to hex
string.
const signature = Buffer.from(encodedSign).toString('hex');
console.log(signature);
// c9a2359afa0b797076982e404aaf853bcc958fff8a31f1c86aeb11156ea086f1bc224e6f1775e04335efaa2829e538a56068114c9d68edf0b7d1794d7ff0c408
At the receiving end of the message, the task is to verify the correctness of the received signature. To accomplish this, we require three essential components: the original message, the corresponding public key, and the received signature. By using these elements together, we can validate whether the received signature is indeed authentic.
// convert the message to Uint8Array
const encodedMsg = new TextEncoder().encode(message);
// here signature and publicKey are hex strings
const isSignatureCorrect = ed25519.verify(signature, encodedMsg, publicKey);
console.log(isSignatureCorrect);
// true
Further Reading -