npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

verifiable-intent-js

v0.1.0

Published

Independent TypeScript implementation of the Verifiable Intent spec: a layered SD-JWT delegation chain (Issuer → User → Agent) for cryptographic agent authorization in commerce. Community project; not affiliated with or endorsed by agent-intent.

Readme

verifiable-intent-js

verifiable-intent-js is a TypeScript implementation of the Verifiable Intent credential format: a layered SD-JWT delegation chain (Issuer → User → Agent) that produces cryptographic proof an AI agent's commercial actions stayed within the scope a human explicitly delegated.

Independent & unofficial (pre-1.0). This is a community project by @miguelvelasquezdev. It is not an official agent-intent release and is not affiliated with or endorsed by that project. It tracks the Verifiable Intent specification and aims for compatibility with the upstream reference, but any errors are our own. APIs may change; see NOTICE for attribution.

It has zero runtime dependencies and uses only Web-standard APIs — WebCrypto (crypto.subtle) for ES256 and SHA-256, crypto.getRandomValues for salts, TextEncoder/TextDecoder and a hand-rolled base64url codec for bytes — so it runs on Node.js >= 20 and in modern browsers unchanged. The Verifiable-Intent-specific layering, delegate_payload mechanism, and split-L3 logic stay compatible with the upstream reference implementation.

Status: crypto, models, issuance, and verification are implemented and validated byte-for-byte (issuance) and verdict-for-verdict (verification) against conformance vectors generated by the upstream reference (test-vectors/vectors.json). The vitest suite (276 tests) replays every shared golden vector — asserting byte-for-byte the 11 error strings those vectors pin — plus TS-specific parity, fail-closed hardening, an isomorphic (Buffer-free) runtime test, and end-to-end issue→verify round-trip tests. Diagnostics outside those 11 are TS-native and intentionally not byte-identical to Python (see src/verification/chain.ts). CI runs the whole suite on Node 20 and 22, and both runtimes produce identical verdicts.

Install & test

npm install
npm test          # vitest, validates against test-vectors/
npm run build     # tsdown -> dist/ (dual ESM + CJS + .d.ts)
npm run typecheck # tsc --noEmit
npm run lint      # eslint
npm run test:isomorphic  # builds, then runs dist/ with Node's Buffer global deleted

Quick start (immediate mode)

Immediate mode: the user authorizes one specific checkout — no agent delegation, no L3. Every symbol below is exported from the package root.

import {
  CheckoutMandate,
  createLayer1,
  createLayer2Immediate,
  generateEs256Key,
  hashAscii,
  IssuerCredential,
  MandateMode,
  PaymentMandate,
  UserMandate,
  verifyChain,
} from 'verifiable-intent-js';

const now = Math.floor(Date.now() / 1000);
const issuer = await generateEs256Key(); // the card network / issuer
const user = await generateEs256Key();   // the user's wallet key

// L1: issuer credential binding the user's public key (cnf.jwk).
const l1 = await createLayer1(
  new IssuerCredential({
    iss: 'https://www.mastercard.com',
    sub: 'user-bob-001',
    iat: now,
    exp: now + 86400,
    aud: 'https://wallet.example.com',
    cnfJwk: user.publicKey,
  }),
  issuer.privateKey,
);

// L2: the user's mandate for one specific, finalized checkout.
const nonce = crypto.randomUUID(); // Web-standard global (Node >= 20 / browsers)
const mandate = new UserMandate({
  nonce,
  aud: 'https://agent.example.com',
  iat: now,
  iss: 'https://wallet.example.com',
  exp: now + 900,
  mode: MandateMode.IMMEDIATE,
  // Hashing is async (WebCrypto): await hashAscii / hashDisclosure / hashBytes.
  sdHash: await hashAscii(l1.serialize()),
  promptSummary: 'Purchase Babolat Pure Aero racket',
  // `checkoutJwt` is a merchant-signed checkout JWT; checkout_hash and
  // transaction_id are auto-computed from it by createLayer2Immediate.
  checkoutMandate: new CheckoutMandate({ vct: 'mandate.checkout.1', checkoutJwt }),
  paymentMandate: new PaymentMandate({
    vct: 'mandate.payment.1',
    paymentInstrument: { type: 'mastercard.srcDigitalCard', id: 'f199c3dd-7106-478b-9b5f-7af9ca725170' },
    payee: { id: 'merchant-uuid-1', name: 'Tennis Warehouse', website: 'https://tennis-warehouse.com' },
    currency: 'USD',
    amount: 27999, // minor units (cents)
  }),
});
const { sdJwt: l2 } = await createLayer2Immediate(mandate, user.privateKey);

// Verify the chain. Fails closed: `valid` is false with populated `errors`
// on any problem; verification never throws on malformed credentials.
const result = await verifyChain(l1, l2, {
  issuerPublicJwk: issuer.publicKey,
  l1Serialized: l1.serialize(),
  expectedL2Aud: 'https://agent.example.com',
  expectedL2Nonce: nonce,
});
if (!result.valid) {
  console.error(result.errors);
}

For autonomous mode (open mandates + agent delegation + split L3a/L3b), see test/roundtrip.test.ts for a full end-to-end chain.

What verifyChain checks

Derived from the implementation (src/verification/chain.ts); every failure is fail-closed (valid: false + a populated errors list):

  • Signatures — ES256 at every layer, each verified against the previous layer's cnf key: L1 against issuerPublicJwk, L2 against the L1 cnf.jwk (user key), L3 against the agent key from the L2 mandates' cnf.jwk — never a key named in the L3 header.
  • Headersalg pinned to ES256 and typ checked per layer and mode.
  • _sd_alg — must be sha-256 when present, at all layers.
  • Duplicate _sd digests — rejected per RFC 9901 §7.1 at all layers. This is the only §7.1 processing rule enforced today; see Standards scope for the ones that are not.
  • Canonical base64url — every segment and disclosure must be unpadded RFC 4648 §5 with no out-of-alphabet characters, so a decoder cannot be talked into accepting two spellings of the same bytes.
  • Time claimsexp / iat validated against a configurable clockSkewSeconds (default 300s); malformed time claims fail closed.
  • L1↔L2 binding — L2 sd_hash must match the presented L1 serialized form; L1 vct is checked against the expected value.
  • L2 aud / nonce — matched against caller-provided expected values.
  • Mandate pairing — L2 delegate_payload disclosures are grouped into checkout/payment pairs with orphan, duplicate, and smuggling (duplicate disclosure reference) detection.
  • card_id cross-check — L1 card_id must match each payment mandate's payment_instrument.id.
  • Per-mode mandate rules — immediate mode requires final values and no cnf; autonomous (open) mandates must carry their pairing constraints.
  • Agent-key consistency — the delegation cnf.jwk (and kid) must be identical across all mandate pairs.
  • L3 binding — L3 sd_hash must match the presented L2 serialized form, and the L2 presentation must include the L3's own mandate-pair disclosure (L3-to-mandate-pair identity binding).
  • L3 restrictions — no cnf claim, lifetime ≤ 1 hour, header kid required and matched when the L2 cnf.jwk names one.
  • L3a↔L3b cross-reference — the payment and checkout fulfillments must reference each other's transaction.
  • L3 structure — required mandate fields (vct, transaction ids, payee, amount, payment instrument) and L3↔L2 payment-instrument consistency.

Per-transaction constraint evaluation (e.g. is this fulfillment within the mandate's allowlists and amount range?) is a separate exported function, checkConstraints, run by the party evaluating a specific transaction — see the next section.

Constraints

Autonomous-mode mandates carry constraints. The stateless ones are enforced by the verifier (checkConstraints); the stateful ones are assigned by the spec to the payment network and are only surfaced by verifyChain (see below).

| Constraint | Key fields | Enforced by | | --- | --- | --- | | mandate.checkout.allowed_merchants | allowed | verifier (checkConstraints) | | mandate.checkout.line_items | items, match_mode | verifier (checkConstraints) | | mandate.payment.allowed_payees | allowed | verifier (checkConstraints) | | mandate.payment.amount_range | currency, min, max | verifier (checkConstraints) | | mandate.payment.budget | currency, max, min | payment network (stateful) | | mandate.payment.recurrence | frequency, start_date, end_date, number | payment network (stateful) | | mandate.payment.agent_recurrence | frequency, start_date, end_date, max_occurrences | payment network (stateful) | | mandate.payment.reference | conditional_transaction_id | auto-injected by issuance; binding checked by the verifier |

Network-enforced constraints

Budget and recurrence constraints are stateful — enforcing them requires knowing how much has already been spent or how many installments have run — so the spec assigns them to the payment network, and this stateless verifier parses but never evaluates them. That means valid: true tells you every stateless check passed; it does not mean a budget or recurrence limit is satisfied.

verifyChain surfaces these constraints on the result so the caller (typically the payment network) knows what it still must enforce:

const result = await verifyChain(l1, l2, opts);
for (const ne of result.networkEnforced) {
  // ne.pairIndex  — which mandate pair it came from
  // ne.type       — e.g. 'mandate.payment.budget'
  // ne.constraint — the raw constraint object from the payment mandate
  enforceAtTheNetwork(ne); // your stateful enforcement here
}

networkEnforced is always present — an empty array when no payment mandate carries budget/recurrence constraints.

VerifyChainOptions highlights

  • issuerPublicJwk — the issuer's public key; required unless skipIssuerVerification is explicitly set (test-only bypass).
  • currentTime — Unix seconds; inject to verify fixed-timestamp credentials deterministically (defaults to the wall clock).
  • clockSkewSeconds — tolerance for exp/iat checks (default 300).
  • expectedL2Aud / expectedL2Nonce — bind the L2 to the intended audience and the nonce you issued for this flow.
  • expectedL3{Payment,Checkout}{Aud,Nonce} — the same for each L3.
  • splitL3s — per-pair L3a/L3b credentials plus the role-specific L2 presentations (l2PaymentSerialized / l2CheckoutSerialized) they bind to.

Layout

src/
  crypto/        ES256 signing, base64url, disclosures, SD-JWT, KB-SD-JWT
  models/        issuer credential, user mandate, agent mandate, constraints
  issuance/      createLayer1 / layer2 (immediate + autonomous) / layer3 (split)
  verification/  verifyChain (L1→L2→split-L3), integrity bindings, constraint checker

The build (tsdown) emits dual ESM + CJS with bundled type declarations and no externals — the package has zero runtime dependencies. verifyChain accepts a currentTime option so fixed-timestamp credentials can be verified deterministically.

The conformance vectors are regenerated from the Python reference with python/scripts/generate_vectors.py. License: Apache-2.0.

Standards scope

This package implements the Verifiable Intent profile of SD-JWT, matching the Python reference. It is not a general-purpose SD-JWT library — if you need one, use @sd-jwt/core. VI is hand-rolled here because the format diverges from plain RFC 9901 in ways a generic library cannot express, and because the golden vectors demand byte-identical output with Python's json.dumps(separators=(",", ":")):

  • Holder binding lives in the SD-JWT payload as sd_hash, not in a trailing KB-JWT, and the layer typ values are sd+jwt / kb-sd-jwt / kb-sd-jwt+kb rather than kb+jwt.
  • delegate_payload carries {"...": "<digest>"} references at the top level of the payload; RFC 9901 uses that form only for array elements.

Consequently, several RFC 9901 §7 verifier rules are not enforced. They are tracked as spec work, and both implementations behave the same way today:

| RFC 9901 rule | Status | | --- | --- | | §7.1 duplicate digest in _sd MUST be rejected | enforced | | §7.1 unreferenced disclosure MUST be rejected | not enforced (extra disclosures are ignored) | | §7.1 claim name _sd / ... MUST be rejected | not enforced | | §7.1 claim name colliding with an existing one MUST be rejected | not enforced (last presented wins) | | §7.3 KB-JWT verifier rules | not applicable — VI has no trailing KB-JWT. verifyKbJwt is an interoperability helper that checks only the signature; it does not check typ, sd_hash, aud, or nonce, and is not used by verifyChain. |

_sd_alg is validated to be sha-256 when present, but is not required to be present. Autonomous mode does not recompute checkout_hash from checkout_jwt (only the L3a↔L3b cross-reference equality is checked); see the Verifiable Intent specification §6.2.