Addresses ARE Public Keys (Base58)
The previous page left us holding a keypair: a 32-byte public key and the 32-byte secret that can sign for it. The natural next question is the one every payment system has to answer — when I want to receive funds, what string do I hand someone? On Bitcoin the answer goes through a small pipeline of hashes. On Solana the answer is almost anticlimactic: you hand them the public key itself. There is no hashing step. The address IS the public key.
That single sentence is doing a lot of work, and it is worth slowing down for, because it shapes everything downstream in this part — how signatures are verified, what a “program id” is, and how Program Derived Addresses can exist at all. This page nails down exactly what a Solana address is, what Base58 does (and, importantly, does not) add, and why the design diverges from Bitcoin. It all serves the book’s throughline: to run a single global state machine at hardware speed, you want the cheapest possible path from “an address” to “verify this signature” — and Solana got there by removing a layer.
The core claim: the address is the raw key
Section titled “The core claim: the address is the raw key”A Solana address is a 32-byte ed25519 public key, presented to humans as a Base58 string. Nothing is hashed. Nothing is truncated. The bytes you see encoded in the address are exactly the bytes of the public key.
ed25519 keypair ┌───────────────────────────────┐ │ secret key (32 bytes) ── kept │ │ public key (32 bytes) ── shown │──► Base58 encode ──► "9xQe…FmP4c" └───────────────────────────────┘ (~44 chars) ▲ ▲ this IS the address same bytes, human-safeContrast that with Bitcoin, which runs the public key through two cryptographic hash functions before it ever becomes an address:
Bitcoin (P2PKH): public key ─► SHA-256 ─► RIPEMD-160 ─► "hash160" (20 bytes) ─► Base58Check ─► "1A1zP…" └────────────── the address is a HASH of the key ──────────────┘
Solana: public key ───────────────────────────────────────────────► Base58 ─► "9xQe…FmP4c" └────────────── the address IS the key ────────────────────────┘The Bitcoin address is a fingerprint of the key. The public key stays hidden inside your wallet until the moment you spend, at which point you reveal it and the network checks that its hash matches the address you’re spending from. The Solana address skips the fingerprint entirely — the key is already on the table.
Why remove the hash?
Section titled “Why remove the hash?”The obvious question is why would you throw away the hash step Bitcoin fought to include? The answer is that the two chains are optimizing for different things.
Bitcoin’s hash pipeline buys two things: a shorter address (20 bytes instead of 32) and a quantum margin. Because the public key is hidden behind a hash until spend, a hypothetical quantum attacker who can derive private keys from public keys still can’t touch a never-spent address — they’d have to break the hash first. That is a real, if speculative, hedge.
Solana spends that hedge to buy simplicity and directness. When the address is the key:
- Verifying a signature needs no extra lookup or reveal step — the address you’re paying is already the verification key. (More on this below.)
- Addresses can be derived deterministically off-curve without a private key — the foundation of PDAs, which are central to how Solana programs own state.
Neither of those would be clean if an address were a one-way hash of a key. You cannot verify a signature against a hash without first being handed the pre-image; you cannot meaningfully reason about whether a hash is “on or off the ed25519 curve.” Solana’s whole account and program model leans on the address and the key being the same 32 bytes.
What Base58 actually is (and isn’t)
Section titled “What Base58 actually is (and isn’t)”Base58 is encoding, not encryption and not hashing. It is a reversible way to render binary bytes as text. It adds zero security. Anyone can Base58-decode an address straight back to the raw 32 bytes; that is the whole point — it’s a display format.
So why not hex, or Base64? Base58 exists to be human-safe. It uses 58 characters and deliberately omits the ones humans confuse:
dropped: 0 (zero) O (capital o) ← look identical in many fonts I (cap i) l (lowercase L) ← same problem + / ← Base64's symbols; ugly in URLs, break on double-clickWhat’s left is 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz — digits and letters
with the ambiguous ones removed. The result is a string you can read aloud, copy by eye in a
pinch, and paste into a URL without escaping.
Under the hood — encoding is arithmetic, not chunking
Section titled “Under the hood — encoding is arithmetic, not chunking”A subtlety that trips people up: Base58 is not a fixed per-chunk mapping the way hex is (hex maps each 4 bits to one character, always). Base58 treats the entire 32-byte key as one big integer and repeatedly divides it by 58, emitting the remainder as a character each time:
n = big-endian integer of the 32 key bytes while n > 0: digit = n mod 58 output ALPHABET[digit] (prepended) n = n / 58One consequence: leading zero bytes carry no value in that big integer, so each leading 0x00
byte is encoded as a separate leading '1' (the first alphabet character) to preserve length.
This is why the encoded length can wobble by a character or two — a key with leading zero bytes
encodes slightly shorter in “value” but pads with 1s. It also means you cannot decode a Base58
string chunk-by-chunk; you decode the whole thing back into one integer and then into 32 bytes.
The payoff: verification needs no lookup
Section titled “The payoff: verification needs no lookup”Here is the design paying off. On Solana, checking a signature is a two-input operation with nothing to fetch first:
verify( address, message, signature ) → true / false │ └── the address IS the ed25519 public key, so it plugs straight into ed25519_verify — no reveal, no hash-match, no extra account readBecause the address equals the verification key, the thing you’re paying is the thing that checks the signature. There is no “reveal your public key and prove it hashes to this address” dance. This matters at hardware speed: Solana’s parallel scheduler is already validating a firehose of transactions, and every avoided indirection is time it doesn’t spend. We’ll do the actual verify step on the next page; the point here is that the address format is what makes it a single, dependency-free call.
In this book’s companion crate this is why a Pubkey is simply a 32-byte wrapper — the address
type and the key type are the same type:
/// A 32-byte public key — the address of an account or a program.pub struct Pubkey([u8; 32]);
impl Pubkey { /// The raw bytes — the same bytes that Base58 renders as the address. pub fn as_bytes(&self) -> &[u8; 32] { &self.0 }}There is no separate Address type hashing a Pubkey down to something shorter. The key is
the address; Pubkey is used interchangeably for “who owns this account,” “which program owns
it,” and “who signed” precisely because those are all the same 32 bytes.
One address type, three roles
Section titled “One address type, three roles”Because an address is just 32 bytes, the byte layout is identical no matter what the address represents. What differs is only whether a private key exists for it — that is, whether it can sign.
32 bytes, Base58-encoded — same shape in all three cases:
1. Wallet keypair pubkey → HAS a matching private key → CAN sign (on the curve) 2. Program id → HAS a matching private key at deploy, but the program executes; nobody signs as "the program" 3. PDA (Program Derived Address) → has NO private key at all → CANNOT sign (off the curve)- A wallet address is the public key of a keypair a user holds. It can sign, so it can authorize spending its own lamports.
- A program id is the address of a deployed program. It looks exactly like a wallet address — 32 bytes, Base58 — but you interact with it by calling it, not by it signing.
- A PDA is an address deliberately constructed to have no corresponding private key. It is a valid 32-byte address a program controls, but no one can ever produce a signature for it, because no secret key exists. That “no private key” property is not a bug — it’s the entire reason PDAs are safe for programs to own.
The takeaway: you cannot tell these apart by looking at the bytes. A Solana address is a 32-byte public key slot; whether it is on or off the ed25519 curve, and whether a secret exists for it, is what gives it one of these three roles. That uniformity is what lets the runtime treat every account owner, signer, and program the same way.
The sharp edge: plain Base58 has no checksum
Section titled “The sharp edge: plain Base58 has no checksum”Now the danger, and it is a real one. Bitcoin uses Base58Check — Base58 plus a 4-byte checksum (a double-SHA-256 of the payload) appended before encoding. If you mistype a Bitcoin address, the checksum almost certainly fails and wallets reject it before any funds move.
Plain Base58, as used for a raw Solana address, has no such checksum by default. The address is just the 32 key bytes, encoded. So consider what happens when a character is wrong:
correct : 7dHbWXmci3dT8UFYWYZweBLXgY... → decodes to key K (real account) typo : 7dHbWXmci3dT8UFYWYZweBLXqY... → decodes to key K' (a DIFFERENT valid key) ▲ one char changedA mistyped address does not become “invalid” — it decodes to a different, perfectly valid 32-byte key. There is no checksum to catch it. If you send funds there, they land in an account whose private key almost certainly nobody holds. They are, for practical purposes, gone.
The architect’s lens
Section titled “The architect’s lens”Addresses-as-public-keys is a foundational design decision, not a detail — interrogate the bargain the way an architect would:
- Why does it exist? To make an address the cheapest possible handle on a key: the address a user shares is literally the ed25519 verification key, so there is no hash pipeline to compute, store, or reverse.
- What problem does it solve? It removes the reveal-and-hash-match step from signature verification and makes deterministic, key-less addresses (PDAs) possible — both of which a stateless-program, parallel-execution runtime needs to run at speed.
- What are the trade-offs? You give up Bitcoin’s quantum margin (the pubkey is exposed the moment the address is, not just at spend) and, in raw Base58, you give up a built-in checksum, pushing typo-safety onto tooling and human discipline.
- When should I avoid it? When hiding the public key until use is a hard requirement — e.g. a design that wants post-quantum protection for never-spent addresses. Solana’s model exposes the key up front by construction, so that property is simply not on the menu.
- What breaks if I remove it? If addresses were hashes of keys, signature verification would need a reveal step, and PDAs would be impossible — you can’t ask whether a hash is off-curve or derive one deterministically. The account/program ownership model that the rest of this part builds on would collapse.
Check your understanding
Section titled “Check your understanding”- State the core claim of this page in one sentence, and name the two hash functions Bitcoin uses that Solana does not.
- Base58 is often mistaken for a security measure. What does it actually do, and what does it
deliberately not do? Why are
0,O,I, andlexcluded? - Explain, in terms of what an address is, why verifying a Solana signature needs no extra lookup or reveal step — and why the same would be awkward if the address were a hash.
- A wallet address, a program id, and a PDA all look like the same 32-byte Base58 string. What actually distinguishes their roles, and which one can never produce a signature?
- Why can a single mistyped character in a Solana address quietly send funds to the wrong place, and how does Bitcoin’s Base58Check avoid this? Name two habits that mitigate the risk on Solana.
Show answers
- A Solana address is the raw 32-byte ed25519 public key, rendered in Base58 — there is no hashing step. Bitcoin runs the public key through SHA-256 then RIPEMD-160 (producing the 20-byte “hash160”) before encoding, so a Bitcoin address is a hash of the key, not the key.
- Base58 is encoding — a reversible, human-safe way to render bytes as text. It adds no
encryption or hashing; anyone can decode an address straight back to the raw 32 bytes. It
excludes
0/OandI/lbecause those characters are visually ambiguous (and drops Base64’s+//), so addresses are safer to read, copy, and put in URLs. - Because the address is the ed25519 public key, it plugs directly into
ed25519_verify(address, message, signature)— the thing you’re paying is the thing that checks the signature, with no account read or “reveal your pubkey and prove it hashes to this” step. If the address were a one-way hash, you’d first have to be handed the pre-image (the actual key) before you could verify anything against it. - The byte layout is identical; what differs is whether a private key exists. A wallet pubkey has a matching private key and can sign; a program id is an address you call rather than sign as; a PDA is constructed off the ed25519 curve to have no private key, so it can never produce a signature — which is exactly why programs can safely own it.
- Plain Base58 has no checksum, so a mistyped character decodes to a different but valid 32-byte key rather than an error; funds sent there are effectively lost since no one holds that key. Bitcoin’s Base58Check appends a 4-byte double-SHA-256 checksum, so a typo almost always fails validation before funds move. Mitigations on Solana: copy/paste rather than retype, and verify the prefix and suffix by eye (and prefer tooling that checks the address decodes to 32 bytes / is on-curve).