Ethereum fundamentals
Keys and addresses
Private key ──[secp256k1]──▶ Public key ──[Keccak-256]──▶ Address
The address is the last 20 bytes of the Keccak-256 hash of the public key,
with the leading 0x04 uncompressed-point prefix stripped before hashing.
Private key
bcd3a9cc19cdf4401106d5315e947d5688112f0bec622d15e19d6f0afe8820c6
Public key
0x0435127b603a3f9f9e38f7683c885bcae69279a1027ccf4693d153e3afc045fc39db1c668a169993f324aa59ddfc44aa91ba178921e31738fd08d335afde244302
Address
0x28826f66983d39e0cf67df3a9e20288337d0c584
Ethereum uses Keccak-256, the original submission, not the finalised
SHA3-256 standard. The two differ in padding and produce different digests for
the same input, so a library's sha3 function may or may not be what you need —
check whether it means NIST SHA-3 or Keccak.
Generic BIP-39 helper tools often truncate or compress the public key, which yields a different address. Derive addresses with an Ethereum-aware library.
Account types
| Externally owned account (EOA) | Contract account | |
|---|---|---|
| Controlled by | A private key | Its own code |
| Can initiate a transaction | Yes | No — only reacts to calls |
| Has code | No | Yes |
| Has storage | No | Yes |
Every transaction originates from an EOA. A contract calling another contract is part of the same transaction, not a new one.
Nonce
A scalar equal to the number of transactions sent from an address, or for a contract account, the number of contracts it has created.
The nonce increments when:
- An EOA sends a transaction.
- A contract creates another contract.
The nonce does not change when:
- A contract is merely called, or a transaction is received.
- A read-only (
eth_call) query is made.
The nonce serves two purposes: it orders an account's transactions, and it makes replay impossible — a nonce can only be used once. A transaction with a nonce gap sits in the mempool until the missing one arrives, which is why one stuck transaction blocks every later one from the same account.
Contract addresses created with CREATE derive from the creator's address and
nonce, which is what makes them predictable before deployment. See
Gas optimization for the CREATE2 variant.
Gas
Gas measures computational work. Every opcode has a fixed cost, so the total is known from the execution trace.
Transaction fee = gas used × gas price
1 Gwei = 0.000000001 ETH
At 20 Gwei, one gas unit costs 0.00000002 ETH. A plain ETH transfer always costs exactly 21 000 gas, so its fee at that price is 0.00042 ETH.
The gas limit caps how much the sender is willing to spend. Unused gas is refunded; running out is not — the transaction reverts and the gas consumed up to that point is still charged.
EIP-1559 fee model
Since the London upgrade the fee is split:
fee = gas used × (base fee + priority fee)
- Base fee is set by the protocol from recent block fullness and is burned, not paid to the validator.
- Priority fee (the tip) goes to the validator and is what determines inclusion speed.
- Max fee per gas is the ceiling the sender accepts; the difference between it and the actual base fee is refunded.
Legacy gasPrice transactions still work and are treated as
maxFeePerGas = maxPriorityFeePerGas = gasPrice.
Function signatures
A function selector is the first 4 bytes of the Keccak-256 hash of the canonical signature — the name plus parameter types, no spaces, no parameter names.
setCompleted(uint256)
──[Keccak-256]──▶ fdacd5762b86e21561bc37c704505a12d8a5b260c671df7e3ed98e1cbb14f7c5
first 4 bytes ──▶ 0xfdacd576
Calldata is the selector followed by the arguments, each padded to 32 bytes:
0xfdacd576
0000000000000000000000000000000000000000000000000000000000000002
└─ the argument 2, left-padded to 32 bytes
The selector appears in the contract's bytecode because the dispatcher compares incoming calldata against each known selector.
Use uint256, not uint, when computing a selector by hand. They are the same
type but only the canonical form hashes correctly.
Selector collisions are possible — 4 bytes is small — and have been used in proxy-contract attacks. This is why the transparent proxy pattern separates admin and implementation calls.
Chain ID and network ID
They are not the same thing, and the difference matters.
- Chain ID (EIP-155) is included in the signed transaction. It prevents a transaction signed for one chain from being replayed on another.
- Network ID is used at the peer-to-peer layer for node discovery and handshakes.
Most networks set both to the same value. Ganache historically did not:
Ganache chain ID 1337 network ID 5777
A wallet that signs with the wrong chain ID produces a transaction the node rejects as invalid, which surfaces as a signature error rather than a configuration one.
Current values worth knowing:
| Network | Chain ID |
|---|---|
| Ethereum mainnet | 1 |
| Sepolia testnet | 11155111 |
| Holesky testnet | 17000 |
| Polygon mainnet | 137 |
| Polygon Amoy testnet | 80002 |
Ropsten, Rinkeby, Kovan and Goerli have all been shut down or deprecated. Sepolia is the general-purpose testnet; Holesky targets staking and validator testing.
Layer 2
Rollups execute transactions off-chain and post data back to Ethereum, which remains the settlement and data-availability layer.
| Type | How validity is established | Withdrawal delay |
|---|---|---|
| Optimistic rollup | Assumed valid; fraud proofs during a challenge window | ~7 days |
| ZK rollup | Validity proof verified on-chain | Minutes to hours |
Rollups plus data-availability sharding is the direction Ethereum's roadmap took. EIP-4844 (proto-danksharding, shipped in the Dencun upgrade) added blob transactions, which cut rollup data costs by an order of magnitude.
See Polygon for one implementation.