Skip to content

Instantly share code, notes, and snippets.

@ardislu
Last active September 23, 2023 05:15
Show Gist options
  • Save ardislu/73af6dcb35e16be4097cc38e24dae787 to your computer and use it in GitHub Desktop.
Save ardislu/73af6dcb35e16be4097cc38e24dae787 to your computer and use it in GitHub Desktop.
This is the minimum amount of JavaScript code to derive an Ethereum address from a signature, using the low-level noble-crypto libraries to minimize dependencies.
import { Signature } from '@noble/secp256k1';
import { keccak_256 } from '@noble/hashes/sha3';
// The message that will be signed
const message = 'Any arbitrary message here. 🏴‍☠️💯😂';
const encodedMessage = new TextEncoder().encode(message);
// Assuming "signature" has been acquired from the user and is in string hex form.
// Note that MetaMask and most other high-level tools will automatically inject the ERC-191 prefix to messages.
// For example, here's the minimal code to request a signature from MetaMask (assuming the below code is run in a frontend):
// const hexMessage = `0x${[...encodedMessage].map(v => v.toString(16).padStart(2, '0')).join('')}`;
// const currentAccount = (await ethereum.request({ method: 'eth_requestAccounts' }))[0];
// const signature = await ethereum.request({
// method: 'personal_sign',
// params: [hexMessage, currentAccount]
// });
// '0xd3fa...'
// Replace this with the user's signature.
const signature = '0xd3fa...';
// Convert signature hex string into noble-curves Signature object
const signatureObj = Signature.fromCompact(signature.substring(2, 130)).addRecoveryBit(signature.slice(-2) === '1b' ? 0 : 1);
// ERC-191
const personalMessage = `\x19Ethereum Signed Message:\n${encodedMessage.length}${message}`; // MUST use encodedMessage.length and not message.length
const personalMessageHash = keccak_256(new TextEncoder().encode(personalMessage));
// Recover secp256k1 public key
const signingPublicKey = signatureObj.recoverPublicKey(personalMessageHash);
const serializedPublicKey = signingPublicKey.toRawBytes(false).slice(1); // MUST use uncompressed public key, AND drop the first byte (0x04)
// The public address is the last 20 bytes of the keccak256 hash of the public key
const signingAddress = `0x${[...keccak_256(serializedPublicKey).slice(-20)].map(v => v.toString(16).padStart(2, '0')).join('')}`;
console.log(signingAddress);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment