Skip to content

dataswift/signify

Repository files navigation

Signify

High-performance W3C Verifiable Credentials with Ed25519 cryptography and CESR encoding

Hex.pm Documentation License: MIT

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.

🎯 What is Signify?

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

🚀 Quick Start

Installation

Add signify to your mix.exs:

def deps do
  [
    {:signify, "~> 0.1.0"}
  ]
end

Then run:

mix deps.get
mix compile

Basic Usage

# 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)

📚 Features

Core Capabilities

  • 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

KERI Protocol 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

Cryptographic Primitives

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

🏗️ Architecture

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

📖 Documentation

Core Modules

Signify.Signer

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)

Signify.Verfer

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)

Signify.Credential

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)
Creating Verifiable Presentations

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)
vLEI Credentials

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)

Signify.KERI

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

Advanced Usage

Multi-Signature Support

# 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 vs Non-Transferable Keys

# 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)

🔍 CESR Encoding

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

CESR Code Examples

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

Encoding Format

QB64 = CODE + BASE64URL(raw_bytes)

Example:
  Raw bytes: [0x01, 0x02, 0x03, ..., 0x20]  (32 bytes)
  Code: "A"
  Base64: "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA"
  QB64: "AAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA"

🧪 Testing

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 ../..

Test Results

  • 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

📝 Examples

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.exs

Available 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")

⚡ Performance

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.exs

Benchmarks run on: Intel Core i7-10510U @ 1.80GHz, 31GB RAM, Linux

🔒 Security

Cryptographic Libraries

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

Security Features

  • 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

Security Considerations

⚠️ Key Management:

  • Store private keys securely (HSM, encrypted storage)
  • Never log or transmit private keys
  • Use secure deletion for key material
  • Implement key rotation policies

⚠️ Credential Validation:

  • Always verify credential signatures
  • Check expiration dates
  • Validate issuer identity
  • Implement revocation checking

🔧 Development

Building from Source

# 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 docs

Project Structure

signify/
├── 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

📊 Comparison with Other Libraries

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

🗺️ Roadmap

Version 0.1.0 (Current) ✅

  • 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)

Version 0.2.0 (Planned)

  • X25519 encryption support
  • Salter (passphrase-based key derivation)
  • ECDSA secp256r1 support
  • Credential templates
  • More digest algorithms
  • KERI Witness support (receipts, escrows)
  • KERI Delegation events

Version 0.3.0 (Future)

  • Credential integration with KERI
  • Revocation checking
  • Credential status lists
  • DID resolution
  • vLEI schema validation
  • KERI Multi-sig coordination

Version 1.0.0 (Stable)

  • Production-ready API
  • Full W3C VC Data Model 2.0
  • Performance optimizations
  • Security audit
  • Comprehensive examples

🤝 Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Add tests for new functionality
  5. Run the test suite (mix test && cd native/signify_rs && cargo test)
  6. Commit with conventional commits (git commit -m 'feat: add amazing feature')
  7. Push to your branch (git push origin feature/amazing-feature)
  8. Open a Pull Request

Development Guidelines

  • 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 credo and mix dialyzer before committing

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

  • KERI Protocol - WebOfTrust
  • signify-ts - TypeScript reference implementation
  • ed25519-dalek - Rust Ed25519 library
  • Rustler - Elixir-Rust NIF bindings

📞 Support

📚 Further Reading

W3C Standards

KERI Protocol

Cryptography


Made with ❤️ by the Dataswyft team

If you find Signify useful, please give it a ⭐ on GitHub!

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published