Ed25519 Security-Focused API Documentation
Version: 1.0
Last Updated: 2025-07-05
**Security Classification: PUBLIC
Author: Phantom (phantom@metamui.id)
Overview
Ed25519 is a high-security digital signature algorithm providing 128-bit security level. This documentation provides security-focused guidance for using the Ed25519 implementation in the MetaMUI cryptographic library.
Security Level: 128 bits
Key Size: 32 bytes (256 bits)
Signature Size: 64 bytes
Security Warnings ⚠️
- Private Key Entropy: Private keys MUST have at least 128 bits of entropy
- Key Storage: Private keys must be stored encrypted and cleared from memory after use
- Deterministic Nonces: Ed25519 uses deterministic nonces - do NOT attempt to provide random nonces
- Quantum Vulnerability: Ed25519 is NOT quantum-resistant
API Functions
generate_keypair() -> (PrivateKey, PublicKey)
Security Contract:
- Preconditions:
- System random number generator must be properly seeded
- At least 256 bits of entropy available
- Postconditions:
- Private key has exactly 32 random bytes
- Public key is deterministically derived from private key
- Private key material is not left in temporary buffers
Attack Resistance: | Attack Type | Protected | Notes | |————-|———–|——-| | Weak RNG | ✅ | Uses system CSPRNG | | Timing Attack | ✅ | Constant-time scalar multiplication | | Memory Disclosure | ✅ | Secure memory clearing | | Fault Injection | ❌ | No protection against hardware faults |
Secure Usage Example:
# SECURE: Generate keypair with proper entropy
private_key, public_key = ed25519.generate_keypair()
try:
# Use the keys
signature = ed25519.sign(message, private_key)
finally:
# Always clear private key from memory
secure_clear(private_key)
Common Mistakes:
# INSECURE: Using weak entropy source
import random
random.seed(12345) # NEVER DO THIS
weak_bytes = bytes([random.randint(0, 255) for _ in range(32)])
private_key = ed25519.from_seed(weak_bytes) # VULNERABLE!
# INSECURE: Not clearing private key
private_key, public_key = ed25519.generate_keypair()
signature = ed25519.sign(message, private_key)
# private_key remains in memory - SECURITY RISK!
sign(message: bytes, private_key: PrivateKey) -> Signature
Security Contract:
- Preconditions:
private_keymust be exactly 32 bytesprivate_keymust have been generated with ≥128 bits entropymessagecan be any length (hashed internally)
- Postconditions:
- Returns deterministic 64-byte signature
- Same message + key always produces same signature
- Private key is cleared from internal buffers
- Execution time independent of key/message content
Attack Resistance: | Attack Type | Protected | Notes | |————-|———–|——-| | Timing Attack | ✅ | Constant-time implementation | | Malleability | ✅ | Canonical signature encoding | | Nonce Reuse | ✅ | Deterministic nonces from RFC 8032 | | Power Analysis | ⚠️ | Basic protection, not DPA-resistant | | Fault Injection | ❌ | No protection |
Security Requirements:
- The implementation MUST follow RFC 8032 exactly
- Nonces are generated deterministically from private key and message
- Do NOT modify the nonce generation - this breaks security
Secure Usage Example:
// SECURE: Sign with proper key handling
let private_key = secure_load_private_key()?;
let signature = ed25519::sign(&message, &private_key)?;
secure_zero(&mut private_key); // Clear immediately after use
// SECURE: Sign multiple messages
let private_key = secure_load_private_key()?;
let signatures: Vec<Signature> = messages
.iter()
.map(|msg| ed25519::sign(msg, &private_key))
.collect::<Result<_, _>>()?;
secure_zero(&mut private_key);
Common Mistakes:
// INSECURE: Reusing nonces (if implementation allowed it)
let nonce = generate_random_nonce(); // NEVER DO THIS
let sig1 = ed25519_sign_with_nonce(msg1, private_key, nonce);
let sig2 = ed25519_sign_with_nonce(msg2, private_key, nonce); // KEY COMPROMISED!
// INSECURE: Leaving private key in memory
let private_key = load_private_key()?;
let signature = ed25519::sign(&message, &private_key)?;
// private_key not cleared - can be extracted from memory dump
verify(message: bytes, signature: Signature, public_key: PublicKey) -> bool
Security Contract:
- Preconditions:
signaturemust be exactly 64 bytespublic_keymust be exactly 32 bytespublic_keymust be a valid curve point
- Postconditions:
- Returns true only for valid signatures
- Execution time independent of signature validity
- No information leaked about why verification failed
Attack Resistance: | Attack Type | Protected | Notes | |————-|———–|——-| | Timing Attack | ✅ | Constant-time verification | | Malleability | ✅ | Rejects non-canonical signatures | | Invalid Curve | ✅ | Validates public key is on curve | | Small Subgroup | ✅ | Cofactor multiplication included |
Security Warnings:
- Verification failure reveals NOTHING about why it failed
- Always check return value - do not assume success
- Public keys from untrusted sources must be validated
Secure Usage Example:
// SECURE: Verify with proper error handling
const isValid = ed25519.verify(message, signature, publicKey);
if (!isValid) {
// Log failure but don't reveal details to attacker
logger.warn('Signature verification failed');
throw new Error('Invalid signature');
}
// SECURE: Batch verification for performance
const results = ed25519.batchVerify(messages, signatures, publicKeys);
const allValid = results.every(r => r === true);
if (!allValid) {
// Don't reveal which signatures failed
throw new Error('Batch verification failed');
}
Common Mistakes:
// INSECURE: Revealing verification details
try {
ed25519.verify(message, signature, publicKey);
} catch (e) {
// NEVER DO THIS - reveals information to attacker
return { error: `Verification failed: ${e.message}` };
}
// INSECURE: Not checking return value
ed25519.verify(message, signature, publicKey);
// Assuming success without checking - SECURITY RISK!
processAuthenticatedMessage(message);
from_seed(seed: bytes) -> PrivateKey
Security Contract:
- Preconditions:
seedmust be exactly 32 bytesseedmust contain ≥128 bits of entropyseedshould be generated by CSPRNG
- Postconditions:
- Returns deterministic private key
- Same seed always produces same key
- Seed is not modified or retained
Attack Resistance: | Attack Type | Protected | Notes | |————-|———–|——-| | Weak Seeds | ❌ | Caller responsible for entropy | | Rainbow Tables | ⚠️ | Only if seed has good entropy | | Timing Attack | ✅ | Constant-time derivation |
Security Requirements:
- NEVER use low-entropy seeds (timestamps, counters, PIDs)
- NEVER use predictable seeds (passwords without KDF)
- ALWAYS use cryptographically secure random seeds
Secure Usage Example:
// SECURE: Generate high-entropy seed
let seed = try SecureRandom.generateBytes(count: 32)
let privateKey = try Ed25519.fromSeed(seed)
defer {
secureZero(&seed)
secureZero(&privateKey)
}
// SECURE: Derive from password using KDF
let salt = try SecureRandom.generateBytes(count: 32)
let seed = try Argon2.deriveKey(
password: password,
salt: salt,
length: 32,
iterations: 4,
memory: 65536,
parallelism: 1
)
let privateKey = try Ed25519.fromSeed(seed)
defer { secureZero(&seed) }
Common Mistakes:
// INSECURE: Low entropy seed
let timestamp = UInt32(Date().timeIntervalSince1970)
var seed = Data(count: 32)
seed[0..<4] = withUnsafeBytes(of: timestamp) { Data($0) }
let privateKey = Ed25519.fromSeed(seed) // EASILY BRUTE-FORCED!
// INSECURE: Predictable seed from password
let seed = password.data(using: .utf8)!.sha256() // NO KDF!
let privateKey = Ed25519.fromSeed(seed) // VULNERABLE TO DICTIONARY ATTACK!
// INSECURE: Reusing seeds across different contexts
let masterSeed = loadMasterSeed()
let signingKey = Ed25519.fromSeed(masterSeed)
let encryptionKey = X25519.fromSeed(masterSeed) // KEY REUSE - DANGEROUS!
Security Best Practices
Key Management
- Generation: Always use system CSPRNG for key generation
- Storage: Encrypt private keys at rest using authenticated encryption
- Transport: Never transmit private keys; use secure key agreement
- Rotation: Implement regular key rotation policies
- Destruction: Clear keys from memory immediately after use
Implementation Security
- Constant Time: All operations are constant-time by default
- No Branching: Never branch on secret data
- Memory Safety: Use secure allocators where available
- Stack Clearing: Enable stack clearing in sensitive functions
Common Integration Patterns
Secure Authentication Flow
# 1. Server generates challenge
challenge = secure_random(32)
session['challenge'] = challenge
send_to_client(challenge)
# 2. Client signs challenge
signature = ed25519.sign(challenge + client_id, private_key)
secure_clear(private_key)
send_to_server(signature, client_id, public_key)
# 3. Server verifies
expected_message = session['challenge'] + client_id
if not ed25519.verify(expected_message, signature, public_key):
raise AuthenticationError()
# Clear challenge to prevent replay
del session['challenge']
Secure Document Signing
// 1. Hash document first (for large files)
let document_hash = sha512::hash(&document);
// 2. Create signature with metadata
let to_sign = format!(
"Document signature v1.0\n\
Hash: {}\n\
Timestamp: {}\n\
Signer: {}",
hex::encode(&document_hash),
timestamp,
signer_id
);
// 3. Sign with proper key handling
let private_key = secure_key_store.get_signing_key()?;
let signature = ed25519::sign(to_sign.as_bytes(), &private_key)?;
secure_key_store.clear_signing_key();
// 4. Create verifiable signature bundle
let signature_bundle = SignatureBundle {
signature,
public_key,
document_hash,
timestamp,
signer_id,
};
Performance Considerations
| Operation | Time | Security Notes |
|---|---|---|
| Key Generation | ~250 μs | Includes secure random generation |
| Signing | ~70 μs | Constant time regardless of message |
| Verification | ~180 μs | Constant time for both success/failure |
| Batch Verify (n=100) | ~12 ms | ~3x faster than individual verification |
Platform-Specific Security Notes
Python
- Uses
secretsmodule for random generation - Memory clearing via
ctypesmemset - Consider
cryptographylibrary for FIPS compliance
Rust
- Uses
getrandomcrate for platform RNG - Implements
Zeroizetrait for automatic clearing - Stack allocated keys cleared automatically
TypeScript/JavaScript
- Uses
crypto.getRandomValues()for RNG - Manual zeroing of typed arrays required
- WebCrypto API provides additional protections
Swift
- Uses
SecRandomCopyBytesfor RNG - Automatic reference counting may retain keys
- Use
deferblocks for cleanup
Kotlin
- Uses
SecureRandomfor JVM platforms - Manual array clearing required
- Consider Android Keystore for key storage
Compliance and Standards
- RFC 8032: EdDSA Signature Algorithms
- NIST SP 800-186: Discrete Logarithm-Based Crypto
- FIPS 186-5: Digital Signature Standard (pending)
- Common Criteria: EAL4+ achievable with secure usage
Security Auditing
Verification Checklist
- All private keys generated with ≥128 bits entropy
- Private keys cleared from memory after use
- No custom nonce generation implemented
- Signature verification results not detailed in errors
- No branching on secret data in custom code
- Key storage uses authenticated encryption
- Regular key rotation policy in place
Logging and Monitoring
# SECURE: Log operations without revealing secrets
logger.info(f"Signature created for document {doc_id}")
logger.info(f"Signature verified for user {user_id}")
# INSECURE: Never log private keys or signatures
logger.debug(f"Private key: {private_key}") # NEVER!
logger.debug(f"Signature: {signature}") # NEVER!
Security Analysis
Threat Model: Ed25519 Threat Model
The comprehensive threat analysis covers:
- Algorithm-specific attack vectors
- Implementation vulnerabilities
- Side-channel considerations
- Quantum resistance analysis (where applicable)
- Deployment recommendations
For complete security analysis and risk assessment, see the dedicated threat model documentation.
References
Support
Security Issues: security@metamui.id
Documentation Updates: phantom@metamui.id
Vulnerability Disclosure: See SECURITY.md
Document Version: 1.0
Review Cycle: Quarterly
Next Review: 2025-04-05
Classification: PUBLIC