Message Signing Implementation (EIP-191/EIP-712) in Mobile Wallet
Message signing — not transaction signing. User confirms arbitrary payload: login to dApp, off-chain DEX order, token transfer approval. Wrong implementation means signature for "harmless" message can be reused for unwanted action.
EIP-191 vs EIP-712 — Practical Difference
EIP-191 — basic standard. Personal sign adds prefix "\x19Ethereum Signed Message:\n" + len(message) before hashing. Protects from replay: raw message signature can't be reused as transaction signature because formats differ.
keccak256("\x19Ethereum Signed Message:\n" + message.length + message)
EIP-712 — structured data. Instead of string, sign typed structure:
keccak256("\x19\x01" + domainSeparator + hashStruct(message))
domainSeparator includes chainId, verifyingContract, name — binds signature to specific contract and network. Signature for contract A on Ethereum mainnet won't be accepted by contract B or testnet.
EIP-712 Implementation on Mobile
Complexity in correctly hashing structures. hashStruct recursive — nested types hash separately. Common mistake — not including nested type in encodeType:
For structure:
Mail { Person from; Person to; string contents }
Person { address wallet; string name }
typeHash for Mail must include "Mail(Person from,Person to,string contents)Person(address wallet,string name)" — both types alphabetically with nested.
In React Native use @metamask/eth-sig-util or ethers.js v6 TypedDataEncoder. On Flutter — native plugin or web3dart with custom EIP-712 hasher (few ready solutions, write custom per spec).
UI for Message Signature
User must see what they sign. For EIP-712 — decoded structure with readable fields, not hex string. Minimum:
- dApp name + domain from
domain.name - Operation type from structure types
- Key fields: addresses, amounts, deadline
MetaMask shows full structure tree. For mobile UI highlighting critical fields sufficient, rest under "Show Details" button.
Biometry / PIN before signing — mandatory like transactions.
Contract Verification
After mobile signing, contract must verify:
function verify(address signer, Mail calldata mail, bytes calldata signature) public view returns (bool) {
bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
keccak256("Mail(address from,address to,string contents)"),
mail.from, mail.to,
keccak256(bytes(mail.contents))
)));
return signer == ECDSA.recover(digest, signature);
}
Test compatibility: mobile signature → contract verification in Hardhat test. Only reliable way ensuring hashes match.
Timeline — 2–3 days: EIP-191 for personal sign, EIP-712 for specific structures, UI with decoding, biometric confirmation, contract test.







