Use drs-core directly (Rust)
Status: non-normative reference implementation.
drs-coreis feature-frozen and receives bug fixes only. The normative DRS verifier isdrs-verify(Go) — all production verification goes through its HTTP/verifyendpoint.drs-coreimplements Blocks A–E only: it performs no revocation checking, no replay protection, and notool_serverbinding, so a chain it accepts may still be revoked or replayed.
Most builders never touch drs-core — you issue receipts with the TypeScript
SDK and verify them with the drs-verify service. Use this guide only if you
are:
- building a Rust tool that needs the DRS primitives (canonicalisation, chain hashing, issuance) in-process, or
- writing conformance tooling that must match the canonical encoder byte-for-byte.
drs-core is the same Rust crate that compiles to the native library and the
WASM artifact. It contains the crypto primitives, JCS canonicalisation, chain
hashing, the capability index, and an offline chain verifier. It performs
no network I/O — did:web resolution and revocation list fetching live in
drs-verify (Go), not here.
Install
[dependencies]
drs-core = "0.1"
cargo add drs-core
Generate a keypair and a DID
#![allow(unused)] fn main() { use drs_core::crypto::ed25519::generate_keypair; use drs_core::did::key::encode_did_key; let (signing_key, verifying_key) = generate_keypair()?; // did:key string for the public half — this is the agent's identity. let did = encode_did_key(&verifying_key.to_bytes()); println!("{did}"); // did:key:z6Mk... Ok::<(), drs_core::DrsError>(()) }
Sign and verify bytes
verify_strict rejects malleable signatures (S ≥ L) — the same strict
Ed25519 check the production verifier enforces.
#![allow(unused)] fn main() { use drs_core::crypto::ed25519::{generate_keypair, sign, verify_strict}; let (signing_key, verifying_key) = generate_keypair()?; let message = b"delegation payload bytes"; let signature: [u8; 64] = sign(&signing_key, message); verify_strict(&verifying_key, message, &signature)?; // Ok(()) on success Ok::<(), drs_core::DrsError>(()) }
Resolve a did:key back to public-key bytes
#![allow(unused)] fn main() { use drs_core::did::key::resolve_did_key; let public_key: [u8; 32] = resolve_did_key("did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2")?; Ok::<(), drs_core::DrsError>(()) }
did:web is intentionally not resolved here — it requires network I/O and
is handled by drs-verify.
Canonicalise JSON (RFC 8785 JCS)
This is the byte-exact encoder. Use it when you need the signing preimage to match the SDK and verifier exactly.
#![allow(unused)] fn main() { use drs_core::jcs::canonicalise::jcs_canonical_bytes; use serde_json::json; let bytes = jcs_canonical_bytes(&json!({ "b": 2, "a": 1 }))?; assert_eq!(bytes, br#"{"a":1,"b":2}"#); // keys sorted by code point, no whitespace Ok::<(), drs_core::DrsError>(()) }
Compute a chain hash
The hash that links one receipt to the next — sha256:<hex> over the exact JWT
string bytes.
#![allow(unused)] fn main() { use drs_core::chain::hash::compute_chain_hash; let hash = compute_chain_hash("eyJhbGciOiJFZERTQS..."); assert!(hash.starts_with("sha256:")); }
Build a signed receipt JWT
#![allow(unused)] fn main() { use drs_core::jwt::encode::build_jwt; use drs_core::crypto::ed25519::generate_keypair; use serde_json::json; let (signing_key, _) = generate_keypair()?; let payload = json!({ "iss": "did:key:z6MkOperator", "sub": "did:key:z6MkOperator", "aud": "did:key:z6MkAgent", "drs_v": "4.0", "drs_type": "delegation-receipt", "cmd": "/mcp/tools/call", "jti": "dr:did:key:z6MkOperator-1", "nbf": 0, "iat": 0 }); let jwt = build_jwt(&payload, &signing_key)?; // header + payload JCS-encoded, then signed Ok::<(), drs_core::DrsError>(()) }
Verify a bundle offline
verify_chain runs the structural, cryptographic, policy, and temporal checks
in-process. Because it does no I/O, it does not perform did:web
resolution or revocation lookups — for those, send the bundle to a running
drs-verify instead.
#![allow(unused)] fn main() { use drs_core::{verify_chain, ChainBundle}; let bundle: ChainBundle = serde_json::from_str(bundle_json)?; let result = verify_chain(&bundle); if result.valid { let ctx = result.context.expect("valid result carries context"); println!("root principal: {}", ctx.root_principal); println!("chain depth: {}", ctx.chain_depth); } else { let err = result.error.expect("invalid result carries error"); eprintln!("{}: {}", err.code, err.message); } Ok::<(), Box<dyn std::error::Error>>(()) }
VerificationResult mirrors the JSON the Go service returns: valid,
context (root_principal, chain_depth, …), and error
(code, message, suggestion). The code values are the same ones listed
in Error Codes.
Check a capability index
The capability index answers "does this grant cover (resource, tool)?" with exact-match precedence over prefix wildcards.
#![allow(unused)] fn main() { use drs_core::capability::index::CapabilityIndex; let index = CapabilityIndex::build( &["mcp://tools/*".to_string()], &["web_search".to_string()], ); assert!(index.covers("mcp://tools/search", "web_search")); assert!(!index.covers("mcp://other/x", "web_search")); }
Build for WebAssembly
The crate still compiles to wasm32-unknown-unknown (a JS host is required
for the clock) and its WASM tests run in CI:
cd drs-core
wasm-pack build --target web
No standalone WASM artifact is published, and as of SDK 0.2.0 the TypeScript SDK no longer exports a loader for one. Treat the WASM target as a build capability of the frozen reference crate, not a production verification path.
When to reach for drs-verify instead
| You need | Use |
|---|---|
| Production verification of any kind | drs-verify (normative) |
did:web resolution | drs-verify (network I/O) |
| Revocation / status-list checks | drs-verify (network I/O) |
| Replay protection (nonce store) | drs-verify |
| A drop-in HTTP verification service | drs-verify |
| In-process primitives, conformance tooling | drs-core (this page) |