Skip to content

feat: add TypeScript port (@verifiable-intent/core) - #31

Open
miguelvelasquezdev wants to merge 7 commits into
agent-intent:mainfrom
miguelvelasquezdev:feat/monorepo-typescript-port
Open

feat: add TypeScript port (@verifiable-intent/core)#31
miguelvelasquezdev wants to merge 7 commits into
agent-intent:mainfrom
miguelvelasquezdev:feat/monorepo-typescript-port

Conversation

@miguelvelasquezdev

Copy link
Copy Markdown

Summary

Byte-compatible TypeScript implementation of the Verifiable Intent
credential format (@verifiable-intent/core), ported from the Python
reference, plus the monorepo restructure and shared conformance vectors
that support it.

Related: #24, #30 (prior community interest in a TypeScript implementation).

Motivation

A TypeScript SDK makes the format usable from Node.js services, browsers,
and the JS agent tooling ecosystem, and a second implementation validates
the spec's portability. Issuance and verification are byte-compatible with
the Python reference, so credentials interoperate across implementations.

What's included

  • Python reference moved to python/ (pure renames) and shared golden
    vectors in test-vectors/ so ports replay issuance byte-for-byte.
  • Fail-closed fix: reject duplicate _sd digests (RFC 9901 §7.1), mirrored
    in both implementations.
  • The SDK: issuance (L1/L2/split-L3), selective disclosure, full chain
    verification with aud/nonce replay protection, and constraint checking —
    with byte-level parity (vectors, Python json.dumps number semantics,
    exact error strings).
  • verifyChain surfaces network-enforced constraints (budget/recurrence)
    explicitly, since the spec assigns them to the payment network.
  • Isomorphic crypto (WebCrypto): runs on Node >= 20 and browsers, zero
    runtime dependencies.
  • CI gains a typescript job; docs updated (AP2 0.2.0 mapping, READMEs).

Test plan

  • Python: ruff clean, 326 tests pass.
  • TypeScript: lint/typecheck/build/publint clean; 183 tests including
    byte-for-byte vector replay; isomorphic check runs issue→verify through
    both ESM and CJS bundles with Buffer deleted.
  • Consumer smoke test: packed tarball installed in a fresh project, full
    immediate + autonomous flows plus tamper/expiry/audience negatives.

Notes

  • No spec/ changes. All commits carry DCO Signed-off-by.

Relocate the Python package (src, tests, examples, conftest,
pyproject) from the repo root into python/ to make room for sibling
language implementations in a polyglot monorepo layout. All moves are
pure renames with no content changes.

CI lint/test jobs now run with working-directory: python, a
package-level README documents the Python implementation's new home,
and CONTRIBUTING.md's setup instructions point at the new location.

Signed-off-by: Miguel Velasquez <veruiz.miguel@gmail.com>
verify_chain now fails closed when the same disclosure digest appears
more than once in an _sd array, at every layer (L1, L2, L3a/L3b).
RFC 9901 section 7.1 forbids repeated digests; a duplicate is an
ambiguity/smuggling vector that valid credentials never produce.

Signed-off-by: Miguel Velasquez <veruiz.miguel@gmail.com>
test-vectors/vectors.json snapshots one Immediate and one Autonomous
flow from the Python implementation with deterministic demo keys and
fixed timestamps, nonces, and salts. ECDSA P-256 signatures are
randomized, so the vectors record every deterministic artifact
(disclosure encodings, hashes, payload JSON, _sd arrays, sd_hash
inputs/outputs) plus the exact salts consumed, letting ports replay
issuance byte-for-byte without depending on signature bytes.

python/scripts/generate_vectors.py regenerates the file:
    uv run --no-project --with "cryptography>=42" python scripts/generate_vectors.py

Signed-off-by: Miguel Velasquez <veruiz.miguel@gmail.com>
AP2 0.2.0 (28 April 2026) introduced an SD-JWT-based Human-Not-Present
model with Open Checkout / Open Payment Mandates and cnf-based agent
key binding, natively covering much of the delegation mechanism VI was
positioned to fill. Update the version baseline, re-map L2 Autonomous
to the Open mandates (same VCTs, cnf binding, and constraints array),
clarify that the Intent Mandate is a separate upstream human-present
artifact, and flag the recurrence vocabulary mismatch (ISO 20022
frequency codes vs AP2 V2's named enum; only ON_DEMAND overlaps).

Signed-off-by: Miguel Velasquez <veruiz.miguel@gmail.com>
Byte-compatible TypeScript implementation of the Verifiable Intent
credential format, ported from the Python reference: SD-JWT issuance
(issuer/user/agent), full chain verification, constraint checking,
and selective disclosure, with fail-closed behavior on every
verification path.

Parity with the reference implementation is enforced to the byte:

- Issuance replays the shared conformance vectors in test-vectors/
  byte-for-byte (disclosure encodings, digests, payload JSON, _sd
  ordering, sd_hash inputs).
- Verification re-encodes signed segments with Python json.dumps
  number semantics (int/float distinction, float repr, arbitrary-
  precision integers), so credentials issued by the Python
  implementation verify without false failures while tampering is
  still detected.
- base64url handling, disclosure resolution order, and error strings
  match the reference exactly, keeping accept/reject decisions and
  reported errors identical across both implementations.

The test suite (180 tests) covers chain verification, fail-closed
rejection of tampered, expired, and mis-bound credentials, cnf
key-binding hardening, issue/verify round trips, and vector replay.
CI gains a typescript job (lint, typecheck, tests, build), and the
root README documents the monorepo layout.

Signed-off-by: Miguel Velasquez <veruiz.miguel@gmail.com>
…ero deps)

Replace Node-specific primitives with Web standards: crypto.subtle for
ES256/SHA-256, crypto.getRandomValues for salts, hand-rolled base64url
over Uint8Array instead of Buffer. Signature bytes are unchanged and the
decoder is fuzz-verified against Node's, so Python vector replay and
parity tests pass unmodified. The package now runs on Node >= 20 and
browsers with zero runtime dependencies.

Hashing APIs are now async (WebCrypto is promise-based); ASCII
validation still throws synchronously to preserve parity assertions.

Also drops the unused unrun devDependency and gitignores npm pack
tarballs.

Signed-off-by: Miguel Velasquez <veruiz.miguel@gmail.com>
… result

budget/recurrence/agent_recurrence are spec-assigned to the payment
network (stateful), so the stateless verifier never evaluates them.
ChainVerificationResult.networkEnforced now lists them per mandate pair
so callers know what remains to be enforced. Additive only; Python
verdict parity unchanged.

Also expands the package README.

Signed-off-by: Miguel Velasquez <veruiz.miguel@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant