trust-tasks-proof 0.12.1

Pluggable ProofVerifier implementations for the Trust Tasks framework. Backends are gated by Cargo features; ships with an Affinidi Data Integrity backend (EdDSA suites) by default.
Documentation

trust-tasks-proof

Pluggable ProofVerifier implementations for the Trust Tasks framework.

The framework's ProofVerifier trait is the seam where W3C Data Integrity cryptosuite implementations plug in; this crate is the umbrella that hosts those implementations behind Cargo features so a single dependency line opts you in to a specific backend without dragging in the others.

Backends

Cargo feature Module Backed by Cryptosuites
affinidi affinidi affinidi-data-integrity eddsa-rdfc-2022, eddsa-jcs-2022

✱ = enabled by default.

Why

The framework's SPEC §7.2 item 7 requires a conforming consumer to verify proof against the in-band issuer when present. trust-tasks-rs intentionally ships no cryptosuites — this crate hosts the concrete implementations.

Quickstart (affinidi backend)

use trust_tasks_proof::affinidi::Verifier;
use trust_tasks_rs::ProofVerifier;

// did:key only — offline, no I/O. Good for tests and self-issued docs.
let verifier = Verifier::for_did_key();
verifier.verify(&inbound_doc).await?;

For did:web / did:webvh / did:peer / did:jwk and the rest of the methods the Affinidi resolver cache handles, use CachedDidResolver:

use std::sync::Arc;
use affinidi_did_resolver_cache_sdk::{config::DIDCacheConfigBuilder, DIDCacheClient};
use trust_tasks_proof::affinidi::{CachedDidResolver, Verifier};

let client = DIDCacheClient::new(DIDCacheConfigBuilder::default().build()).await?;
let resolver = Arc::new(CachedDidResolver::new(Arc::new(client)));
let verifier = Verifier::with_resolver(resolver);

The default builder runs in local mode (no network) — sufficient for did:key, did:peer, and did:jwk. Add .with_network_mode(...) to point at a running resolver cache server for did:web, did:webvh, and the rest. See affinidi-did-resolver-cache-sdk for the full configuration surface.

The adapter currently extracts public keys from Multikey-typed verification methods (publicKeyMultibase); JWK-bearing methods surface a clean Resolver error so callers can stack a custom resolver in front.

Signing (affinidi backend)

Producers sign with sign_trust_task — the sign-side counterpart to the stock Verifier. It signs the document with the proof member removed (the exact canonicalisation contract the verify side applies), replaces any existing proof, and pre-flights the SPEC §4.7/§4.8 issuer binding (issuer must equal the DID of the signer's verificationMethod), so its output verifies with the stock Verifier by construction. Defaults: eddsa-jcs-2022, proofPurpose: assertionMethod.

use trust_tasks_proof::affinidi::{sign_trust_task, SignOptions};

// `doc` is a serde_json::Value with `issuer` set to the signer's DID;
// `secret` is an affinidi-secrets-resolver Secret (or any upstream Signer).
let signed = sign_trust_task(&doc, &secret, SignOptions::new()).await?;

Signing a typed document — ProofExt

sign_trust_task works on serde_json::Value, which is the right shape for the primitive and the wrong one for a producer holding a TrustTask<P>. ProofExt is the typed wrapper: import the trait and both halves of the round-trip become methods on the document. It calls sign_trust_task internally, so the canonicalisation, the defaults and the issuer pre-flight are identical — a document signed either way is byte-for-byte the same.

use trust_tasks_proof::{affinidi::{SignOptions, Verifier}, ProofExt};
use trust_tasks_rs::{specs::acl::grant::v0_1 as grant, TrustTask};

let mut req = TrustTask::for_payload(new_id(), grant::Payload { /**/ });
req.issuer = Some(my_did.clone());          // set every member first …
req.recipient = Some(server_did.clone());
req.sign(&secret, SignOptions::new()).await?;   // … then sign.

// Consumer side, same trait:
req.verify(&Verifier::for_did_key()).await?;

Sign last. The proof covers the document as it stands when sign is called; mutating any member afterwards invalidates the signature. Re-signing is always safe — the existing proof is discarded and a fresh one minted over the current content.

Opting out of the default backend

[dependencies]
trust-tasks-proof = { git = "...", default-features = false }
# add other backends here as they're introduced

With no features enabled the crate compiles to an empty surface — useful while you wait for a non-Affinidi backend to land, or if you're consuming the trait from trust-tasks-rs directly and only want the umbrella as a future home for adapters.

Error mapping (affinidi backend)

DataIntegrityError VerificationError SPEC §8.3 standard code
UnsupportedCryptoSuite UnsupportedCryptosuite proof_invalid
KeyTypeMismatch IssuerMismatch (descriptive) proof_invalid
InvalidSignature SignatureInvalid proof_invalid
InvalidPublicKey MalformedProof proof_invalid
MalformedProof MalformedProof proof_invalid
Canonicalization Other proof_invalid
other Other proof_invalid

A consumer pipeline that catches VerificationError converts it to a trust-task-error/0.1 document with code = proof_invalid (or code = proof_required if the proof was missing in the first place — that case is raised by the framework, not by this crate).

MSRV

1.94, matching affinidi-data-integrity 0.6.

Status

0.1.0, tracking SPEC.md 0.1. Round-trip-tested against affinidi-data-integrity's sign (see tests/round_trip.rs).