Ed25519 Algorithm

Ed25519 is the digital signature algorithm used by Solana, offering high security, fast performance, and small key/signature sizes.

Ed25519 Overview

Text
┌─────────────────────────────────────────────────────────────────┐
│                      Ed25519 Specification                      │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Curve: Edwards25519 (twisted Edwards curve)                   │
│  Equation: -x² += 1 + dx²y²  where d = -121665/121666      │
│                                                                 │
│  Parameters:                                                    │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ Prime p = 2²⁵⁵ - 19                                     │   │
│  │ Order n = 2²⁵² + 27742317777372353535851937790883648493 │   │
│  │ Cofactor h = 8                                          │   │
│  │ Base point G = (x, 4/5)                                 │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│  Key Sizes:                                                     │
│  • Private key: 32 bytes (256 bits)                            │
│  • Public key: 32 bytes (256 bits)                             │
│  • Signature: 64 bytes (512 bits)                              │
│                                                                 │
│  Security Level: ~128 bits (equivalent to 3072-bit RSA)        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Key Generation

TypeScript
import nacl from "tweetnacl";
import { sha512 } from "@noble/hashes/sha512";

// Ed25519 key generation process
function generateEd25519Keypair(seed?: Uint8Array): {
  publicKey: Uint8Array;
  secretKey: Uint8Array;
} {
  // 1. Generate or use provided 32-byte seed
  const privateSeed = seed || nacl.randomBytes(32);

  // 2. Hash seed with SHA-512 to get 64 bytes
  const hash = sha512(privateSeed);

  // 3. Clamp the first 32 bytes (scalar)
  //    - Clear lowest 3 bits (ensure divisible by 8)
  //    - Set bit 254 (ensure high bit)
  //    - Clear bit 255 (ensure less than 2^255)
  hash[0] &= 248; // Clear bits 0, 1, 2
  hash[31] &= 127; // Clear bit 255
  hash[31] |= 64; // Set bit 254

  // 4. Scalar multiplication: A = s * G
  //    (This is the public key derivation)
  const keypair = nacl.sign.keyPair.fromSeed(privateSeed);

  return {
    publicKey: keypair.publicKey, // 32 bytes
    secretKey: keypair.secretKey, // 64 bytes (seed + public key)
  };
}

// Solana keypair is just an Ed25519 keypair
import { Keypair } from "@solana/web3.js";

const keypair = Keypair.generate();
console.log("Private (seed):", keypair.secretKey.slice(0, 32)); // First 32 bytes
console.log("Public key:", keypair.publicKey.toBytes()); // Last 32 bytes

Signing Algorithm

TypeScript
// Ed25519 signature algorithm (EdDSA)
// This is a simplified explanation of the actual algorithm

/*
To sign message M with private key (seed s, public key A):

1. Hash the private seed to get (a, prefix)
   h = SHA512(s)
   a = h[0:32] (clamped scalar)
   prefix = h[32:64]

2. Compute r = SHA512(prefix || M) mod L
   (L is the curve order)

3. Compute R = r * G
   (Point on the curve)

4. Compute k = SHA512(R || A || M) mod L

5. Compute S = (r + k * a) mod L

6. Signature = (R, S) - each 32 bytes = 64 bytes total
*/

import nacl from "tweetnacl";

function signEd25519(message: Uint8Array, secretKey: Uint8Array): Uint8Array {
  // nacl.sign.detached returns just the signature (64 bytes)
  // nacl.sign returns signature + message concatenated
  return nacl.sign.detached(message, secretKey);
}

// Example
const keypair = nacl.sign.keyPair();
const message = new TextEncoder().encode("Hello, Solana!");
const signature = signEd25519(message, keypair.secretKey);

console.log("Signature length:", signature.length); // 64
console.log("Signature (hex):", Buffer.from(signature).toString("hex"));

Verification Algorithm

TypeScript
/*
To verify signature (R, S) on message M with public key A:

1. Check S < L (curve order) - reject if not

2. Compute k = SHA512(R || A || M) mod L

3. Check: S * G == R + k * A
   - Left side: scalar multiplication
   - Right side: point addition

If the equation holds, signature is valid.
This works because:
  S * G = (r + k * a) * G
        = r * G + k * a * G
        = R + k * A  ✓
*/

import nacl from "tweetnacl";

function verifyEd25519(
  message: Uint8Array,
  signature: Uint8Array,
  publicKey: Uint8Array,
): boolean {
  return nacl.sign.detached.verify(message, signature, publicKey);
}

// Example verification
const isValid = verifyEd25519(message, signature, keypair.publicKey);
console.log("Signature valid:", isValid);

// Tampered message
const tamperedMessage = new TextEncoder().encode("Hello, Solana!!");
const isValidTampered = verifyEd25519(
  tamperedMessage,
  signature,
  keypair.publicKey,
);
console.log("Tampered valid:", isValidTampered); // false

Deterministic Signatures

TypeScript
// Ed25519 produces DETERMINISTIC signatures
// Same message + same key = same signature every time

const keypair = nacl.sign.keyPair();
const message = new TextEncoder().encode("Test message");

const sig1 = nacl.sign.detached(message, keypair.secretKey);
const sig2 = nacl.sign.detached(message, keypair.secretKey);
const sig3 = nacl.sign.detached(message, keypair.secretKey);

// All signatures are identical
console.log("sig1 === sig2:", Buffer.from(sig1).equals(Buffer.from(sig2))); // true
console.log("sig2 === sig3:", Buffer.from(sig2).equals(Buffer.from(sig3))); // true

// This is DIFFERENT from ECDSA which requires a random nonce
// Benefits:
// - No random number generator vulnerabilities
// - Reproducible for testing
// - No risk of nonce reuse attacks

Batch Verification

TypeScript
// Ed25519 supports efficient batch verification
// Verify multiple signatures faster than individually

import * as ed from "@noble/ed25519";

interface SignatureBundle {
  message: Uint8Array;
  signature: Uint8Array;
  publicKey: Uint8Array;
}

async function batchVerify(bundles: SignatureBundle[]): Promise<boolean> {
  // Batch verification equation:
  // Σ(z_i * S_i) * G = Σ(z_i * R_i) + Σ(z_i * k_i * A_i)
  // Where z_i are random scalars to prevent attacks

  // Using @noble/ed25519 for batch verification
  const results = await Promise.all(
    bundles.map((b) => ed.verifyAsync(b.signature, b.message, b.publicKey)),
  );

  return results.every((r) => r === true);
}

// Performance comparison
async function benchmark() {
  const keypairs = Array.from({ length: 100 }, () => nacl.sign.keyPair());
  const messages = keypairs.map((_, i) =>
    new TextEncoder().encode(`Message ${i}`),
  );
  const signatures = keypairs.map((kp, i) =>
    nacl.sign.detached(messages[i], kp.secretKey),
  );

  // Individual verification
  const start1 = performance.now();
  for (let i = 0; i < 100; i++) {
    nacl.sign.detached.verify(
      messages[i],
      signatures[i],
      keypairs[i].publicKey,
    );
  }
  console.log("Individual:", performance.now() - start1, "ms");

  // Batch verification (conceptual - actual implementation varies)
  const bundles = keypairs.map((kp, i) => ({
    message: messages[i],
    signature: signatures[i],
    publicKey: kp.publicKey,
  }));

  const start2 = performance.now();
  await batchVerify(bundles);
  console.log("Batch:", performance.now() - start2, "ms");
}

Ed25519 vs Other Curves

FeatureEd25519secp256k1 (Bitcoin)P-256
Key size32 bytes32 bytes32 bytes
Signature64 bytes64-72 bytes64 bytes
Signing speed~15,000/s~5,000/s~5,000/s
Verify speed~8,000/s~4,000/s~4,000/s
DeterministicYesNo (without RFC6979)No
Side-channel resistantYesNeeds careNeeds care

Security Properties

TypeScript
// Ed25519 security features

// 1. Small subgroup attack resistance
// The cofactor (8) is handled in verification
// No need for separate point validation

// 2. Timing attack resistance
// Operations are constant-time by design
function constantTimeCompare(a: Uint8Array, b: Uint8Array): boolean {
  if (a.length !== b.length) return false;
  let diff = 0;
  for (let i = 0; i < a.length; i++) {
    diff |= a[i] ^ b[i];
  }
  return diff === 0;
}

// 3. No weak keys
// All 32-byte values are valid private keys
// (after clamping)

// 4. Canonicality
// Only one valid signature exists for each message+key pair
// Prevents signature malleability attacks

// Verify signature is canonical (standard Ed25519)
function isCanonicalSignature(sig: Uint8Array): boolean {
  // S must be less than L (curve order)
  // R must be a valid curve point
  return sig.length === 64;
  // (Actual validation is done in verify function)
}

Using Ed25519 with Solana

TypeScript
import { Keypair, PublicKey, Transaction } from "@solana/web3.js";
import nacl from "tweetnacl";

// Solana uses Ed25519 for all signatures
class SolanaEd25519 {
  // Create keypair
  static generateKeypair(): Keypair {
    return Keypair.generate();
  }

  // Sign arbitrary message
  static signMessage(message: Uint8Array, keypair: Keypair): Uint8Array {
    return nacl.sign.detached(message, keypair.secretKey);
  }

  // Verify message signature
  static verifyMessage(
    message: Uint8Array,
    signature: Uint8Array,
    publicKey: PublicKey,
  ): boolean {
    return nacl.sign.detached.verify(message, signature, publicKey.toBytes());
  }

  // Sign transaction
  static signTransaction(tx: Transaction, signers: Keypair[]): Transaction {
    for (const signer of signers) {
      tx.partialSign(signer);
    }
    return tx;
  }

  // Verify transaction signatures
  static verifyTransaction(tx: Transaction): boolean {
    return tx.verifySignatures();
  }

  // Check if address is on Ed25519 curve
  static isOnCurve(publicKey: PublicKey): boolean {
    return PublicKey.isOnCurve(publicKey.toBytes());
  }
}

// Usage
const keypair = SolanaEd25519.generateKeypair();
const message = Buffer.from("Sign this message");
const signature = SolanaEd25519.signMessage(message, keypair);
const isValid = SolanaEd25519.verifyMessage(
  message,
  signature,
  keypair.publicKey,
);

console.log("Public key:", keypair.publicKey.toBase58());
console.log("Signature valid:", isValid);
console.log("On curve:", SolanaEd25519.isOnCurve(keypair.publicKey));

Ed25519ph (Pre-hashed)

TypeScript
// Ed25519ph variant for large messages
// Hash the message first, then sign the hash

import { sha512 } from "@noble/hashes/sha512";

function ed25519phSign(message: Uint8Array, secretKey: Uint8Array): Uint8Array {
  // Pre-hash the message with SHA-512
  const hash = sha512(message);

  // Sign the hash with domain separation
  const domainSeparator = new TextEncoder().encode(
    "SigEd25519 no Ed25519 collisions\x01",
  );
  const toSign = new Uint8Array([...domainSeparator, ...hash]);

  return nacl.sign.detached(toSign, secretKey);
}

// Standard Ed25519 is preferred for most uses
// Ed25519ph is useful for:
// - Very large messages
// - Streaming scenarios
// - Hardware constraints

Key Takeaways

AspectDetails
AlgorithmEdDSA on Edwards25519 curve
Security128-bit (equivalent to RSA-3072)
Performance~70,000 signatures/second
DeterministicYes, same input = same signature
Solana usageAll transaction and message signing

Next: Zero-Knowledge Proofs - Proving without revealing.