Reputation: 4058
Let's say we have this code to sign a simple transaction in Ethereum and then verify the signature, using the Elliptic Curve Digital Signature Algorithm (ECDSA), just like in real life:
const { secp256k1 } = require("ethereum-cryptography/secp256k1");
const { keccak256 } = require("ethereum-cryptography/keccak");
const { toHex, utf8ToBytes } = require("ethereum-cryptography/utils");
const privateKey = secp256k1.utils.randomPrivateKey();
console.log('private key : ', toHex(privateKey));
const publicKey = secp256k1.getPublicKey(privateKey);
console.log('public key :', toHex(publicKey));
const transaction = { message: 'hello world!' };
const data = JSON.stringify(transaction);
const hash = keccak256(utf8ToBytes(data));
const signature = secp256k1.sign(hash, privateKey);
console.log("payload :", { transaction, signature });
Then I send the transaction
and signature
through the network and use this code to recover the public key and verify the signature:
const pkHonest = signature.recoverPublicKey(hash).toRawBytes();
console.log('recovered :', toHex(publicKey) === toHex(pkHonest)); // true
console.log('verified :', secp256k1.verify(signature, hash, pkHonest)); // true
So far so good.
But what if I (or someone in the middle) change the content of the transaction? You would expect the signature verification to fail, but it retrieves a different public key and the verification is successful.
const dataEvil = "modified";
const hashEvil = keccak256(utf8ToBytes(dataEvil));
const pkEvil = signature.recoverPublicKey(hashEvil).toRawBytes();
console.log('recovered :', toHex(publicKey) === toHex(pkEvil)); // false
console.log('verified :', secp256k1.verify(signature, hashEvil, pkEvil)); // true... why?
It doesn't seem right to me, but I can't figure out what I'm doing wrong.
Upvotes: 0
Views: 122
Reputation: 4058
As mentioned in the comments, you can send the public key (or address) as part of the transaction body (normally the address is sent instead of the public key, I have simplified this example for convenience).
const transaction = {
from: toHex(publicKey),
to: '...',
value: '...',
nonce: 0
};
Then you can check the recovered public key as part of the transaction verification:
const getTransactionCount = from => 0; // TODO: Implement properly
const getBalance = from => 0; // TODO: Implement properly
const { from, to, value, nonce } = transaction;
const recovered = signature.recoverPublicKey(hash).toRawBytes();
if (toHex(recovered) !== from) {
console.error("Sender does not match");
} else if (secp256k1.verify(signature, hash, recovered) !== true) {
console.error("Invalid signature");
} else if (nonce !== getTransactionCount(from)) {
console.error("Invalid nonce");
} else if (getBalance(from) < value) {
console.error("Not enough funds");
} else {
console.log("Transaction verified!");
}
Upvotes: 0