Web4: DID Identity & x402 Payments
zmodu.web4 provides self-certifying Ed25519 did:key decentralized identifiers, Verifiable Credentials, and HTTP 402 (x402) payment verification middleware.
Runnable example: examples/web4.
1. DID Identity (zmodu.web4.DidKey)
Generate keys, sign challenges, and verify did:key signatures without external blockchain calls:
const web4 = zmodu.web4;
var key = try web4.DidKey.generate(allocator, io);
defer allocator.free(key.did);
const sig = try key.sign(allocator, "challenge-123");
defer allocator.free(sig);
const ok = try key.verify("challenge-123", sig); // true
// Resolve any public `did:key:z6Mk...` identifier for verification
var resolved = try web4.resolve(allocator, "did:key:z6Mk...");
defer allocator.free(resolved.did);
Use web4.issueCredential and web4.verifyCredential to issue and verify VerifiableCredential payloads.
2. x402 Payment Protocol (zmodu.web4.x402)
The x402 middleware gates endpoints behind HTTP 402 Payment Required:
- Requests without payment proof receive
402 Payment Requiredwith anInvoiceJSON body. - Clients retry with
x402-tx-hashandx402-invoice-idheaders. PaymentVerifiervalidates the proof. Verification is fail-closed by default (verifyPaymentRejectrejects all proofs unless a real verifier or developmentverifyPaymentAllowAllis configured).
3. HTTP Middleware (zmodu.web4.middleware)
// 1. DID authentication middleware (must run BEFORE x402Middleware so payer attributes are set)
var challenges = web4.challenge.ChallengeStore.init(allocator, io);
defer challenges.deinit();
var did_cfg = web4.middleware.DidAuthConfig{
.path_prefix = "/api/identity",
.challenge_store = &challenges, // Verifies signature first, then consumes one-time challenge
};
try server.addMiddleware(web4.middleware.didAuthMiddleware(&did_cfg));
// 2. Persistent invoice store + x402 payment gate
var store = web4.x402_store.X402Store.init(allocator, &backend);
try store.migrate();
var x402_cfg = web4.middleware.X402Config{
.verifier = my_verifier,
.store = &store,
.path_prefix = "/api/paid",
.on_invoice = my_invoice_builder,
};
try server.addMiddleware(web4.middleware.x402Middleware(&x402_cfg));
4. Production Security Guarantees
X402Storeis a ledger, not a verifier:X402Storepersists issued invoices and ensures eachinvoice_idis redeemed at most once (already_used→ HTTP410,not_found/expired→ HTTP403). Cryptographic or on-chain proof validation always runs throughX402Config.verifier.- Payer binding: When an invoice is created,
x402Middlewarerecords the authenticated payer fromctx.attributes(didoruser_id, never from unverified client headers). On redemption, a mismatched payer returnspayer_mismatch(403) without consuming the invoice. Mountx402Middlewareafter your identity middleware (DID or JWT). - One-time DID challenges (
ChallengeStore):ChallengeStoremints short-lived CSPRNG challenges (std.Io.randomSecure).didAuthMiddlewareverifies the Ed25519 signature before consuming the challenge so an attacker cannot burn a user’s challenge with an invalid signature. - DID-to-JWT bridge: Setting
DidAuthConfig.jwt_issuer = &app_secissues a framework JWT in thex-did-tokenresponse header upon successful DID authentication.