High-performance W3C Verifiable Credentials with Ed25519 cryptography and CESR encoding
Signify is an Elixir library for creating and verifying W3C Verifiable Credentials using Ed25519 cryptography with CESR (Composable Event Streaming Representation) encoding. It provides fast, secure, trust-based identity verification for legal entities and individuals.
Think of Signify as JWT for verifiable identity credentials - but with:
- ✅ Stronger cryptography (Ed25519 instead of RSA)
- ✅ W3C standards compliance
- ✅ KERI/CESR support for composable credentials
- ✅ Blazing fast performance (Rust NIF)
- ✅ Simple Elixir API
Add signify to your mix.exs:
def deps do
[
{:signify, "~> 0.1.0"}
]
endThen run:
mix deps.get
mix compile# 1. Create a signing key
{:ok, signer} = Signify.Signer.new_random(true)
# 2. Sign a message
message = "Hello, Signify!"
{:ok, signature} = Signify.Signer.sign(signer, message)
# 3. Get the verification key
{:ok, verfer} = Signify.Signer.verfer(signer)
# 4. Verify the signature
{:ok, true} = Signify.Verfer.verify(verfer, signature, message)- ✅ Ed25519 Signatures - Fast, secure, 64-byte signatures
- ✅ CESR Encoding - Composable Event Streaming Representation
- ✅ KERI Protocol - Inception, Rotation, and Interaction events
- ✅ Key Event Log - Immutable event storage and validation
- ✅ Key Rotation - Secure identifier key rotation without changing the ID
- ✅ W3C Verifiable Credentials - Standard-compliant credentials
- ✅ DID Support - Decentralized Identifiers (
did:keri:...) - ✅ vLEI Credentials - Verifiable Legal Entity Identifiers
- ✅ High Performance - Rust NIF for cryptographic operations
- ✅ Type Safety - Full Elixir typespecs and Dialyzer support
Signify implements the KERI (Key Event Receipt Infrastructure) protocol in the Elixir layer with cryptographic operations in Rust. This provides:
- Self-Certifying Identifiers (AIDs) - Cryptographically derived identifiers
- Key Event Log (KEL) - Immutable, append-only event history
- Key State Management - Current cryptographic configuration tracking
- Event Types:
- Inception (icp) - Create a new identifier (sequence 0)
- Rotation (rot) - Rotate signing keys (sequence > 0)
- Interaction (ixn) - Anchor data without key changes (sequence > 0)
- Pre-Rotation - Commit to next keys before rotation
- Witness Support - Decentralized witness pool management
- BLAKE3 Hashing - Fast cryptographic digests via Rust NIF
| Primitive | Description | Size |
|---|---|---|
| Ed25519 | Digital signatures | 64 bytes |
| BLAKE3 | Fast cryptographic hashing | 32 bytes |
| CESR | Composable encoding | Variable |
| Seed | Private key seed | 32 bytes |
| Public Key | Verification key | 32 bytes |
Signify uses a hybrid architecture optimized for performance and maintainability:
┌─────────────────────────────────┐
│ Elixir Application Layer │ ← Business Logic
│ - Credential creation │
│ - W3C VC/VP formatting │
│ - High-level API │
└────────────┬────────────────────┘
│
▼
┌─────────────────────────────────┐
│ Signify Elixir Wrapper │ ← Ergonomic API
│ - Error handling │
│ - Documentation │
│ - Type specs │
└────────────┬────────────────────┘
│ Rustler NIF
▼
┌─────────────────────────────────┐
│ signify_rs (Rust) │ ← Cryptography
│ - Ed25519 operations │
│ - CESR encoding/decoding │
│ - ed25519-dalek library │
└─────────────────────────────────┘
Why this architecture?
- Rust handles cryptography (fast, safe, audited libraries)
- Elixir handles business logic (flexible, maintainable, testable)
- Best of both worlds - Performance + Productivity
Manage Ed25519 private keys and create signatures.
# Create a random signer
{:ok, signer} = Signify.Signer.new_random(true)
# Sign data
{:ok, signature} = Signify.Signer.sign(signer, "message")
# Export/Import keys (CESR QB64 format)
{:ok, qb64} = Signify.Signer.to_qb64(signer)
{:ok, signer} = Signify.Signer.from_qb64(qb64, true)Verify Ed25519 signatures with public keys.
# Get verifier from signer
{:ok, verfer} = Signify.Signer.verfer(signer)
# Verify signature
{:ok, true} = Signify.Verfer.verify(verfer, signature, "message")
# Export/Import public key
{:ok, qb64} = Signify.Verfer.to_qb64(verfer)
{:ok, verfer} = Signify.Verfer.from_qb64(qb64)Create and verify W3C Verifiable Credentials.
# Create a credential
credential = %{
"@context" => ["https://www.w3.org/2018/credentials/v1"],
"type" => ["VerifiableCredential", "EmployeeCredential"],
"issuer" => "did:keri:issuer_id",
"issuanceDate" => DateTime.utc_now() |> DateTime.to_iso8601(),
"credentialSubject" => %{
"id" => "did:keri:employee_id",
"name" => "John Doe",
"role" => "Software Engineer"
}
}
# Sign the credential
{:ok, signed_credential} = Signify.Credential.sign(credential, signer)
# Verify the credential
{:ok, true} = Signify.Credential.verify(signed_credential, verfer)Presentations combine multiple credentials for authentication:
# Create multiple credentials
{:ok, role_credential} = Signify.Credential.create(%{
type: ["VerifiableCredential", "RoleCredential"],
credentialSubject: %{id: holder_did, role: "Engineer"}
}, issuer_signer)
{:ok, profile_credential} = Signify.Credential.create(%{
type: ["VerifiableCredential", "ProfileCredential"],
credentialSubject: %{id: holder_did, name: "Alice"}
}, issuer_signer)
# Create a presentation combining both credentials
presentation = %{
"@context" => ["https://www.w3.org/2018/credentials/v1"],
"type" => ["VerifiablePresentation"],
"holder" => holder_did,
"verifiableCredential" => [role_credential, profile_credential]
}
# Add challenge and domain for replay protection
secured_presentation = Map.merge(presentation, %{
"challenge" => "efae261a-89ec-428e-a854-c2d64037270c",
"domain" => "https://example.com",
"customPayload" => %{
"session_id" => "abc123",
"timestamp" => DateTime.utc_now() |> DateTime.to_iso8601()
}
})
# Sign the presentation with holder's key
{:ok, signed_vp} = Signify.Credential.sign(secured_presentation, holder_signer)
# Verify the presentation
{:ok, true} = Signify.Credential.verify(signed_vp, holder_verfer)Verifiable Legal Entity Identifiers (vLEI) for organizational identity:
# vLEI data for a legal entity
lei_data = %{
"@context" => [
"https://www.w3.org/2018/credentials/v1",
"https://www.gleif.org/vlei/v1"
],
"type" => ["VerifiableCredential", "vLEICredential"],
"issuer" => %{
"id" => "did:keri:qualified_vlei_issuer",
"name" => "GLEIF Qualified vLEI Issuer"
},
"issuanceDate" => DateTime.utc_now() |> DateTime.to_iso8601(),
"expirationDate" => ~U[2025-12-31 23:59:59Z] |> DateTime.to_iso8601(),
"credentialSubject" => %{
"id" => "did:keri:entity_id",
"LEI" => "98450012E89468BE9808",
"legalName" => "ACME Corporation",
"entityStatus" => "ACTIVE",
"legalJurisdiction" => "US-DE",
"entityCategory" => "GENERAL"
}
}
# Sign the vLEI credential
{:ok, vlei_credential} = Signify.Credential.sign(lei_data, issuer_signer)
# Verify the vLEI credential
{:ok, true} = Signify.Credential.verify(vlei_credential, issuer_verfer)
# Use in presentations for organizational authentication
org_presentation = %{
"@context" => ["https://www.w3.org/2018/credentials/v1"],
"type" => ["VerifiablePresentation"],
"holder" => "did:keri:entity_id",
"verifiableCredential" => [vlei_credential],
"challenge" => "trade-portal-auth-12345",
"domain" => "https://trade-portal.example.com"
}
{:ok, signed_org_vp} = Signify.Credential.sign(org_presentation, entity_signer)Create and manage KERI identifiers with key rotation support.
# Create signers for current and next keys
{:ok, signer1} = Signify.Signer.new_random(true)
{:ok, signer2} = Signify.Signer.new_random(true)
{:ok, signer3} = Signify.Signer.new_random(true)
# Create a new KERI identifier (inception)
{:ok, aid} = Signify.KERI.create_identifier(%{
signer: signer1,
next_signer: signer2,
witnesses: [],
witness_threshold: 0
})
# The identifier is now created with sequence 0
aid.prefix # => "EKN5TkDL1..."
aid.sequence # => 0
# Rotate keys (creates rotation event at sequence 1)
{:ok, rotated_state} = Signify.KERI.rotate_keys(aid.prefix, %{
current_signer: signer1, # Current key (proves ownership)
new_signer: signer2, # New current key
next_signer: signer3 # Pre-commitment to next rotation
})
rotated_state.sequence # => 1
rotated_state.last_event_type # => :rot
# Create interaction event (anchor data without key change)
{:ok, interaction_state} = Signify.KERI.create_interaction(aid.prefix, %{
signer: signer2,
anchors: ["ECredentialDigest123..."]
})
interaction_state.sequence # => 2
interaction_state.last_event_type # => :ixn
# Export identifier for sharing
{:ok, exported} = Signify.KERI.export_identifier(aid.prefix)
# Returns full identifier state as JSON-serializable map# Create multiple signers
{:ok, signer1} = Signify.Signer.new_random(true)
{:ok, signer2} = Signify.Signer.new_random(true)
# Sign with indexed signatures
{:ok, sig1} = Signify.Signer.sign_indexed(signer1, message, 0)
{:ok, sig2} = Signify.Signer.sign_indexed(signer2, message, 1)
# Verify multi-sig (2-of-2)
{:ok, verfer1} = Signify.Signer.verfer(signer1)
{:ok, verfer2} = Signify.Signer.verfer(signer2)
{:ok, true} = Signify.Verfer.verify(verfer1, sig1, message)
{:ok, true} = Signify.Verfer.verify(verfer2, sig2, message)# Transferable key (can be rotated in KERI)
{:ok, signer_t} = Signify.Signer.new_random(true)
# CESR code: "D" (Ed25519 transferable)
# Non-transferable key (cannot be rotated)
{:ok, signer_nt} = Signify.Signer.new_random(false)
# CESR code: "B" (Ed25519 non-transferable)Signify uses CESR (Composable Event Streaming Representation) for encoding cryptographic primitives. CESR provides:
- Self-describing - Code prefix identifies the type
- Composable - Easy to concatenate and parse
- Efficient - Minimal overhead (~4% for base64)
- Interoperable - Works with KERI ecosystem
| Code | Description | Example QB64 |
|---|---|---|
A |
Ed25519 Seed (32 bytes) | AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEk |
D |
Ed25519 Public Key (32 bytes) | DKvp4T9yNzJxQ3mH5c0v8L2fR9pD1nW6sX4jG7kB3hM8 |
0B |
Ed25519 Signature (64 bytes) | 0BABCDEFabcdef... (88 chars) |
E |
BLAKE3-256 Digest (32 bytes) | EKvp4T9yNzJxQ3mH5c0v8L2fR9pD1nW6sX4jG7kB3hM8 |
QB64 = CODE + BASE64URL(raw_bytes)
Example:
Raw bytes: [0x01, 0x02, 0x03, ..., 0x20] (32 bytes)
Code: "A"
Base64: "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA"
QB64: "AAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA"
Signify includes comprehensive test coverage:
# Run Elixir tests
mix test
# Run with coverage
mix test --cover
# Run Rust tests
cd native/signify_rs
cargo test
# Run all tests
mix test && cd native/signify_rs && cargo test && cd ../..- Elixir Tests: 39 passing ✅
- 18 Phase 1 tests (Inception, KEL, Key State)
- 21 Phase 2 tests (Rotation, Interaction, Mixed sequences)
- Rust Tests: 149 passing ✅
- Coverage: >90% for core modules
Working examples are available in the examples/ directory to help you get started:
# Basic signing and verification
mix run examples/sign_and_verify.exs
# Load keys from CESR files
mix run examples/load_keri_cesr.exsAvailable examples:
| Example | Description |
|---|---|
sign_and_verify.exs |
Basic Ed25519 signing and verification workflow |
load_keri_cesr.exs |
Load keys from CESR credential files |
Creating your own examples:
# examples/my_credential.exs
{:ok, signer} = Signify.Signer.new_random(true)
{:ok, verfer} = Signify.Signer.verfer(signer)
credential = %{
"@context" => ["https://www.w3.org/2018/credentials/v1"],
"type" => ["VerifiableCredential"],
"issuer" => "did:keri:example",
"credentialSubject" => %{"name" => "Example"}
}
{:ok, signed} = Signify.Credential.sign(credential, signer)
{:ok, true} = Signify.Credential.verify(signed, verfer)
IO.inspect(signed, label: "Signed Credential")Signify uses Rust NIFs for cryptographic operations, providing excellent performance:
| Operation | Time (avg) | Throughput | vs Pure Elixir |
|---|---|---|---|
| Key Generation | ~15-20μs | ~50-65K ops/sec | 10x faster |
| JSON Signing | ~40-45μs | ~22-25K ops/sec | 8x faster |
| Signature Verification | ~50-70μs | ~14-20K ops/sec | 6x faster |
| CESR Encoding | ~5-10μs | ~100-200K ops/sec | 15x faster |
| KERI Inception | ~60-80μs | ~12-16K ops/sec | N/A |
| KERI Rotation | ~100-120μs | ~8-10K ops/sec | N/A |
Actual benchmark results from signify_native (cesride-based):
generate_keypair: ~15.69 μs (63,720 ips)sign_json: ~42.66 μs (23,440 ips)verify_signature: ~46.50 μs (21,510 ips)
Rust NIF provides near-native performance, typically 25-50x faster than pure Elixir crypto implementations.
Run benchmarks yourself:
# Run basic benchmarks
mix run bench/signify_bench.exs
# Run signify_rs benchmarks
mix run bench/signify_rs_bench.exsBenchmarks run on: Intel Core i7-10510U @ 1.80GHz, 31GB RAM, Linux
Signify builds on industry-standard, audited cryptographic libraries:
- ed25519-dalek v2.1 - Ed25519 signatures (Rust)
- BLAKE3 v1.5 - Fast cryptographic hashing
- rand v0.8 - Cryptographically secure RNG
- ✅ Constant-time operations - Resistant to timing attacks
- ✅ Memory safety - Rust's ownership prevents buffer overflows
- ✅ Input validation - Strict size and format checks
- ✅ Deterministic signatures - Ed25519 RFC 8032 compliance
- ✅ Secure random - OS-level entropy for key generation
- Store private keys securely (HSM, encrypted storage)
- Never log or transmit private keys
- Use secure deletion for key material
- Implement key rotation policies
- Always verify credential signatures
- Check expiration dates
- Validate issuer identity
- Implement revocation checking
# Clone the repository
git clone https://github.com/dataswyft/signify.git
cd signify
# Install dependencies
mix deps.get
# Compile (includes Rust NIF)
mix compile
# Run tests
mix test
# Generate documentation
mix docssignify/
├── lib/
│ ├── signify.ex # Main module
│ ├── signify/
│ │ ├── signer.ex # Ed25519 signing
│ │ ├── verfer.ex # Ed25519 verification
│ │ ├── credential.ex # W3C VC helpers
│ │ ├── native.ex # NIF module
│ │ ├── keri.ex # KERI public API
│ │ └── keri/
│ │ ├── events/ # KERI event types
│ │ │ ├── event.ex # Base event module
│ │ │ ├── inception.ex # Inception events
│ │ │ ├── rotation.ex # Rotation events
│ │ │ └── interaction.ex # Interaction events
│ │ ├── kel/
│ │ │ └── log.ex # Key Event Log
│ │ └── state/
│ │ └── key_state.ex # Key State tracking
├── native/
│ └── signify_rs/ # Rust implementation
│ ├── src/
│ │ ├── core/ # Core primitives
│ │ │ ├── signer.rs # Ed25519 signing
│ │ │ ├── verfer.rs # Ed25519 verification
│ │ │ ├── diger.rs # BLAKE3 hashing
│ │ │ ├── matter.rs # CESR encoding
│ │ │ └── ...
│ │ ├── nif.rs # NIF bindings
│ │ └── lib.rs # Entry point
│ └── Cargo.toml
├── test/
│ ├── signify_test.exs # Core tests
│ ├── signify_keri_test.exs # Phase 1 KERI tests
│ └── signify_keri_phase2_test.exs # Phase 2 KERI tests
├── bench/ # Benchmarks
├── documentation/ # Additional docs
│ ├── GAP_ANALYSIS.md # Implementation analysis
│ └── ...
└── reference/
└── signify-ts/ # TypeScript reference
| Feature | Signify | Joken (JWT) | KERI-Elixir |
|---|---|---|---|
| Signatures | Ed25519 | RS256/HS256 | Ed25519 |
| W3C VC | ✅ | ❌ | ✅ |
| CESR | ✅ | ❌ | ✅ |
| Performance | ⚡ Fast (Rust) | Medium | Medium |
| KERI Protocol | Core only | ❌ | Full |
| Maturity | Beta | Stable | Alpha |
When to use Signify:
- ✅ W3C Verifiable Credentials
- ✅ High-performance signature operations
- ✅ CESR/KERI compatibility needed
- ✅ Ed25519 cryptography preferred
When to use alternatives:
- JWT with existing infrastructure → Joken
- Full KERI protocol stack → KERI-Elixir (when available)
- Simple HMAC signing → Phoenix.Token
- Ed25519 signing and verification
- CESR encoding/decoding
- BLAKE3-256 hashing via Rust NIF
- KERI Inception events (identifier creation)
- KERI Rotation events (key rotation)
- KERI Interaction events (data anchoring)
- Key Event Log (KEL) management
- Key State tracking
- Basic W3C VC support
- Rust NIF implementation
- Comprehensive tests (39 passing)
- X25519 encryption support
- Salter (passphrase-based key derivation)
- ECDSA secp256r1 support
- Credential templates
- More digest algorithms
- KERI Witness support (receipts, escrows)
- KERI Delegation events
- Credential integration with KERI
- Revocation checking
- Credential status lists
- DID resolution
- vLEI schema validation
- KERI Multi-sig coordination
- Production-ready API
- Full W3C VC Data Model 2.0
- Performance optimizations
- Security audit
- Comprehensive examples
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add tests for new functionality
- Run the test suite (
mix test && cd native/signify_rs && cargo test) - Commit with conventional commits (
git commit -m 'feat: add amazing feature') - Push to your branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Follow Elixir style guide (use
mix format) - Follow Rust style guide (use
cargo fmt) - Add typespecs to all public functions
- Write comprehensive tests (aim for >90% coverage)
- Update documentation for API changes
- Run
mix credoandmix dialyzerbefore committing
This project is licensed under the MIT License - see the LICENSE file for details.
- KERI Protocol - WebOfTrust
- signify-ts - TypeScript reference implementation
- ed25519-dalek - Rust Ed25519 library
- Rustler - Elixir-Rust NIF bindings
- Documentation: https://hexdocs.pm/signify
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Made with ❤️ by the Dataswyft team
If you find Signify useful, please give it a ⭐ on GitHub!