A .NET 10 implementation of the W3C ZCAP-LD authorization capability model.
ZCAP-LD uses object capabilities: if you hold a valid signed capability, you have authority to invoke the permitted action.
This library provides:
- Capability creation and delegation
- Delegation-chain verification
- Invocation signing and verification
- Caveat processing (expiration, usage count, and ValidWhileTrue remote revocation)
- Invocation replay protection with pluggable nonce stores
- Revocation persistence with pluggable storage backends
- ValidWhileTrue caveat support with HTTP-based remote revocation checking
- Pluggable crypto suites (Ed25519 and P-256 included, additional curves extensible)
- Dynamic JSON-LD context URLs per crypto suite
- Multibase signature encoding
dotnet add package ZcapLd.Core
dotnet add package ZcapLd.AspNetCore # Optional endpoint adapterusing ZcapLd.Core.Cryptography;
using ZcapLd.Core.Models;
using ZcapLd.Core.Services;
// Wire up services — in production, replace InMemoryDidProvider with your
// IDidSigner (HSM/Key Vault) and IDidResolver implementations.
var didProvider = new InMemoryDidProvider(); // test helper: IDidSigner + IDidResolver
var suiteProvider = new CryptoSuiteProvider();
suiteProvider.Register(new Ed25519CryptoSuite());
var signingService = new SigningService(didProvider, didProvider, suiteProvider);
var capabilityService = new CapabilityService(signingService);
var caveatProcessor = new CaveatProcessor();
var revocationService = new RevocationService(new InMemoryRevocationStore());
var nonceStore = new InMemoryNonceStore();
var verificationService = new VerificationService(
didProvider, caveatProcessor, suiteProvider, revocationService, nonceStore);
var rootDid = "did:key:z6MkRoot";
var leafDid = "did:key:z6MkLeaf";
didProvider.GenerateAndRegisterKeyPair(rootDid);
didProvider.GenerateAndRegisterKeyPair(leafDid);
// Root capability (root metadata only)
var root = await capabilityService.CreateRootCapabilityAsync(
rootDid,
"https://api.example.com/resources",
new[] { "read", "write" });
// Delegated capability (restrictions live here)
var delegated = await capabilityService.DelegateCapabilityAsync(
root,
leafDid,
new[] { "read" },
DateTime.UtcNow.AddDays(7),
new Caveat[]
{
new ExpirationCaveat { Expires = DateTime.UtcNow.AddDays(3) }
});
var invocation = new Invocation
{
Capability = delegated.Id,
CapabilityAction = "read",
InvocationTarget = "https://api.example.com/resources/123"
};
invocation.Proof = await signingService.SignInvocationAsync(invocation, leafDid);
var isValid = await verificationService.VerifyInvocationAsync(invocation, delegated);The core package is storage-agnostic for revocation.
IRevocationStore: plug in your own backend (database, contract gateway, oracle bridge, cache)IRevocationService: orchestration for persistence and expiry-aware lookupsVerificationService: checks revocation status during capability/invocation verification
Optional ASP.NET endpoint rails are provided by ZcapLd.AspNetCore:
using ZcapLd.AspNetCore.DependencyInjection;
using ZcapLd.AspNetCore.Endpoints;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddZcapRevocationSupport(); // Or AddZcapRevocationSupport<MyStore>()
var app = builder.Build();
app.MapZcapRevocationEndpoints(); // /zcaps/revocations/{*capabilityId}
app.Run();Use ZcapLd.AspNetCore when your runtime is ASP.NET and you want ready-made minimal API rails:
- Register services via
AddZcapRevocationSupport(...) - Map routes via
MapZcapRevocationEndpoints(...) - Override route prefix when needed (for example
/wallet/revocations)
If you do not want ASP.NET endpoints, call IRevocationService from your own transport layer:
- gRPC handler
- message consumer
- admin CLI
- worker-triggered orchestration
Configure storage via IRevocationStore:
InMemoryRevocationStorefor local development/testing- database-backed custom stores for centralized persistence
- smart-contract/oracle-backed stores for decentralized persistence
- hybrid cache + durable store composites for high-throughput workloads
Full developer guide: docs/REVOCATION-INTEGRATION.md
The ValidWhileTrue caveat (per the W3C ZCAP-LD spec) enables remote revocation by embedding a URI in the capability. At verification time, the verifier checks the URI to confirm the capability is still valid. The delegator/controller hosts the endpoint — not the verifier.
Controller side (hosts the revocation status endpoint):
builder.Services.AddZcapRevocationSupport<MyStore>();
app.MapZcapRevocationEndpoints();
// When delegating, attach the caveat pointing to your endpoint:
var delegated = await capabilityService.DelegateCapabilityAsync(
root, partnerDid, new[] { "read" },
DateTime.UtcNow.AddDays(30),
new Caveat[]
{
new ValidWhileTrueCaveat
{
Uri = $"https://my-service/zcaps/revocations/{Uri.EscapeDataString(root.Id)}"
}
});Verifier side (checks the URI during invocation verification):
builder.Services.AddZcapValidWhileTrueSupport(); // registers HttpValidWhileTrueHandler
builder.Services.AddZcapServices();
// HttpClient can be configured for timeouts/retry:
builder.Services.AddHttpClient("ZcapValidWhileTrue", client =>
{
client.Timeout = TimeSpan.FromSeconds(5);
});When a ValidWhileTrue caveat is encountered during verification, the handler GETs the URI and checks the isRevoked field in the response. Without a handler configured, the caveat fails closed (denies access).
- Root capability:
- Contains
@context,id,controller,invocationTarget - Does not include
proof,expires,parentCapability
- Contains
- Delegated capability:
- Contains
parentCapability,expires, delegationproof - Supports attenuation (
allowedAction, target narrowing, caveats)
- Contains
src/ZcapLd.Core: library codesrc/ZcapLd.AspNetCore: optional ASP.NET endpoint adapter packagetests/ZcapLd.Core.Tests: unit, integration, and compliance testsexamples/ZcapLd.Examples: console examples (8 scenarios including ValidWhileTrue)examples/ZcapLd.RevocationEndpointsDemo: ASP.NET revocation endpoints demo wired to SQLite (with ValidWhileTrue support)docs: implementation/security notes
- Architecture:
architecture.md - Revocation Integration:
docs/REVOCATION-INTEGRATION.md - Contributing:
CONTRIBUTING.md - NuGet Release Runbook:
docs/NUGET-RELEASE.md - Monorepo Pipelines:
docs/MONOREPO-PIPELINES.md - MIT License:
LICENSE
dotnet restore
dotnet build ZcapLd.sln
dotnet test ZcapLd.sln
dotnet pack src/ZcapLd.Core/ZcapLd.Core.csproj -c Release
dotnet pack src/ZcapLd.AspNetCore/ZcapLd.AspNetCore.csproj -c Release- Core CI:
.github/workflows/ci-core.yml - ASP.NET Adapter CI:
.github/workflows/ci-aspnet.yml - Core publish:
.github/workflows/release-core-nuget.ymloncore-v*.*.*tags - ASP.NET adapter publish:
.github/workflows/release-aspnet-nuget.ymlonaspnet-v*.*.*tags - Shared package version source:
Directory.Build.props(ZcapLdVersion) - Secrets:
- Core:
NUGET_API_KEY_CORE(fallbackNUGET_API_KEY) - ASP.NET adapter:
NUGET_API_KEY_ASPNET(fallbackNUGET_API_KEY)
- Core:
- No default
IDidSignerships in the core package — consumers must provide their own (HSM/KMS/Key Vault). InMemoryDidProvider(in examples and tests) stores private keys in plaintext memory and is NOT for production use.- Canonicalization currently uses deterministic JSON canonicalization, not full RDF Dataset Canonicalization.
- The
ICryptoSuiteabstraction supports pluggable algorithms; Ed25519 and P-256 are registered by default.