The Undocumented Secret to Hedera Message Signature Verification
Source: Dev.to
Problem
I was building a P2P NFT swap on Hedera. Everything worked, but message signature verification kept failing.
// Frontend
const signature = await wallet.signMessage("Sign in to MichiMint");
// Backend
const isValid = pubKey.verify(
Buffer.from("Sign in to MichiMint"),
signature
);
// false ❌
Same message, fresh signature, correct public key—why was verification false?
Solution
Hedera wallets prepend a hidden prefix to the signed message.
function prefixMessage(message: string): string {
return '\x19Hedera Signed Message:\n' + message.length + message;
}
Verify using the prefixed message:
// Backend verification
const prefixedMessage = prefixMessage("Sign in to MichiMint");
const isValid = pubKey.verify(Buffer.from(prefixedMessage), signature);
// true ✅
Explanation
\x19– magic byte that prevents transaction hash collisions.Hedera Signed Message:\n– context identifier.22– length of the original message ("Sign in to MichiMint").- The original message follows.
Hedera adopted Ethereum’s EIP‑191 standard for signed messages, assuming developers would be aware of the prefix. Without it, signatures could be replayed in different contexts, making them insecure.
Impact
This insight powers MichiMint – the first trustless P2P NFT swap on Hedera using Scheduled Transactions.