Keypairs & Addresses
Solana identity is built on Ed25519 keypairs. The public key is the account address; the secret key authorizes transactions. Understanding signing roles prevents authorization bugs and key leaks.
Search across all documentation pages
Solana identity is built on Ed25519 keypairs. The public key is the account address; the secret key authorizes transactions. Understanding signing roles prevents authorization bugs and key leaks.
Quick-reference recipe card - copy-paste ready.
solana-keygen new --outfile ./keypair.json --no-bip39-passphrase
solana-keygen pubkey ./keypair.jsonimport { createKeyPairSignerFromBytes, address } from "@solana/kit";
import { readFileSync } from "node:fs";
const secret = Uint8Array.from(JSON.parse(readFileSync("./keypair.json", "utf8")));
const signer = await createKeyPairSignerFromBytes(secret);
console.log(signer.address);When to reach for this:
isSigner: trueimport {
createSolanaRpc,
createKeyPairSignerFromBytes,
createTransactionMessage,
setTransactionMessageFeePayer,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstruction,
signTransactionMessageWithSigners,
getSignatureFromTransaction,
pipe,
} from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
import { readFileSync } from "node:fs";
const rpc = createSolanaRpc("https://api.devnet.solana.com");
// Load signer from keypair file
const secret = Uint8Array.from(
JSON.parse(readFileSync(process.env.HOME + "/.config/solana/id.json", "utf8")),
);
const feePayer = await createKeyPairSignerFromBytes(secret);
const recipient = await createKeyPairSignerFromBytes(
crypto.getRandomValues(new Uint8Array(64)),
);
const { value: blockhash } = await rpc.getLatestBlockhash().send();
const message = pipe(
createTransactionMessage({ version: 0 }),
(m) => setTransactionMessageFeePayer(feePayer.address, m),
(m) => setTransactionMessageLifetimeUsingBlockhash(blockhash, m),
(m) =>
appendTransactionMessageInstruction(
getTransferSolInstruction({
source: feePayer.address,
destination: recipient.address,
amount: 1_000_000n,
}),
m,
),
);
const signed = await signTransactionMessageWithSigners(message, [feePayer]);
const signature = getSignatureFromTransaction(signed);
console.log("Signature:", signature);What this demonstrates:
createKeyPairSignerFromBytes wraps the keypair as a Kit signerisSigner and isWritable flags| Role | Signs? | Typical Account |
|---|---|---|
| Fee payer | Yes | First signer; pays transaction fee |
| Authority | Yes | Owner of lamports or token account |
| Program | No | Executable account invoked by instruction |
| PDA | No (program signs via seeds) | Derived address without private key |
use anchor_lang::prelude::*;
#[derive(Accounts)]
pub struct UpdateData<'info> {
#[account(mut, signer)]
pub authority: Signer<'info>,
#[account(mut, has_one = authority)]
pub data_account: Account<'info, MyData>,
}Signer<'info> constraint verifies the account signed the transactioninvoke_signed with seeds instead of a private key.gitignore, env vars, or hardware wallets for production.isSigner flag - transaction rejected at runtime. Fix: mark every signing account as signer in instruction accounts.find_program_address and let the program sign.solana-keygen or Kit helpers, not raw byte slicing.| Alternative | Use When | Don't Use When |
|---|---|---|
solana-keygen CLI | Quick dev wallets | Production key management |
| Hardware wallet (Ledger) | Mainnet signing | Automated server signing at high throughput |
@solana/kit signers | TypeScript transaction building | On-chain program logic |
| PDAs with seeds | Program-controlled accounts | User-owned wallets |
Ed25519. Public keys are 32 bytes, base58-encoded for display.
A JSON array of 64 bytes: the 32-byte secret seed concatenated with the 32-byte public key.
Yes. The terms are interchangeable - the base58-encoded 32-byte Ed25519 public key.
No. Each valid keypair maps to exactly one public key/address.
An address derived from seeds and a program ID. It has no private key - the program signs for it via invoke_signed.
Up to 16 signatures per transaction in the current format. The fee payer is always one of them.
No. Only the source account's authority (and fee payer) must sign.
import { address } from "@solana/kit";
try {
address("invalid!");
} catch {
console.log("Invalid address");
}No. The secret key cannot be derived from the public key (one-way function).
11111111111111111111111111111111 - handles account creation and SOL transfers.
No. Use dedicated service keypairs with minimal funded balances and strict access controls.
The private key never leaves the device. The app sends the transaction message; the device returns the signature.
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.
Reviewed by Chris St. John·Last updated Jul 19, 2026