Ed25519 Signatures
Deep dive into Ed25519 cryptographic signatures.
What is Ed25519?
Ed25519 is a high-speed, high-security digital signature scheme using:
- Curve: Edwards curve (Curve25519)
- Hash: SHA-512
- Key size: 32 bytes (256 bits)
- Signature size: 64 bytes
Why Ed25519?
| Property | Ed25519 | RSA-2048 | ECDSA-P256 |
|---|---|---|---|
| Public key size | 32 bytes | 256 bytes | 64 bytes |
| Signature size | 64 bytes | 256 bytes | 64 bytes |
| Verification speed | ~70μs | ~30μs | ~150μs |
| Signing speed | ~50μs | ~1ms | ~150μs |
| Security level | 128-bit | 112-bit | 128-bit |
Ed25519 offers the best balance of security, speed, and key/signature size.
Key Generation
JavaScript (tweetnacl)
javascript
import nacl from 'tweetnacl';
// Generate a random keypair
const keypair = nacl.sign.keyPair();
// Public key: 32 bytes → 64 hex characters
const publicKey = Buffer.from(keypair.publicKey).toString('hex');
// Secret key: 64 bytes (includes public key)
const secretKey = keypair.secretKey;
console.log('Public Key:', publicKey);
console.log('Public Key length:', publicKey.length); // 64Python (PyNaCl)
python
from nacl.signing import SigningKey
# Generate a random keypair
key = SigningKey.generate()
# Public key: 32 bytes → 64 hex characters
public_key = key.verify_key.encode().hex()
print(f"Public Key: {public_key}")
print(f"Public Key length: {len(public_key)}") # 64Signing Process
Step 1: Create the Message
The message is a JSON object specific to the operation:
javascript
const message = {
purpose: "registration",
public_key: publicKey,
profile: { /* ... */ },
timestamp: Date.now()
};Step 2: Canonical JSON
Convert to canonical form (keys sorted alphabetically, no whitespace):
javascript
function canonicalJson(obj) {
if (obj === null) return 'null';
if (typeof obj === 'undefined') return undefined;
if (Array.isArray(obj)) {
return '[' + obj.map(canonicalJson).join(',') + ']';
}
if (typeof obj === 'object') {
const keys = Object.keys(obj).sort();
const pairs = keys
.map(k => {
const v = canonicalJson(obj[k]);
return v !== undefined ? `"${k}":${v}` : undefined;
})
.filter(p => p !== undefined);
return '{' + pairs.join(',') + '}';
}
return JSON.stringify(obj);
}Example transformation:
javascript
// Input
{ "name": "Alice", "age": 30, "city": null }
// Output (canonical)
{"age":30,"city":null,"name":"Alice"}Step 3: Sign the Bytes
Sign the UTF-8 encoded canonical JSON:
javascript
const messageBytes = Buffer.from(canonicalJson(message));
const signatureBytes = nacl.sign.detached(messageBytes, secretKey);
const signature = Buffer.from(signatureBytes).toString('hex');The signature is 64 bytes → 128 hex characters.
Verification Process
The server verifies signatures by:
- Reconstructing the expected message
- Converting to canonical JSON
- Verifying the signature with the public key
javascript
// Server-side verification
const messageBytes = Buffer.from(canonicalJson(expectedMessage));
const signatureBytes = Buffer.from(signature, 'hex');
const publicKeyBytes = Buffer.from(publicKey, 'hex');
const isValid = nacl.sign.detached.verify(
messageBytes,
signatureBytes,
publicKeyBytes
);Test Vectors
Use these to verify your implementation:
Test Vector 1: Simple Object
json
{
"input": { "a": 1, "b": 2 },
"canonical": "{\"a\":1,\"b\":2}",
"public_key": "5f3d93f26e0cf7cf06b81d7fc7fb1e3b79d15c7b0d0c7f7c0d7c7f7c0d7c7f7c",
"signature": "..."
}Test Vector 2: Nested Object
json
{
"input": { "z": { "b": 2, "a": 1 }, "a": "test" },
"canonical": "{\"a\":\"test\",\"z\":{\"a\":1,\"b\":2}}",
"public_key": "...",
"signature": "..."
}Common Mistakes
1. Non-canonical JSON
javascript
// Wrong: keys not sorted
JSON.stringify({ name: "Alice", age: 30 })
// Produces: {"name":"Alice","age":30}
// Correct: keys sorted
canonicalJson({ name: "Alice", age: 30 })
// Produces: {"age":30,"name":"Alice"}2. Wrong Encoding
javascript
// Wrong: signing the string directly
nacl.sign.detached(message, secretKey);
// Correct: signing UTF-8 bytes
nacl.sign.detached(Buffer.from(message), secretKey);3. Including undefined Values
javascript
// Wrong: undefined becomes "undefined" string
JSON.stringify({ a: undefined })
// Correct: undefined values should be omitted
canonicalJson({ a: undefined }) // Returns "{}"Libraries
JavaScript/TypeScript
bash
npm install tweetnacljavascript
import nacl from 'tweetnacl';Python
bash
pip install pynaclpython
from nacl.signing import SigningKey, VerifyKeyRust
toml
[dependencies]
ed25519-dalek = "2.0"rust
use ed25519_dalek::{SigningKey, Signature, Verifier};Go
bash
go get golang.org/x/crypto/ed25519go
import "golang.org/x/crypto/ed25519"