← Web3 Dev: Zero to On-Chain
Web3Web3 Dev: Zero to On-Chain

Seed Phrases, Private Keys, Addresses: What 'You' Actually Are On-Chain

Part 2 of the Web3 Dev: Zero to On-Chain series: a one-way pipeline turns 12 English words into your on-chain identity — BIP-39 mnemonics, HD wallet derivation paths, secp256k1 and Keccak-256, the three generations of signing and their security grades, plus hands-on key generation, signing, and verification with cast.

Web3WalletBIP-39ECDSAEIP-712Foundry

In Web2, “you” are a row in a database: an email, a password hash, a user ID. Forget the password and you reset it; get hacked and you call support — because ultimately the platform decides who you are.

In Web3, “you” are no record at all. You are a 256-bit random number and everything derived from it one-way. No company’s database holds you, and no support desk can “recover your account” — that is the real meaning of “there’s no forgot password” from Part 1. This part dissects the full derivation chain: how a seed phrase becomes a private key, how the key becomes an address, why a signature equals identity — and then you’ll walk every step yourself with cast.

A One-Way Pipeline

Get the whole picture first, details after. From “12 English words” to “an 0x-prefixed address,” every stop along the way is a one-way function — instant to compute forward, infeasible to reverse:

Four standards each own one stop. The names are worth memorizing — you’ll meet them in every wallet’s documentation:

BIP-39 (mnemonic → seed). The wallet generates 128 bits of true random entropy, appends a 4-bit checksum to make 132 bits, slices it into twelve 11-bit groups, and looks each up in a fixed 2048-word list — that’s your 12-word phrase. The checksum means a mistyped word fails loudly at entry instead of silently opening someone else’s empty wallet. Then the phrase (optionally plus a passphrase) is stretched through 2,048 rounds of PBKDF2-HMAC-SHA512 to produce a 512-bit seed.

BIP-32 / BIP-44 (seed → an entire key tree). The seed isn’t used directly — it roots a Hierarchical Deterministic (HD) key tree. Path conventions are industry-standard: m/44'/60'/0'/0/0, where 60' is Ethereum’s coin type and the last segment is the account index. That’s why one mnemonic yields unlimited accounts — clicking “create account” in your wallet just increments the index, all recoverable from the same backup.

secp256k1 (private key → public key). Ethereum and Bitcoin share the same elliptic curve. The private key is that 256-bit random number (64 hex characters); the public key comes from elliptic-curve multiplication — easy forward, reverse is the discrete-logarithm problem. With current hardware, brute-forcing 128 bits of entropy takes longer than the age of the universe.

Keccak-256 (public key → address). Hash the public key with Keccak-256, take the last 20 bytes, prepend 0x, and you have the 42-character address. The mixed-case addresses you see (like 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045) are EIP-55 checksums: the casing itself encodes validation data, so wallets catch a one-character copy error.

Only the private key and seed phrase need secrecy; public keys and addresses are meant to be seen. Remember the industry’s one-liner: Not your keys, not your coins — your entire ownership of on-chain assets is control over this key pair. Nothing else.

Signing: Prove It’s You Without Handing Over the Key

The private key’s core job isn’t decryption — it’s signing. This is where beginners get confused most, so it deserves its own section.

When you “sign” a message: the message is hashed, the private key runs ECDSA over the hash, and out comes a 65-byte signature (three components v, r, s). Anyone holding “message + signature” can run ecrecover to derive the signer’s address and compare it with the claimed one — proving whether you signed it. Nobody ever needs to see the private key. That is the cryptographic meaning of “a signature is identity.”

But not all signature popups are equally safe. Ethereum has three generations of signing, with wildly different security grades:

  1. eth_sign (blind signing, dangerous) — signs an arbitrary 32-byte hash; the wallet shows you unreadable hex. You might be authorizing a transfer without knowing it. MetaMask now shows a red warning for it. See it, run.
  2. personal_sign (EIP-191, readable) — the message is prefixed with "\x19Ethereum Signed Message:\n" before hashing, so the wallet shows plaintext. Common for “Sign-In with Ethereum.” The prefix guarantees the signature can never be replayed as a real transaction.
  3. EIP-712 (structured signing, preferred) — you sign typed structs the wallet can render field by field. Crucially, the domain separator binds the signature to a specific chain ID and contract address — an approval you sign for app A is worthless if an attacker replays it on app B or another chain. ERC-20 permit, Uniswap’s Permit2, and OpenSea orders are all built on it.

Once more, the vaccine from Part 1: signing is not sending a transaction. A signature itself touches no chain, spends no gas, leaves no record — the danger is that the “authorization” you signed can later be submitted on-chain by anyone. So the rule stands: if you can’t read the signature request, reject it.

Hands-On: Walk the Key Pipeline with cast

Talking without doing is the cardinal sin of learning Web3. Open a terminal and generate a key set with the Foundry you installed in Part 1 (relax — these are just random numbers, no real money involved):

# Generate a fresh random key pair (private key + address)
cast wallet new

# Or generate a BIP-39 mnemonic
cast wallet new-mnemonic

# Given a private key, derive the address (proving key → address is deterministic)
cast wallet address --private-key 0xyour_private_key

# Sign a message with the private key (personal_sign format)
cast wallet sign "hello, chain" --private-key 0xyour_private_key

# Anyone can verify: signature + message → recover the address
cast wallet verify 0xyour_address "hello, chain" 0xsignature_from_previous_step

Run these four commands and you’ve touched every concept in this article: key generation, address derivation, signing, verification.

One step further — store this dev key as an encrypted keystore (a password-protected JSON file), so you never paste a plaintext private key into your command line when deploying contracts:

# Interactive import; neither the key nor the password lands in shell history
cast wallet import dev-account --interactive

# Confirm it's there
cast wallet list

The keystore lives in ~/.foundry/keystores/ and is encrypted at rest — even if the file is stolen, it’s useless without the password. This is the standard posture for every “needs a private key” operation in the rest of this series.

Three Rules of Key Hygiene

This is the most security-dense article of the series. The rules get set here and won’t be repeated:

  1. Physically isolate dev wallets from real assets. The wallet you create for this series holds test tokens only. Never import your real wallet’s mnemonic or private key into a development environment.
  2. Private keys never enter code or Git. When a test script needs a key, use the keystore (--account dev-account) or environment variables, and add .env to .gitignore. Bots scan new GitHub commits for private-key formats around the clock; leaked balances are typically drained within minutes — that’s not legend, it’s an automated industry.
  3. Seed phrases live on paper only. Handwritten, copied twice, stored apart. No screenshots, no photos, no cloud notes, no “support agents.” Anyone who asks for your seed phrase is a scammer — one hundred percent, no exceptions.

Exercises, and Part 3

  1. Run the full cast wallet new → sign → verify flow. Then change a single character in the message and verify again — watch it fail, and confirm with your own hands that “a signature binds every byte of the message.”
  2. Generate a mnemonic with cast wallet new-mnemonic, then derive a second account address with --mnemonic plus --mnemonic-index 1, and compare it with index 0 — see “one mnemonic, unlimited accounts” with your own eyes.
  3. Look up your dev address on the Sepolia explorer. It’s probably empty with no transactions — notice that an address “existing” requires no registration; it’s simply the inevitable output of cryptographic derivation. Once your Part 1 faucet tokens arrive, refresh and see which fields appear.

In Part 3 we put the keys in motion: what a transaction actually looks like, why a nonce defeats replay, how gas fees are computed, which gates a transaction passes from signing to inclusion — and the EOA/contract-account twins that define the entire EVM programming model.

guest@swangnice:~$