Public/Private Keys
Asymmetric cryptography uses mathematically linked key pairs to enable secure communication and digital ownership without shared secrets.
Key Pair Fundamentals
Text
┌─────────────────────────────────────────────────────────────────┐
│ Asymmetric Key Pair │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Private Key (Secret) Public Key (Shareable) │
│ ┌───────────────────┐ ┌───────────────────────┐ │
│ │ 256-bit random │ ─────────▶ │ Derived mathematically │ │
│ │ number │ One-way │ from private key │ │
│ │ │ function │ │ │
│ │ KEEP SECRET! │ │ Share with everyone │ │
│ └───────────────────┘ └───────────────────────┘ │
│ │
│ Properties: │
│ • Private key generates public key (one-way) │
│ • Cannot derive private key from public key │
│ • Messages encrypted with public key → only private can read │
│ • Messages signed with private key → public key verifies │
│ │
│ Use Cases: │
│ • Digital signatures (authentication) │
│ • Encryption (confidentiality) │
│ • Key exchange (establishing shared secrets) │
│ │
└─────────────────────────────────────────────────────────────────┘
Generating Key Pairs
Using Node.js Crypto
TypeScript
import { generateKeyPairSync, randomBytes } from "crypto";
// RSA key pair (traditional, larger keys)
const rsaKeys = generateKeyPairSync("rsa", {
modulusLength: 2048,
publicKeyEncoding: { type: "spki", format: "pem" },
privateKeyEncoding: { type: "pkcs8", format: "pem" },
});
console.log("RSA Public Key:", rsaKeys.publicKey);
console.log("RSA Private Key:", rsaKeys.privateKey);
// Ed25519 key pair (modern, efficient - used by Solana)
const ed25519Keys = generateKeyPairSync("ed25519", {
publicKeyEncoding: { type: "spki", format: "der" },
privateKeyEncoding: { type: "pkcs8", format: "der" },
});
Using @solana/web3.js
TypeScript
import { Keypair } from "@solana/web3.js";
import bs58 from "bs58";
// Generate new random keypair
const keypair = Keypair.generate();
console.log("Public Key:", keypair.publicKey.toBase58());
// e.g., 7C4jsPZpht42Tw6MjXWF56Q5RQUocjBBmciEjDa8HRtp
console.log("Secret Key (base58):", bs58.encode(keypair.secretKey));
// 64 bytes: 32-byte private key + 32-byte public key
// Restore from secret key
const secretKey = keypair.secretKey;
const restored = Keypair.fromSecretKey(secretKey);
console.log("Restored:", restored.publicKey.toBase58());
// From seed (deterministic)
const seed = new Uint8Array(32).fill(1); // Use proper entropy!
const fromSeed = Keypair.fromSeed(seed);
// From mnemonic (BIP39)
import * as bip39 from "bip39";
import { derivePath } from "ed25519-hd-key";
const mnemonic = bip39.generateMnemonic();
const seed = bip39.mnemonicToSeedSync(mnemonic);
const derivedSeed = derivePath("m/44'/501'/0'/0'", seed.toString("hex")).key;
const fromMnemonic = Keypair.fromSeed(derivedSeed);
Elliptic Curve Cryptography
Solana and most modern blockchains use elliptic curve cryptography (ECC) instead of RSA.
Text
┌─────────────────────────────────────────────────────────────────┐
│ Elliptic Curve (simplified) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ y² = x³ + ax + b (mod p) │
│ │
│ │ * │
│ │ * * │
│ │ * * G = Generator point │
│ │ * * k = Private key (scalar) │
│ ───────┼─*─────────*─── P = Public key (point) │
│ │ * * │
│ │ * * P = k × G │
│ │ * * │
│ │ * (scalar multiplication) │
│ │
│ Security: Given G and P, finding k is computationally hard │
│ (Elliptic Curve Discrete Logarithm Problem) │
│ │
│ Common Curves: │
│ • secp256k1 - Bitcoin, Ethereum │
│ • Ed25519 - Solana, SSH, Signal │
│ • P-256 (secp256r1) - TLS, WebAuthn │
│ │
└─────────────────────────────────────────────────────────────────┘
Why Ed25519 for Solana?
TypeScript
// Ed25519 advantages:
const comparison = {
keySize: {
RSA: "2048+ bits",
Ed25519: "256 bits",
},
signatureSize: {
RSA: "256 bytes",
Ed25519: "64 bytes",
},
speed: {
RSA: "~1000 ops/sec",
Ed25519: "~70,000 ops/sec",
},
security: {
RSA2048: "~112 bits",
Ed25519: "~128 bits",
},
};
// Ed25519 is:
// - Faster signing and verification
// - Smaller keys and signatures
// - Resistant to timing attacks
// - Deterministic signatures (no random nonce needed)
Key Derivation
Hierarchical Deterministic (HD) Wallets
TypeScript
import * as bip39 from "bip39";
import { derivePath } from "ed25519-hd-key";
import { Keypair } from "@solana/web3.js";
// BIP39: Mnemonic to seed
const mnemonic =
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
const seed = bip39.mnemonicToSeedSync(mnemonic);
// BIP44 path for Solana: m/44'/501'/account'/change'
// 44' = BIP44 purpose
// 501' = Solana coin type
// account' = account index
// change' = change index (usually 0)
function deriveKeypair(seed: Buffer, account: number): Keypair {
const path = `m/44'/501'/${account}'/0'`;
const derived = derivePath(path, seed.toString("hex"));
return Keypair.fromSeed(derived.key);
}
// Derive multiple accounts from one mnemonic
const accounts = [0, 1, 2, 3, 4].map((i) => {
const kp = deriveKeypair(seed, i);
return {
index: i,
path: `m/44'/501'/${i}'/0'`,
publicKey: kp.publicKey.toBase58(),
};
});
console.log("Derived accounts:", accounts);
Key Derivation Functions (KDF)
TypeScript
import { scrypt, pbkdf2 } from "crypto";
import { promisify } from "util";
const scryptAsync = promisify(scrypt);
const pbkdf2Async = promisify(pbkdf2);
// Derive encryption key from password
async function deriveKeyFromPassword(
password: string,
salt: Buffer,
): Promise<Buffer> {
// scrypt is memory-hard, resistant to GPU/ASIC attacks
const key = await scryptAsync(password, salt, 32, {
N: 2 ** 14, // CPU/memory cost
r: 8, // Block size
p: 1, // Parallelization
});
return key as Buffer;
}
// Encrypt private key with password
async function encryptPrivateKey(
privateKey: Buffer,
password: string,
): Promise<{ encrypted: Buffer; salt: Buffer; iv: Buffer }> {
const salt = randomBytes(32);
const iv = randomBytes(16);
const key = await deriveKeyFromPassword(password, salt);
const cipher = createCipheriv("aes-256-gcm", key, iv);
const encrypted = Buffer.concat([
cipher.update(privateKey),
cipher.final(),
cipher.getAuthTag(),
]);
return { encrypted, salt, iv };
}
Public Key Infrastructure
Address Formats
TypeScript
import bs58 from "bs58";
import { PublicKey } from "@solana/web3.js";
// Solana: Base58 encoded public key
const solanaAddress = "7C4jsPZpht42Tw6MjXWF56Q5RQUocjBBmciEjDa8HRtp";
const pubkey = new PublicKey(solanaAddress);
console.log("Bytes:", pubkey.toBytes()); // 32 bytes
// Validate address
function isValidSolanaAddress(address: string): boolean {
try {
const pubkey = new PublicKey(address);
return PublicKey.isOnCurve(pubkey.toBytes());
} catch {
return false;
}
}
// Note: PDAs are valid addresses but NOT on the curve
function isPDA(address: string): boolean {
try {
const pubkey = new PublicKey(address);
return !PublicKey.isOnCurve(pubkey.toBytes());
} catch {
return false;
}
}
Key Encoding Formats
| Format | Description | Example Use |
|---|---|---|
| Raw bytes | 32/64 bytes | Internal storage |
| Base58 | Bitcoin-style encoding | Solana addresses |
| Base64 | Standard encoding | API transport |
| Hex | Hexadecimal | Ethereum, debugging |
| PEM | Text format with headers | TLS certificates |
TypeScript
// Converting between formats
const keypair = Keypair.generate();
const publicKey = keypair.publicKey;
// Raw bytes
const bytes = publicKey.toBytes();
console.log("Bytes:", bytes);
// Base58 (Solana standard)
const base58 = publicKey.toBase58();
console.log("Base58:", base58);
// Base64
const base64 = Buffer.from(bytes).toString("base64");
console.log("Base64:", base64);
// Hex
const hex = Buffer.from(bytes).toString("hex");
console.log("Hex:", hex);
Security Best Practices
Key Storage
TypeScript
// NEVER do this:
const BAD_STORAGE = {
privateKey: "5abc123...", // Plain text in code
};
// Better: Environment variables (still not ideal)
const privateKey = process.env.PRIVATE_KEY;
// Best: Hardware security module (HSM) or secure enclave
// - AWS KMS
// - Google Cloud KMS
// - Hardware wallets (Ledger, Trezor)
// - Mobile secure enclave
// For development/testing only:
import { writeFileSync, readFileSync, chmodSync } from "fs";
import { homedir } from "os";
import { join } from "path";
function saveKeypair(keypair: Keypair, name: string): void {
const path = join(homedir(), ".config", "solana", `${name}.json`);
writeFileSync(path, JSON.stringify(Array.from(keypair.secretKey)));
chmodSync(path, 0o600); // Owner read/write only
}
function loadKeypair(name: string): Keypair {
const path = join(homedir(), ".config", "solana", `${name}.json`);
const data = JSON.parse(readFileSync(path, "utf-8"));
return Keypair.fromSecretKey(Uint8Array.from(data));
}
Key Rotation
TypeScript
interface KeyRotationStrategy {
// Separate keys by purpose
keys: {
signing: Keypair; // For signing transactions
encryption: Keypair; // For encrypting data
backup: Keypair; // Cold storage
};
// Rotation schedule
rotationPeriod: number; // Days
lastRotation: Date;
// Migration helpers
oldKeys: Keypair[]; // Keep for verification
}
// Multisig for high-value operations
async function requireMultipleSignatures(
transaction: Transaction,
signers: Keypair[],
threshold: number,
): Promise<Transaction> {
if (signers.length < threshold) {
throw new Error(`Need ${threshold} signers, got ${signers.length}`);
}
for (const signer of signers.slice(0, threshold)) {
transaction.sign(signer);
}
return transaction;
}
Key Concepts Summary
| Concept | Description |
|---|---|
| Private Key | Secret number that must never be shared |
| Public Key | Derived from private key, safe to share |
| Address | Often the public key or hash of it |
| Keypair | Private + public key together |
| Seed | Entropy source for key generation |
| Mnemonic | Human-readable seed backup (12-24 words) |
| HD Wallet | Derive many keys from one seed |
Next: Digital Signatures - Proving ownership and authenticity.