Use drs-core directly (Rust)

Status: non-normative reference implementation. drs-core is feature-frozen and receives bug fixes only. The normative DRS verifier is drs-verify (Go) — all production verification goes through its HTTP /verify endpoint. drs-core implements Blocks A–E only: it performs no revocation checking, no replay protection, and no tool_server binding, 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/Odid: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 needUse
Production verification of any kinddrs-verify (normative)
did:web resolutiondrs-verify (network I/O)
Revocation / status-list checksdrs-verify (network I/O)
Replay protection (nonce store)drs-verify
A drop-in HTTP verification servicedrs-verify
In-process primitives, conformance toolingdrs-core (this page)