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² =+ 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

FormatDescriptionExample Use
Raw bytes32/64 bytesInternal storage
Base58Bitcoin-style encodingSolana addresses
Base64Standard encodingAPI transport
HexHexadecimalEthereum, debugging
PEMText format with headersTLS 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

ConceptDescription
Private KeySecret number that must never be shared
Public KeyDerived from private key, safe to share
AddressOften the public key or hash of it
KeypairPrivate + public key together
SeedEntropy source for key generation
MnemonicHuman-readable seed backup (12-24 words)
HD WalletDerive many keys from one seed

Next: Digital Signatures - Proving ownership and authenticity.