Keys and Signatures in Ethereum (Node.js)[Node.js Home][Home]
Node.js is a great advancement in creating back-end code. So, let's use it to understand how Ethereum uses its keys and signs for messages. Overall, we use public-key encryption and which uses the elliptic curve that Satoshi Nakamoto selected for Bitcoin: secp256k1. With this, we have a random private key of 32 bytes (256 bits). If we run our local blockchain with Ganache, it will show us a number of account addresses. In this we will create a random private key, and then sign for a message.
|
Outline
Node.js is a great advancement in creating back-end code. So, let's use it to understand how Ethereum uses its keys and signs for messages. Overall, we use public key encryption and which uses the elliptic curve that Satoshi Nakamoti selected for Bitcoin: secp256k1. With this we have a random private key of 32 bytes (256 bits). If we run our local blockchain with Ganache, it will show us a number of account addresses:
This is a public identifier for each user. Underneath this is a private key, and which should not be exposed to anyone:
This private key is used to sign of transactions, and has an associated public key. The conversion of the private address to a public address is to perform an ECDSA signature, and then take a hash of this with Keccak-256. The address is the first 40 bytes of this signature:
The great thing about this public address, is that the public key can be easily extracted from the identifier. This allows everyone to check the signature on a transaction. In the following we use Node.js to generate a private key, and then sign for a message:
const EthCrypto = require("eth-crypto"); const crypto = require("crypto"); var message="Hello"; var args = process.argv; if (args.length>2) message=args[2]; const privateKey = crypto.randomBytes(32).toString("hex"); const publicKey = EthCrypto.publicKeyByPrivateKey(privateKey); const address = EthCrypto.publicKey.toAddress(publicKey); const hash = EthCrypto.hash.keccak256 (message); const signature = EthCrypto.sign(privateKey, hash); console.log(`Private key: ${privateKey}\n`); console.log(` Public key: ${publicKey}\n`); console.log(` Signer address ${address}\n`); console.log(`Message: ${message}\n`); console.log(`Hash: ${hash}\n`); console.log(`Signature: ${signature}\n`); console.log(`--- Now checking signature ---`); const signer = EthCrypto.recoverPublicKey(signature, hash); console.log(`Public key recovered: ${signer}`); const signerAddress = EthCrypto.recover(signature,hash); console.log(`Sender (recovered): ${signerAddress}`); console.log("\n\nNow we will encrypt ..."); go(); async function go() { cipher = await EthCrypto.encryptWithPublicKey(publicKey,message); console.log("Cipher: ",cipher) plain = await EthCrypto.decryptWithPrivateKey(privateKey,cipher); console.log("Decryption: ",plain) }
We generate a random 32 byte key with:
const privateKey = crypto.randomBytes(32).toString("hex");
And then generate the public key and the address from:
const publicKey = EthCrypto.publicKeyByPrivateKey(privateKey); const address = EthCrypto.publicKey.toAddress(publicKey);
Next we can take a message, and generate a hash of this, and then generate a signature. This requires the private key of the user:
const hash = EthCrypto.hash.keccak256 ([{type: "string", value: message},]); const signature = EthCrypto.sign(privateKey, hash);
We can check the public key of a signer by recovering it from the signature and the hash of the message:
const signer = EthCrypto.recoverPublicKey(signature, hash);
Finally we can check the signature, by recovering the identity of the signer:
const senderAddress = EthCrypto.recover(signature,hash);
And that's basically it for keys and signatures in Ethereum. A sample run is:
Private key: 2b6ceaa26d40398a5d4072fc255d2582335ddbc9d0db1cd8c721f485395407c4 Public key: be3335c09520a160bd3fc2faed453be39cea34dbbd4b131eff25160eb78ba28d4c08a314d19fc9579f9dee164e25784990333f2dd7c1ddace41e04cff0b49a8a Signer address 0x669654868972029F1Bd75c1b5a7A2cB42Dcd70E3 Message: Test Hash: 0x85cc825a98ec217d960f113f5f80a95d7fd18e3725d37df428eb14f880bdfc12 Signature: 0xd82f3bef84da99d909d973b3caf37231e4db97ec606ba2f0f6ac820b61c86d1741cc7fbe92fc145d59d14d73a9465437b2ee1a2f50018be839256013a6be231c1b --- Now checking signature --- Public key recovered: be3335c09520a160bd3fc2faed453be39cea34dbbd4b131eff25160eb78ba28d4c08a314d19fc9579f9dee164e25784990333f2dd7c1ddace41e04cff0b49a8a Sender (recovered): 0x669654868972029F1Bd75c1b5a7A2cB42Dcd70E3 Now we will encrypt ... Cipher: { iv: '26dab379573738a107fa3150b037f79b', ephemPublicKey: '047329eb00a6787a3e13321b9894db3c00539d3cf14fc685acdd7bebab5e22457f35494d461f0664c070fd8a86914ab67626cdc8cb61431184405d0a0d99e52eef', ciphertext: '0b9eb5c69899c912bc198b843bea930f', mac: 'ae430fdeb89c4d6e2ac6344de95138b0cd361ad7cf9383c9747c64bb778c49cd' } Decryption: Test
For encryptio and decryption we use:
async function go() { cipher = await EthCrypto.encryptWithPublicKey(publicKey,message); console.log("Cipher: ",cipher) plain = await EthCrypto.decryptWithPrivateKey(privateKey,cipher); console.log("Decryption: ",plain) }
This uses ECIES to encrypt and decrypt, and which uses symmetric key encryption (AES). We thus have an IV for the encryption process. This is HMAC-SHA-256 (HMAC) for MAC and AES-256-CBC for the encryption. An example of the sample run shows this:
Cipher: { iv: '26dab379573738a107fa3150b037f79b', ephemPublicKey: '047329eb00a6787a3e13321b9894db3c00539d3cf14fc685acdd7bebab5e22457f35494d461f0664c070fd8a86914ab67626cdc8cb61431184405d0a0d99e52eef', ciphertext: '0b9eb5c69899c912bc198b843bea930f', mac: 'ae430fdeb89c4d6e2ac6344de95138b0cd361ad7cf9383c9747c64bb778c49cd' }