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.
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).
Then you can check the recovered public key as part of the transaction verification: