Revision — Cryptography & Keys
The first part asked why Solana exists — one global state machine, run at hardware speed, that must not fall apart. This part answered a smaller but load-bearing question underneath it: who is allowed to change that shared state, and how does everyone prove it without a central authority to ask? Every page since the overview has been building one answer — that identity and authorization are pure cryptography, not a lookup in some registry — and now it is time to hold the whole answer in your head at once.
So put the diagrams down and read this as a story. A single global machine has exactly one copy of every balance and every account. If anyone could rewrite anyone’s account, there would be nothing to defend and nothing worth building. The cryptography in this part is the entire fence: a keypair is an identity, a signature is a permission slip for one exact action, and a Program Derived Address is how a program owns state without a human holding a key. Four ideas, one fence. If you can retell all four from memory, you are ready for Part 3, where transactions start bundling these signatures into the units the runtime actually executes.
Identity is an ed25519 keypair
Section titled “Identity is an ed25519 keypair”Start where Ed25519 Keypairs started. On Solana there is no account you “sign up” for and no username the network stores. An identity is nothing but a keypair: a 32-byte secret you keep, and a 32-byte public key you can hand to the world. The secret can produce signatures; the public key can only check them. That asymmetry — easy to verify, infeasible to forge — is the one-way property the whole system leans on.
secret key (32 bytes) ── kept private, never leaves the wallet │ │ derived one-way (scalar × basepoint on the curve) ▼ public key (32 bytes) ── shared freely; this IS your identityThe part was careful about which curve, because the choice is not cosmetic. Bitcoin and Ethereum sign with secp256k1; Solana chose ed25519, and it chose it for reasons that serve the throughline directly:
- Determinism. Ed25519 signing is deterministic — the same key and message always produce the same signature, with no per-signature random nonce. A single global machine that thousands of validators must replay byte-for-byte cannot tolerate a signature scheme where a bad random number leaks the secret key (the flaw that has burned secp256k1 implementations more than once). Determinism removes an entire class of catastrophic footgun.
- Speed. Ed25519 verification is fast and, crucially, batch-verifiable — a validator draining a firehose of transactions can check many signatures together far cheaper than one at a time. When the goal is hardware speed, signature verification cannot be the bottleneck.
- Fixed sizes. Keys are always 32 bytes, signatures always 64 bytes, no encoding variants, no recovery-id bookkeeping. Fixed sizes make transactions predictable to parse and cheap to lay out in memory — exactly what a pipeline that fetches and verifies in parallel wants.
The one-line summary: your identity is a 32-byte public key, backed by a 32-byte secret, on a curve chosen for determinism, speed, and fixed sizes. Everything else in this part is what you can do with that pair.
The address IS the public key
Section titled “The address IS the public key”Addresses ARE Public Keys delivered the part’s most surprising simplification, and it is worth stating as bluntly as the page did: on Solana, there is no address derivation. The address of an account is the raw 32-byte public key itself, written in Base58 so a human can copy it.
This is a real departure from Bitcoin and Ethereum, and the contrast is the fastest way to remember it:
Bitcoin / Ethereum Solana ────────────────── ────── pubkey pubkey (32 bytes) │ hash (SHA-256/RIPEMD-160, │ │ or Keccak-256) │ just re-encode the SAME bytes ▼ ▼ address ≠ pubkey address == pubkey, in Base58 (a fingerprint of the key) (the key itself, human-readable)There is no hash pipeline, no checksum-wrapped fingerprint, no “pay-to-hash” indirection. Base58 is only an encoding — the same alphabet Bitcoin uses, minus the visually ambiguous characters (0, O, I, l), plus a leading-zero convention — chosen so an address is copy-paste-safe, not so it hides anything. Decode the Base58 and you are holding the exact 32 bytes the curve verifies against.
The consequence is the sentence to carry forward: the address you pay is the same bytes that check the signature. There is no step where a key gets fingerprinted into an address and the link is obscured. To spend from an account whose address is X, you produce a signature that verifies under public key X — and X is the address. The fence and the nameplate are the same object.
Signing proves authorization of one exact message
Section titled “Signing proves authorization of one exact message”Message Signing & Verification turned the keypair from a static identity into an action. A signature is the mechanism by which the holder of a secret key says, in a way anyone can check but no one can forge, “I authorize this specific message.”
Three facts, and none of them are optional:
- A signature is 64 bytes, produced from
sign(secret_key, message). - Anyone can run
verify(public_key, message, signature)and get back a plain yes or no — no secret is needed to verify, only the public key that everyone already has. - The signature is bound to the exact message. Flip a single bit of the message and the signature no longer verifies. You cannot lift a valid signature off one transaction and staple it onto another; it authorizes that message and nothing else.
signer any verifier ────── ──────────── message m message m secret sk public pk (== the address) │ │ ▼ ▼ sig = sign(sk, m) ───── 64 bytes ────► verify(pk, m, sig) → true / false tamper with m → false wrong pk → falseThis is where the fence does its work. A Solana transaction names the accounts it touches and marks some of them as required signers. The runtime will not execute the transaction until it has checked a valid signature for every account flagged as a signer, against the exact bytes of the message being run. That single gate is the entire authorization model of the state machine: to move lamports out of an account, to let a program mutate data it owns on your behalf, to do anything that needs your say-so — you must have signed the precise message that does it. No signature, no execution. It is checkable by anyone, forgeable by no one, and it is the reason a shared global ledger can let strangers touch the same state without trusting each other.
Program Derived Addresses: keys without a private key
Section titled “Program Derived Addresses: keys without a private key”The last idea, from Program Derived Addresses and Deriving a PDA, closes a gap the first three ideas open. If every address is a public key with a secret behind it, then only whoever holds a secret can authorize an action. But programs are stateless code — they hold no secrets. How can a program own an account and sign for it?
The answer is a beautiful piece of misdirection: derive an address that is a valid 32-byte public key but has no matching secret key, on purpose. These are Program Derived Addresses (PDAs) — addresses that are provably off the curve.
Recall that a real public key is a point that lies on the ed25519 curve; that on-curve-ness is exactly what makes it verifiable, and every on-curve point has some scalar secret behind it. A PDA is computed by hashing together seeds (bytes the program picks, like [b"vault", user_pubkey]) plus the program’s own id. A hash output is just 32 random-looking bytes, and here is the load-bearing fact: most 32-byte values are not valid curve points — only about half of all 32-byte strings land on the ed25519 curve. So the derivation deliberately searches for a result that falls off the curve.
on-curve point → a normal public key → a secret key exists → a human can sign OFF-curve value → a Program Derived Address → NO secret can exist → only the program can "sign" for it
hash( seeds ++ program_id ++ bump ) → 32 bytes │ ├─ lands ON the curve? → reject, try again └─ lands OFF the curve? → accept: this is the PDAThe retry is not random — it is the bump seed. The derivation starts with a bump of 255 and appends it to the seeds before hashing; if the result lands on the curve (unusable, because someone could theoretically hold its key), it decrements the bump to 254, 253, … and hashes again, stopping at the first value that lands off the curve. That first success is the canonical bump. Because hashing is deterministic, anyone who knows the seeds and the program id recomputes the exact same PDA and the exact same bump — no registry, no coordination, just arithmetic everyone can reproduce.
And because a PDA has no secret key, no user can ever sign for it. Instead, the runtime lets the owning program authorize actions on its PDAs — the program presents the seeds, the runtime re-derives the off-curve address, confirms it belongs to that program, and treats it as signed. This is how a program owns a vault, a token account, a per-user record — deterministic, unforgeable addresses that answer only to the program that derived them. A program gets to be an identity without ever holding a private key.
The thread back to the throughline
Section titled “The thread back to the throughline”Step back and look at what these four ideas built together.
A single global state machine has one problem no amount of raw speed solves: it has exactly one copy of everything, so it needs an airtight, decentralized answer to “who may change this?” This part is that answer, with no registry anywhere in it:
- An identity is a 32-byte public key, backed by a 32-byte secret, on ed25519 — deterministic, fast to batch-verify, fixed-size, so signature checking never becomes the bottleneck that a hardware-speed pipeline can’t afford.
- An address is that public key itself, in Base58 — so the bytes you pay are the bytes that verify, with no fingerprinting step to obscure the link.
- A signature is a 64-byte proof that authorizes one exact message, checkable by anyone with the public key, and it gates every required signer on every transaction.
- A PDA extends ownership to code — off-curve addresses with no secret, derived deterministically from seeds and a program id via a decrementing bump — so programs can own and sign for their own state.
Notice there is nowhere in that list where the network stores a table of “who owns what.” Ownership is a public key. Authorization is a signature over the exact action. Program ownership is an off-curve address anyone can re-derive. The entire trust model reduces to math that every one of thousands of validators can check independently and get the same answer — which is precisely the property a single global machine needs if it is going to run at hardware speed without a central authority and without falling apart.
That is the identity and authorization layer, and it is now complete. What we have not done yet is package these signatures into the thing the runtime actually schedules and executes. That is the job of the next part. Transactions takes the required-signer gate you just learned, wraps it around a list of instructions and the accounts they touch, anchors it to a recent blockhash so it can’t be replayed, and hands the runtime a footprint it can execute in parallel. The keys and signatures gave the state machine its ownership model; transactions are how that model gets spent. Keep the fence in mind as you go — every field you meet in a transaction is either naming the state it will touch or proving it is allowed to.