By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.
Public?key cryptography lets anyone generate a pair of keys: a public key that can be shared openly and a private key that must stay secret. In Ethereum the private key signs transactions; the network verifies the signature with the public key (actually the derived address). This mechanism underpins every trust?less action—whether a user swaps tokens on Uniswap, mints an NFT, or casts a vote in a DAO—because it proves who sent the transaction without a central authority.
ECDSA (Elliptic Curve Digital Signature Algorithm): The signature scheme Ethereum uses (secp256k1 curve). It turns a 32?byte hash into a (v,r,s) tuple that can be recovered on?chain. solidity // Solidity: recover signer address from a signed message function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) public pure returns (address) { return ecrecover(hash, v, r, s); }
(v,r,s)
solidity // Solidity: recover signer address from a signed message function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) public pure returns (address) { return ecrecover(hash, v, r, s); }
Private Key: A 256?bit random number that must never be exposed. It is the seed for all signatures and the only thing that can move funds from an address.
Public Key-Address: Ethereum derives the address by keccak256(publicKey)[12:]. The address is what you share with the world.
keccak256(publicKey)[12:]
Message Hash (keccak256): Before signing, data is hashed with Keccak?256 (Ethereum’s SHA?3 variant). The hash is what actually gets signed, not the raw data. js const ethers = require('ethers'); const msg = "I approve Uniswap swap #42"; const hash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(msg));
keccak256
js const ethers = require('ethers'); const msg = "I approve Uniswap swap #42"; const hash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(msg));
Signature (v, r, s): The three components returned by eth_sign or wallet.signMessage. v is the recovery id (27/28 or 0/1), r and s are 32?byte values.
v, r, s
eth_sign
wallet.signMessage
v
r
s
eth_sign vs personal_sign vs eth_signTypedData: Different JSON?RPC methods that sign raw data, a prefixed message, or a typed EIP?712 struct. Use personal_sign for simple messages and eth_signTypedData for structured data (e.g., DAO proposals).
personal_sign
eth_signTypedData
ecrecover (Solidity built?in): Takes a hash and a signature and returns the address that created it. It’s the on?chain verification primitive.
ecrecover
Nonce: A per?account counter that prevents replay attacks. Each signed transaction must include the current nonce; the network increments it after a successful inclusion.
Replay Attack: Re?using a valid signature on another chain or after a nonce reset. Mitigate by signing chain?specific data (include chainId) and by never re?using the same signed payload.
chainId
chainId (EIP?155): Embedded in the transaction signature to bind a signature to a specific network (e.g., 1 for Mainnet, 5 for Goerli). Prevents cross?chain replay.
Hardware Wallet (Ledger/Trezor): Stores the private key in a secure element and performs signing internally, exposing only the signature to the host computer.
js const wallet = ethers.Wallet.createRandom(); // returns mnemonic, privateKey, address
js const proposal = { id: 7, title: "Fund Dev", amount: ethers.utils.parseEther("10") }; const typedData = { types: { Proposal: [{ name: "id", type: "uint256" }, { name: "title", type: "string" }, { name: "amount", type: "uint256" }] }, primaryType: "Proposal", domain: { name: "MyDAO", version: "1", chainId: 5, verifyingContract: "0xABC…" }, message: proposal, };
wallet._signTypedData
js const signature = await wallet._signTypedData(typedData.domain, typedData.types, typedData.message);
vote(uint256 proposalId, bytes signature)
js const tx = await daoContract.vote(7, signature); await tx.wait();
ECDSA
solidity function vote(uint256 proposalId, bytes memory sig) external { bytes32 hash = keccak256(abi.encodePacked(address(this), proposalId)); address signer = ECDSA.recover(hash, sig); require(signer == member, "Not a member"); // record vote… }
Mistake: Signing raw transaction data with eth_sign and then sending it to a contract that expects an EIP?712 typed hash. Correction: Always match the signing method to the contract’s verification logic; use eth_signTypedData for typed structs and personal_sign for simple messages.
Mistake: Storing the private key in plain text (e.g., in a .env file). Correction: Use a hardware wallet or an encrypted secret manager (AWS KMS, HashiCorp Vault). Plain?text keys are a single point of failure.
.env
Mistake: Forgetting to include chainId in the signed payload, leading to cross?chain replay attacks. Correction: Always sign the EIP?155 replay?protected format ({nonce, gasPrice, gasLimit, to, value, data, chainId}) or include chainId in your custom hash.
{nonce, gasPrice, gasLimit, to, value, data, chainId}
Mistake: Assuming ecrecover returns the public key; it actually returns the address derived from the public key. Correction: If you need the full public key, use secp256k1 libraries off?chain; on?chain you only get the address, which is sufficient for most auth checks.
secp256k1
Mistake: Using tx.origin for access control. Correction: Always use msg.sender because tx.origin can be spoofed through a malicious contract that forwards calls.
tx.origin
msg.sender
Explain the difference between call and delegatecall. Interviewers look for understanding that call runs code in the callee’s context (its storage, msg.sender), while delegatecall runs in the caller’s context, allowing proxy patterns but also opening up storage?collision risks.
call
delegatecall
Why is EIP?155 important for signatures? It binds a signature to a specific chainId, preventing replay attacks across networks. Auditors will check that every signed transaction includes the correct chainId.
Distinguish ERC?20 vs ERC?777. ERC?777 adds hooks (tokensReceived) and a richer operator model, enabling “send?and?receive” callbacks that ERC?20 lacks. Knowing the extra security surface (re?entrancy via hooks) is a plus.
tokensReceived
operator
Optimistic Rollup vs ZK Rollup trade?offs. Optimistic rollups assume transactions are valid and provide a fraud?proof window; ZK rollups generate succinct validity proofs on?chain. Interviewers may ask which is better for latency?sensitive DeFi vs privacy?preserving applications.
Scenario: A contract uses tx.origin to restrict a function to the contract owner. Answer: Dangerous – a malicious contract can call the vulnerable contract on behalf of the owner, making tx.origin equal the owner’s address and bypassing the check.
Scenario: You receive a signature (v,r,s) from a user, but the v value is 0 instead of 27/28. Answer: Modern clients return 0/1; you must add 27 before calling ecrecover, otherwise the recovered address will be wrong.
0
27/28
0/1
27
Scenario: A developer signs a message with eth_sign and later re?uses the same signature on a different chain. Answer: This is a replay attack; because the signature lacks a chainId, the attacker can replay it on any network that accepts the same message format.
ecrecover(hash, v, r, s)
"\x19Ethereum Signed Message:\n"
Join 4M+ learners. Unlock unlimited quizzes, wrong-answer tracking, flashcards + reminders, study guides, and 1-on-1 challenges.