Use drs-core directly (Rust)

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 agent, tool server, or CLI and want the primitives in-process (no HTTP hop), or
  • compiling DRS logic to WebAssembly for the browser / React Native, 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 same crate targets WASM via wasm-pack:

cd drs-core
wasm-pack build --target web

The wasm module exposes the verification and canonicalisation entry points to JavaScript. The TypeScript SDK ships an optional WASM loader (initWasm, getWasmModule) that consumes this artifact when you want the Rust core's speed in a JS runtime.

When to reach for drs-verify instead

You needUse
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, offline verify, WASMdrs-core (this page)