Signet is a cryptographic vault that sits between you and the world. Your agent proves things about you without revealing you. No passwords handed over. No data copied. No trust required.
cargo install --git https://github.com/jmcentire/signet.git signet
For Everyone
Every time you use an AI agent, buy something online, or prove your age, you hand over real data. Your name. Your address. Your card number. Your medical history. That data gets copied, stored, breached, sold, and correlated.
You are not the customer. You are the product. And your data outlives every company that collects it.
What if you could prove you're over 21 without showing your ID? Pay for something without giving a merchant your card number? Let your AI agent book a flight without it knowing your passport number?
Signet makes this real. Your vault holds your secrets. Your agent carries proofs. The outside world verifies the proof and never sees the original.
"Are you over 21?" Your agent answers instantly with a cryptographic proof. No data leaves the vault. The answer is yes or no, mathematically guaranteed, with no way to learn anything else.
Age verification, preferences, public profile
"What shelves did they order last time?" Your agent knows. It uses that knowledge to reason and act on your behalf. But it never exports the raw data. External services get conclusions, not facts.
Purchase history, browsing context, recommendations
Your credit card. Your passport. Your medical records. Your agent literally cannot read them without your explicit, one-time authorization. It knows they exist. It can ask. It cannot proceed without you.
Payment, identity documents, medical data
A signet was a seal carried by a trusted proxy to sign documents on behalf of a lord — in their absence, with their full authority. The vault is the matrix (the original, never leaves you). The agent is the proxy carrying the seal. The proof is the impression — proves authority without revealing the ring.
Never reverse. The service cannot reach into your vault. Your agent cannot send data upstream. Every disclosure is logged, scoped, and revocable.
For Builders
Signet is a Rust workspace — nine crates, 1,050 tests, clippy clean. It integrates with AI agents via the Model Context Protocol (MCP), meaning it works with Claude today.
The trust hierarchy is simple:
User (Ed25519 root)
+-- Vault (encrypted, never reachable)
+-- Agent (credentialed steward)
+-- Services (petitioners)
signet-core
Shared types and traits. Signer, StorageBackend, AuditChainWriter. The interfaces everything else implements.
signet-vault
Root of trust. BlindDB storage, Ed25519 root keypair, HKDF key hierarchy, AES-256-GCM envelope encryption, hash-chained audit log, passkey/FIDO2 hardware-backed keys. No recovery by design.
signet-cred
Credential issuance with authority protocol. SD-JWT VCs for interop, BBS+ for unlinkable selective disclosure, composable decay model (TTL, use count, rate limits, multi-phasic transitions), authority push/accept flow with Ed25519 double-signing, multi-authority chains, revocation.
signet-proof
Typestate proof pipeline. ProofRequest → ProofPlan → ProofBundle → BoundPresentation. Compile-time guarantees that you can't skip steps.
signet-policy
XACML-for-individuals. Three-way PERMIT / DENY / ANOMALY decisions. Role hierarchy with deny-override combining. Pattern detection with MAC protection.
signet-notify
Authorization channel. HMAC-SHA256 webhook signatures, challenge-response, circuit breaker, scope subset validation to prevent privilege escalation.
signet-mcp
MCP server with middleware pipeline. Session validation, tier classification, policy evaluation, tool execution, audit recording. Seven MCP tools out of the box.
signet-sdk
Four primitives: verify, requestCapability, checkAuthority, parseCredential. Everything a service needs to accept Signet proofs.
use signet_sdk::verify; let result = verify(proof_token, "age_over_21", "shop.example.com"); // result.valid, result.expires, result.scope
use signet_sdk::request_capability; let cap = request_capability(CapabilitySpec { kind: "payment", max_amount: 150, domain: "amazon.com", one_time: true, });
| Layer | Standard | Why |
|---|---|---|
| Identity | Ed25519 fingerprint | Self-certifying. No DID. No registry. |
| Credentials | SD-JWT VC (RFC 9901) | IETF standard, EU wallet compatible |
| Privacy | BBS+ Signatures | Unlinkable selective disclosure |
| Range proofs | Bulletproofs | No trusted setup required |
| Agent protocol | MCP | Native Claude integration |
| Tokens | PASETO v4 | Misuse-resistant, no algorithm confusion |
| Key generation | Ed25519 + HKDF | 256-bit OsRng entropy. No seed phrase. No recovery. |
Get Started
# Install from source cargo install --git https://github.com/jmcentire/signet.git signet # Create your vault (generates root keypair) signet init # Your signet_id: 283iRafkBsTcE1yFuvG4a3WYg3PE # This is your identity. There is no recovery. Guard the key.
# Accept a credential pushed by a trusted authority # Key = (authority, account) -- you see who attested what signet credential accept \ --authority "dmv.ca.gov" \ --review # inspect before accepting # Or import a signed token directly signet credential import < signed_credential.jwt # The credential is signed by the DMV's Ed25519 key. # When you prove "age > 21", the verifier checks the DMV's # signature -- not your word for it.
# Add to ~/Library/Application Support/Claude/claude_desktop_config.json { "mcpServers": { "signet": { "command": "signet", "args": ["serve"] } } }
# Scoped, one-time capability derived from vault root signet capability \ --domain "amazon.com" \ --max-amount 150 \ --purpose "purchase" \ --one-time
# Start the verification server signet serve --transport http --port 3000 # Verify a proof against the issuing authority curl -X POST http://localhost:3000/verify \ -H "Content-Type: application/json" \ -d '{"proof": "...", "claim": {"attribute": "age_over_21"}}'
For Security Engineers
Signet's storage layer implements the BlindDB model from The Ephemeral Internet (McEntire, 2026). The core insight: in most breaches, the relationships between records are more valuable than the records themselves. A credit card number is dangerous. A credit card number linked to a name, address, email, and purchase history is an identity.
BlindDB doesn't just encrypt your data. It makes relationships between records uncomputable by anyone without the client-side master secret.
Record IDs are SHA-256(master_secret || label || index). The server stores a flat pile of opaque hashes. No user table. No foreign keys. No joins. No way to determine which records belong to the same user, which form a collection, or what any record represents.
This is the defense that matters most. Even if every other layer fails, an attacker with full database access sees a pile of unrelated blobs.
Hash-chained audit log. Every vault operation is signed and appended. Chain breaks are detectable. You know if someone modified the record store.
Hash chains provide causal ordering. You can prove a record existed before another. You can prove a sequence of operations happened in order. The server cannot reorder history.
AES-256-GCM per-record envelope encryption. Per-record DEKs derived from the master secret and record ID: enc_key = SHA-256(master_secret || record_id || "encrypt"). For data that has independent value (credit cards, SSNs), encryption is the last line.
Seed data is cryptographically indistinguishable from real data. An attacker cannot determine whether a record is genuine or injected noise. The total record count reveals nothing about the number of users or records per user.
Three users store emails, addresses, payment cards, medical records, video watch histories, and credential statuses. Here is the server's complete database:
SELECT * FROM records;
# record_id data
--- ----------------------------------- -------------------------
1 0a3f8b2c...e771c921 <encrypted 44 bytes>
2 1b8e4d7a...f9823d44 <encrypted 68 bytes>
3 2c7a91e5...a4f08812 <encrypted 52 bytes>
4 3d1f6c88...b8e22a97 <encrypted 40 bytes>
5 4e2a7d99...c7f33b06 <encrypted 88 bytes>
6 5f3b8eaa...d6044c15 <encrypted 36 bytes>
7 6a4c9fbb...e5155d24 <encrypted 48 bytes>
8 7b5da0cc...f4266e33 <encrypted 72 bytes>
9 8c6eb1dd...03377f42 <encrypted 44 bytes>
10 9d7fc2ee...12488051 <encrypted 56 bytes>
11 ae80d3ff...21599160 <encrypted 40 bytes>
12 bf91e400...306aa27f <encrypted 64 bytes>
13 c0a2f511...4f7bb38e <encrypted 48 bytes>
Run it yourself: cargo test --package signet-vault --test show_db -- --nocapture
Ed25519 root keypair (generated once from 256 bits entropy. No mnemonic. No recovery.) +-- VaultSealingKey (HKDF: root + "vault-seal") | +-- Per-record DEKs (HKDF: VSK + record_id) +-- CompartmentKeys (HKDF: root + compartment_id) | +-- Tier 3 DEKs (NOT derivable from VSK -- structural isolation) +-- DeviceKeys (HKDF: root + device_id)
There is no recovery mechanism. Lose the root key and everything is crypto-erased. That's the point — if it's recoverable, it's stealable. You know your own birthday. Your bank will re-issue credentials. GDPR compliance by mathematics, not by policy.
Tier 3 compartment keys are structurally isolated from the vault sealing key. An agent session that holds the VSK cannot derive compartment keys. This is enforced by the key hierarchy, not by access control.
| Tier | Key Derivation | Loss Consequence | Use Case |
|---|---|---|---|
| 1 | HKDF(root + "tier1" + label) | Re-derive from root | Authority-attested proofs, preferences |
| 2 | HKDF(root + "tier2" + label) | Re-derive from root | Agent reasoning context, session data |
| 3 | HKDF(root + compartment_id) | Gone with root | Payment credentials, identity docs, medical |
Lose the root key and all tiers are crypto-erased simultaneously. You know your own birthday — the DMV will re-issue the credential. Your credit card is in your wallet. Recovery is the enemy: if it's recoverable, it's stealable.
All CAS operations and authentication checks use subtle::ConstantTimeEq. No timing side-channels on secret-dependent branches.
rand::rngs::OsRng for every cryptographic operation. No userspace PRNGs in security paths. Verified across all 9 crates.
All secret key material wrapped in Zeroizing<>. Secrets are zeroed from memory on drop. No residual key material.
The BlindStorageWrapper is a transparent layer that blinds record IDs (SHA-256) and encrypts data (AES-256-GCM) before any backend call. The server never sees labels, plaintext, or semantic content.
Hash-chained append-only log. Each entry includes the hash of the previous entry. Tampering breaks the chain. Independently verifiable.
Tier 3 keys are not derivable from the vault sealing key. This is a property of the key hierarchy, not a policy check. A compromised agent session cannot access Tier 3 data.
unwrap() in library code. Clippy clean.Read the code. Run the tests. Break the assumptions.