Digital Signatures

✍️ Ed25519 Edwards-curve Digital Signature

Security Level 128-bit
Performance ⭐⭐⭐⭐⭐ Excellent
Quantum Resistant ❌ No
Standardization RFC 8032, NIST SP 800-186
Key Size 32 bytes
Signature Size 64 bytes

📖 Overview

Ed25519 is a high-performance public-key signature system based on elliptic curve cryptography. It uses the Edwards-curve Digital Signature Algorithm (EdDSA) over the twisted Edwards curve edwards25519, designed to provide high security while avoiding common implementation pitfalls and offering excellent performance characteristics.

✨ Key Features

Fast Operations

Among the fastest signature algorithms available

📦

Compact Keys

32-byte public and private keys, 64-byte signatures

🎯

Deterministic

No random number generator required for signing

🛡️

High Security

128-bit security level against best known attacks

🔒

Side-channel Resistant

Designed to resist timing and cache attacks

📋

Standardized

RFC 8032 and NIST SP 800-186 approved

🌐

Widely Adopted

Used in SSH, TLS, cryptocurrencies, and many protocols

🔧

Implementation Safe

Designed to avoid common cryptographic pitfalls

🎯 Common Use Cases

🔐 Protocol Integration

  • SSH: ssh-ed25519 host and user keys
  • TLS 1.3: Certificate and handshake signatures
  • Signal Protocol: Identity key signatures
  • Git: Commit and tag signing

💰 Blockchain & Identity

  • Cryptocurrencies: Transaction signing (Monero, Stellar)
  • Digital Identity: Self-sovereign identity systems
  • Code Signing: Software package authentication
  • Document Signing: Digital document authentication

Algorithm Parameters

Parameter Value
Security Level 128 bits
Public Key Size 32 bytes
Private Key Size 32 bytes
Signature Size 64 bytes
Hash Function SHA-512

Usage Examples

Basic Signing and Verification

from metamui_crypto import Ed25519

# Generate a new keypair
keypair = Ed25519.generate_keypair()

# Sign a message
message = b"Hello, Ed25519!"
signature = Ed25519.sign(message, keypair.private_key)

# Verify the signature
is_valid = Ed25519.verify(signature, message, keypair.public_key)
print(f"Signature valid: {is_valid}")

Key Generation from Seed

from metamui_crypto import Ed25519
import hashlib

# Deterministic key generation from seed
seed = b"this is a 32 byte seed phrase!!!"  # Must be 32 bytes
keypair = Ed25519.from_seed(seed)

# Same seed always produces same keypair
keypair2 = Ed25519.from_seed(seed)
assert keypair.public_key == keypair2.public_key
assert keypair.private_key == keypair2.private_key

# Derive seed from passphrase
passphrase = "my secure passphrase"
salt = b"application-specific-salt"
seed = hashlib.pbkdf2_hmac('sha256', passphrase.encode(), salt, 100000, 32)
keypair = Ed25519.from_seed(seed)

Batch Verification

from metamui_crypto import Ed25519

# Generate multiple signatures
messages = [f"Message {i}".encode() for i in range(100)]
keypairs = [Ed25519.generate_keypair() for _ in range(100)]
signatures = [
    Ed25519.sign(msg, kp.private_key) 
    for msg, kp in zip(messages, keypairs)
]

# Batch verify (more efficient than individual verification)
public_keys = [kp.public_key for kp in keypairs]
all_valid = Ed25519.batch_verify(signatures, messages, public_keys)
print(f"All signatures valid: {all_valid}")

# Benchmark difference
import time

# Individual verification
start = time.time()
for sig, msg, pk in zip(signatures, messages, public_keys):
    Ed25519.verify(sig, msg, pk)
individual_time = time.time() - start

# Batch verification
start = time.time()
Ed25519.batch_verify(signatures, messages, public_keys)
batch_time = time.time() - start

print(f"Individual: {individual_time:.3f}s, Batch: {batch_time:.3f}s")
print(f"Speedup: {individual_time / batch_time:.1f}x")

Message Signing with Context

from metamui_crypto import Ed25519
import json
import time

class MessageSigner:
    def __init__(self, private_key):
        self.private_key = private_key
    
    def sign_with_metadata(self, message, metadata=None):
        """Sign message with additional context"""
        # Create canonical signing structure
        signing_data = {
            "message": message.decode('utf-8') if isinstance(message, bytes) else message,
            "timestamp": int(time.time()),
            "version": "1.0"
        }
        
        if metadata:
            signing_data["metadata"] = metadata
        
        # Canonical JSON serialization
        canonical = json.dumps(signing_data, sort_keys=True, separators=(',', ':'))
        
        # Sign the canonical form
        signature = Ed25519.sign(canonical.encode('utf-8'), self.private_key)
        
        return {
            "data": signing_data,
            "signature": signature.hex()
        }
    
    @staticmethod
    def verify_with_metadata(signed_message, public_key):
        """Verify signed message with metadata"""
        # Reconstruct canonical form
        canonical = json.dumps(
            signed_message["data"], 
            sort_keys=True, 
            separators=(',', ':')
        )
        
        # Verify signature
        signature = bytes.fromhex(signed_message["signature"])
        return Ed25519.verify(
            signature,
            canonical.encode('utf-8'),
            public_key
        )

Implementation Details

Curve Operations

Ed25519 uses the twisted Edwards curve:

-x² + y² = 1 + d·x²·y²

where d = -121665/121666

Key advantages:

  • Complete addition formulas (no special cases)
  • Fast arithmetic operations
  • Resistance to timing attacks

Signature Algorithm

  1. Key Generation:
    • Generate 32-byte seed
    • Hash seed with SHA-512
    • Clamp first 32 bytes for private scalar
    • Compute public key: A = [s]B
  2. Signing:
    • Hash private key to get (s, prefix)
    • r = SHA-512(prefix   message)
    • R = [r]B
    • h = SHA-512(R   A   message)
    • S = r + h·s mod ℓ
  3. Verification:
    • Check 0 ≤ S < ℓ
    • h = SHA-512(R   A   message)
    • Check [S]B = R + [h]A

Security Properties

  • Deterministic Signatures: Same message always produces same signature
  • SUF-CMA Security: Strongly unforgeable under chosen message attack
  • No Malleability: Signatures cannot be modified
  • Collision Resistance: Based on SHA-512 security

Performance Characteristics

Operation Timings

Operation Time (μs) Cycles (3GHz)
Key Generation 15 45,000
Sign 25 75,000
Verify 50 150,000
Batch Verify (64) 1,200 3,600,000

Comparison with Other Algorithms

Algorithm Sign Verify Signature Size Security
Ed25519 25 μs 50 μs 64 bytes 128-bit
ECDSA-P256 300 μs 600 μs 64 bytes 128-bit
RSA-2048 5 ms 0.2 ms 256 bytes 112-bit
RSA-3072 15 ms 0.3 ms 384 bytes 128-bit

Advanced Usage

Hierarchical Deterministic Keys

from metamui_crypto import Ed25519, HMAC
import struct

class HDEd25519:
    """Hierarchical Deterministic Ed25519 keys"""
    
    def __init__(self, master_seed):
        self.master_seed = master_seed
        self.master_keypair = Ed25519.from_seed(master_seed)
    
    def derive_child(self, index):
        """Derive child keypair at index"""
        # Use HMAC to derive child seed
        hmac = HMAC(self.master_seed, 'sha512')
        hmac.update(b"Ed25519-HD")
        hmac.update(struct.pack('>I', index))
        child_seed = hmac.finalize()[:32]
        
        return Ed25519.from_seed(child_seed)
    
    def derive_path(self, path):
        """Derive key at path like m/0/1/2"""
        current_seed = self.master_seed
        
        for index in path:
            hmac = HMAC(current_seed, 'sha512')
            hmac.update(b"Ed25519-HD")
            hmac.update(struct.pack('>I', index))
            current_seed = hmac.finalize()[:32]
        
        return Ed25519.from_seed(current_seed)

Threshold Signatures

from metamui_crypto import Ed25519
import secrets

class ThresholdEd25519:
    """Simplified threshold signature scheme"""
    
    def __init__(self, threshold, total_parties):
        self.threshold = threshold
        self.total_parties = total_parties
        self.shares = self._generate_shares()
    
    def _generate_shares(self):
        """Generate key shares using Shamir's Secret Sharing"""
        # Generate master key
        master_seed = secrets.token_bytes(32)
        master_keypair = Ed25519.from_seed(master_seed)
        
        # For demonstration - in practice use proper Shamir's
        shares = []
        for i in range(self.total_parties):
            share_seed = HMAC(master_seed, 'sha256').update(
                f"share-{i}".encode()
            ).finalize()
            share_keypair = Ed25519.from_seed(share_seed)
            shares.append({
                'id': i,
                'keypair': share_keypair,
                'master_public': master_keypair.public_key
            })
        
        return shares
    
    def sign_partial(self, message, share_id):
        """Create partial signature"""
        share = self.shares[share_id]
        return Ed25519.sign(message, share['keypair'].private_key)
    
    def combine_signatures(self, partial_signatures, message):
        """Combine threshold signatures"""
        if len(partial_signatures) < self.threshold:
            raise ValueError("Insufficient signatures")
        
        # Simplified - in practice use proper combination
        # This just returns the first valid signature
        return partial_signatures[0]

VRF (Verifiable Random Function)

from metamui_crypto import Ed25519, SHA256

class Ed25519VRF:
    """Simple VRF construction using Ed25519"""
    
    def __init__(self, private_key):
        self.private_key = private_key
        self.public_key = Ed25519.get_public_key(private_key)
    
    def prove(self, input_data):
        """Generate VRF proof"""
        # Hash input with private key
        to_sign = SHA256.hash(b"VRF" + input_data)
        
        # Create proof (signature)
        proof = Ed25519.sign(to_sign, self.private_key)
        
        # Derive output
        output = SHA256.hash(proof + b"output")
        
        return output, proof
    
    def verify(self, input_data, output, proof, public_key):
        """Verify VRF proof"""
        # Verify the proof
        to_sign = SHA256.hash(b"VRF" + input_data)
        if not Ed25519.verify(proof, to_sign, public_key):
            return False
        
        # Check output matches
        expected_output = SHA256.hash(proof + b"output")
        return output == expected_output

Common Pitfalls

1. Private Key Exposure

# Bad: Logging or printing private keys
# print(f"Private key: {keypair.private_key.hex()}")  # Never do this!

# Good: Only log public keys
print(f"Public key: {keypair.public_key.hex()}")

2. Signature Malleability

# Ed25519 signatures are malleable in some implementations
# Always use canonical signature verification

# Bad: Custom signature parsing
# r = signature[:32]
# s = signature[32:]
# # Manual verification...

# Good: Use library verification
is_valid = Ed25519.verify(signature, message, public_key)

3. Weak Random Seeds

# Bad: Predictable seeds
# seed = b"0" * 32  # Predictable!
# seed = hashlib.sha256(b"password").digest()  # Weak!

# Good: Cryptographically secure random
import secrets
seed = secrets.token_bytes(32)

# Or derive from strong passphrase with salt
import hashlib
passphrase = "strong passphrase with high entropy"
salt = secrets.token_bytes(16)  # Random salt
seed = hashlib.pbkdf2_hmac('sha256', passphrase.encode(), salt, 100000, 32)

Integration Examples

JWT with Ed25519

import json
import base64
from metamui_crypto import Ed25519

class Ed25519JWT:
    @staticmethod
    def create_token(payload, private_key):
        """Create JWT signed with Ed25519"""
        # Header
        header = {
            "alg": "EdDSA",
            "typ": "JWT"
        }
        
        # Encode header and payload
        header_b64 = base64.urlsafe_b64encode(
            json.dumps(header).encode()
        ).rstrip(b'=')
        
        payload_b64 = base64.urlsafe_b64encode(
            json.dumps(payload).encode()
        ).rstrip(b'=')
        
        # Create signature
        message = header_b64 + b'.' + payload_b64
        signature = Ed25519.sign(message, private_key)
        signature_b64 = base64.urlsafe_b64encode(signature).rstrip(b'=')
        
        # Combine
        return (header_b64 + b'.' + payload_b64 + b'.' + signature_b64).decode()
    
    @staticmethod
    def verify_token(token, public_key):
        """Verify JWT signed with Ed25519"""
        parts = token.encode().split(b'.')
        if len(parts) != 3:
            return False, None
        
        # Verify signature
        message = parts[0] + b'.' + parts[1]
        signature = base64.urlsafe_b64decode(parts[2] + b'==')
        
        if not Ed25519.verify(signature, message, public_key):
            return False, None
        
        # Decode payload
        payload = json.loads(base64.urlsafe_b64decode(parts[1] + b'=='))
        return True, payload

SSH Key Format

import struct
import base64

def ed25519_to_openssh(public_key):
    """Convert Ed25519 public key to OpenSSH format"""
    key_type = b"ssh-ed25519"
    
    # Build the public key blob
    blob = b""
    blob += struct.pack(">I", len(key_type))
    blob += key_type
    blob += struct.pack(">I", len(public_key))
    blob += public_key
    
    # Base64 encode
    b64_key = base64.b64encode(blob).decode('ascii')
    
    return f"ssh-ed25519 {b64_key}"

def openssh_to_ed25519(ssh_public_key):
    """Parse OpenSSH format Ed25519 public key"""
    parts = ssh_public_key.strip().split()
    if parts[0] != "ssh-ed25519":
        raise ValueError("Not an Ed25519 SSH key")
    
    # Decode base64
    blob = base64.b64decode(parts[1])
    
    # Parse blob
    offset = 0
    
    # Read key type
    key_type_len = struct.unpack(">I", blob[offset:offset+4])[0]
    offset += 4
    key_type = blob[offset:offset+key_type_len]
    offset += key_type_len
    
    # Read public key
    key_len = struct.unpack(">I", blob[offset:offset+4])[0]
    offset += 4
    public_key = blob[offset:offset+key_len]
    
    return public_key

Migration Guide

From ECDSA to Ed25519

# Old ECDSA code
# from ecdsa import SigningKey, SECP256k1
# sk = SigningKey.generate(curve=SECP256k1)
# signature = sk.sign(message)

# New Ed25519 code
from metamui_crypto import Ed25519
keypair = Ed25519.generate_keypair()
signature = Ed25519.sign(message, keypair.private_key)

# Benefits:
# - Faster operations
# - Smaller keys
# - Deterministic signatures
# - No random number issues

From RSA to Ed25519

# Consider hybrid approach for compatibility
class HybridSigner:
    def __init__(self):
        self.ed25519_key = Ed25519.generate_keypair()
        # Keep RSA for legacy systems
        self.rsa_key = existing_rsa_key
    
    def sign(self, message, use_legacy=False):
        if use_legacy:
            return rsa_sign(message, self.rsa_key)
        return Ed25519.sign(message, self.ed25519_key.private_key)

Resources