This article will be a walkthrough of Alchemy University Ethereum Developer Bootcamp Week One course Public Key Cryptography exercises. The exercises are practice for the concepts reviewed in the Public Key Cryptography lecture.
Disclaimer: Most of the content below is a general summary and retelling of the information from the course. The exercise instructions are provided in the Alchemy course and the coding challenges are completed in the Alchemy IDE.
Hashing Messages
To use Elliptic Curve Digital Signature Algorithm (ECDSA), the first step is to hash the message before applying the signature algorithm.
The signature algorithm would be using either your private key or public key to sign a hashed message.
For example, if you wanted to vote on a proposal, you would hash the message then sign it with your private key. This would prove that the individual or group of a specific address voted on the proposal.
// turn this into an array of bytes, the expected format for the hash algorithm
const bytes = utf8ToBytes("Vote Yes on Proposal 123");
// hash the message using keccak256
const hash = keccak256(bytes);
console.log(toHex(hash)); // 928c3f25193b338b89d5646bebbfa2436c5daa1d189f9c565079dcae379a43be
In web3, when you send a transaction to a blockchain you also sign a hashed representation of that transaction before sending it to a blockchain node.
Exercise #1. Hash The Message
The goal of the exercise is to return a hashed message using the keccak256 hash and utf8ToBytes function provided by the ethereum-cryptography library.
Steps:
- Your first step is to take the string message passed in and turn it into an array of UTF-8 bytes. You can do so with the utf8ToBytes function.
- Then take the keccak256 hash of those bytes and return this value.
My solution:
const { keccak256 } = require("ethereum-cryptography/keccak");
const { utf8ToBytes } = require("ethereum-cryptography/utils");
function hashMessage(message) {
// convert message to bytes for hash algorithm
const bytes = utf8ToBytes(message);
// return hashed message
return keccak256(bytes);
}
module.exports = hashMessage;
Signing the Hash
Now that we've converted the message to a hash message, we can sign it with our key.
We will be using secp256k1 from the ethereum-cryptography library to sign the message. The signature will be returned with the recovery bit that will allow us to recover the public key from the signature. The recovery bit will enable the blockchain node to take a signature of a transaction and determine which address authenticated this particular transaction.
A blockchain transaction indicates the intent of the person who signed it and also authenticates them through public key cryptography.
Exercise #2. Sign The Message
- To sign the message, we will first get the hash message using the hashMessage function from the previous exercise.
- Then we will use the sign method for secp256k1.
My solution:
const secp = require("ethereum-cryptography/secp256k1");
const hashMessage = require('./hashMessage');
const PRIVATE_KEY = "6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e";
async function signMessage(msg) {
// hash the message
const hashMsg = hashMessage(msg);
// sign the hash message with the private key and set recovered to true to get the recovered bit
return secp.sign(hashMsg, PRIVATE_KEY, {recovered: true});
}
module.exports = signMessage;
Important Notes:
The sign method will take the hash message along with the constant PRIVATE_KEY declared at the top of the file. However, this is not good practice. This private key is a valid key that could be used to authorize blockchain transactions. If it is published on the internet then anyone could use the key to authenticate with the private key.
The ethereum-cryptography library uses noble-secp256k1. The detailed documentation is in the readme of noble-secp256k1.
The sign method takes an optional third parameter called options. Use this parameter to return the recovered bit so that the public key can be recovered from this signature.
Recover the Public Key
After the signature is provided with all of its components (recovery bit included), the public key can be recovered. Using the public key, the blockchain nodes will be able to determine who signed the transaction that was sent to them.
For example, a transaction could indicate the user would like to send 1 ether to another address and provide a certain transaction fee. Since the signature signs the hash containing this request, it is enough to authenticate this action.
Exercise #3. Recover The Key
- Given a message, signature, and recoveryBit find the public key and return it! Be sure to hash the message when passing it to the recovery method. Note: Use the noble-secp256k1 documentation to find the correct method and parameters for this one.
My solution:
const secp = require("ethereum-cryptography/secp256k1");
const hashMessage = require("./hashMessage");
async function recoverKey(message, signature, recoveryBit) {
// hash message
const hashMsg = hashMessage(message);
// recover the public key by passing in the hash message, signature, and recovery bit
return secp.recoverPublicKey(hashMsg, signature, recoveryBit);
}
module.exports = recoverKey;
Public Key to Address
Both Bitcoin and Ethereum have a process to transform a public key and turn it into an address. For Bitcoin, it uses a checksum and Base58 encoding. For Ethereum, its address is the last 20 bytes of the public key hash.
The address is different from the public key, but you can always derive the address if you have the public key.
Exercise #4: Get Ethereum Address
Let's get the Ethereum address from the public key.
- First step, you'll need to take the first byte off the public key. The first byte indicates the format of the key, whether it is in the compressed format or not. The publicKey will be a Uint8Array so you can use the slice method to slice off the first byte.
- Next, take the keccak hash of the rest of the public key.
- Finally, take the last 20 bytes of the keccak hash and return this. Once again, you can make use of the slice method.
My solution:
const secp = require("ethereum-cryptography/secp256k1");
const { keccak256 } = require("ethereum-cryptography/keccak");
function getAddress(publicKey) {
// slice of the first byte of the Uint8Array publicKey
const sliceKey = publicKey.slice(1);
// hash the rest of the public key => returns a Uint8Array keccak256 hash
const hashKey = keccak256(sliceKey);
// return last 20 bytes of the Uint8Array keccak256 hash
return hashKey.slice(-20);
}
module.exports = getAddress;
Resource:
- Alchemy University: Ethereum Developer Bootcamp
- ethereum-cryptography noble-secp256
Top comments (7)
Hello !!!
I am taking the Alchemy university ethereum Dev part 1 course, on public key cryptography sign messages;
voici mon code:
const secp = require("ethereum-cryptography/secp256k1");
const hashMessage = require('./hashMessage');
const PRIVATE_KEY = "6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e";
async function signMessage(msg) {
}
module.exports = signMessage;
It returns errors ReferenceError: recovered is not defined and also expected signMessage to return both a signature and recovery bit!
I have tried everything and made so many changes nothing, it always sees recovered undefined. I'm stuck on this piece of code
Thank you for guiding me.
No problem, I'm glad I could help :).
my comment is in relation to a problem I am currently experiencing that I have not yet been able to solve, I think I may have expressed myself badly.
I'm sorry, I misunderstood you. For your code and error, is there a reason why you are returning "return {messageSign, recovered}"?
Right now, you are returning an object with the result of secp.sign() and recovered, but recovered isn't declared anywhere within your function so that's why its giving you the undefined error.
For the "also expected signMessage to return both a signature and recovery bit" error, your messageSign variable already contains the signature and recovery bit as a result of secp.sign(). Basically, the result of secp.sign() contains the signature and recovery bit. To be more specific, its returning a Uint8Array with signature and the recovery bit. I would refer to the noble-secp256k1 documentation for more specifics if you want to see what secp.sign() is doing specifically.
Also, if you looks within the test.js file in the Alchemy University IDE for the Sign Message problem, you can see its testing your function with something like "const response = await signMessage('hello world');" and then later getting the signature and recovery using the destructing assignment JavaScript syntax from the result of your sign message function with "const [signature, recoveryBit] = response;".
That's why in my solution, all I'm doing is returning the result of secp.sign(). Hopefully that helps!
I don't understand, or is it because at the beginning instead of recovered I had put recoveryBit? I don't think so, or because I hadn't put my recoveryBit in an object and with true, yes I see I had rather put an integer, as I had seen in the test.js file we had put "expected the recovery bit to be a number" I still don't understand why the recovery to true.
When I arrive on your post I also test your part and it did not pass, I was overwhelmed, that's why I put my problem by returning messaSign and recovered, I was convinced that by returning messageSign alone it did not pass, now it passes and I do not see the error I made when I copied your post and it did not pass.
Thank you very much for your answer.
Hey man
I just enrolled in the course last week,
amazing resource very helpful
in my comment I presented my code and the errors it returns