Node SDK — feature guide
Node.js SDK for the AITP reference implementation
This page is a feature-by-feature pointer into bindings/aitp-node.
Each section names the RFC, the Cargo feature flag (if any), and a
~5-line example. Full TypeScript signatures live in the auto-generated
bindings/aitp-node/index.d.ts.
The Python SDK has a symmetric surface; see sdk-python.md.
Current release: 0.4.1 — carries the 0.4.0 crate hardening (canonical low-S P-256 signatures, RSA-2048 floor on the OIDC/DPoP paths, SSRF-guarded fetches). No SDK function signatures changed.
Build
npm run build:debug # full surface (all capabilities)
npm run build:minimal:debug # minimal surface (--no-default-features)
# Release variants:
npm run build
npm run build:minimalDefault surface
Mutual handshake (RFC-AITP-0004)
import { AitpAgent } from 'aitp';
const alice = AitpAgent.generate();
const bob = AitpAgent.generate();
const bobManifest = bob.buildManifest({
displayName: 'bob',
handshakeEndpoint: 'https://bob.example/aitp/handshake/',
offeredCaps: ['demo.echo'],
});
alice.buildManifest({
displayName: 'alice',
handshakeEndpoint: 'https://alice.example/aitp/handshake/',
offeredCaps: ['demo.write'],
});
const s = alice.newSession(), r = bob.newResponder();
const hello = s.buildHello(bobManifest, ['demo.echo']);
const { ackJson, sessionId } = r.processHello(hello);
const commit = s.processHelloAck(ackJson, sessionId);
const { ackJson: cack } = r.processCommit(commit);
const held = s.complete(cack);
// v0.2: completion exposes the peer-issued TCT (opaque compact JWS), its
// decoded claims, and the optional companion grant voucher.
const tctJws = held.tct.token; // compact JWS string — store / present this
const claims = held.tct.claims; // decoded {ver, jti, iss, sub, aud, iat, exp, grants, cnf}
const voucherJws = held.grantVoucher; // compact JWS string, or null if delegation disallowedTCT verification (RFC-AITP-0005 §9)
A TCT is an opaque compact JWS string. verifyTct parses it strictly
(typ == aitp-tct+jwt, AID-pinned alg, signature over the transmitted bytes)
and returns the verified identity.
// Holder-receipt model (default).
const ident = agent.verifyTct(tctJws, 'demo.echo');
// Presented-TCT model — for a resource server checking a TCT a peer
// presented in `X-AITP-TCT`. Pass the TCT's own subject AID (== sub).
const presented = agent.verifyTct(tctJws, 'demo.echo', peerAid);
// Optional revocation gate (F-1): pass the set of revoked TCT `jti`s; a TCT
// whose jti is listed is rejected with TCT_REVOKED.
const gated = agent.verifyTct(tctJws, 'demo.echo', undefined, ['<revoked-uuid>']);A TCT also verifies under any stock JOSE library (node jose) given only the
issuer's public key. See architecture.md.
Delegation (RFC-AITP-0006)
Delegation embeds the grant voucher A minted alongside B's TCT — pass the voucher string B holds; nothing is reconstructed.
import { verifyDelegation } from 'aitp';
const delegationJws = b.buildDelegation(voucherBHoldsFromA, c.aid, cPubkey, ['demo.write']);
const verified = verifyDelegation(delegationJws, a.aid);
const freshTctForC = a.issueTctForDelegatee(verified); // compact JWS stringManifest verification
import { verifyManifestJson } from 'aitp';
verifyManifestJson(manifestEnvelopeJson); // throws on failureRevocation-list signing
const envelope = issuer.signRevocationList(
[{ jti: 'uuid-here', reason: 'compromised' }],
600,
);OIDC identity (RFC-AITP-0002)
import { JwksProvider } from 'aitp';
const jwks = new JwksProvider({ 'https://idp.example/': [myJwk] });
agent.buildManifest({
...,
identityType: 'oidc',
oidcIssuer: 'https://idp.example/',
oidcSubject: 'alice',
});
const sess = agent.newSession(jwks);
const hello = sess.buildHello(peerManifest, grants, (nonce) =>
myIdp.mintJwtSync({ nonce, sub: 'alice', aud: peerAid }),
);The oidcMintJwt callback is synchronous — it runs on the libuv main
thread inside the buildHello / processHello call. Do not pass an async
function.
P-256 signing suite (RFC-AITP-0001 §5.4.3)
const agent = AitpAgent.generateP256(); // aid:pubkey:p256:<44>
const det = AitpAgent.fromP256Seed(seedBuf);P-256 produces p256.<86-char-b64url> signatures; an algorithm-agile
verifier on the other side accepts them. Caveat: the manifest's
pinned_key identity_hint embeds an Ed25519 public key only, so P-256
agents must use identityType: 'oidc'.
Additional capabilities (on by default)
TCT renewal (RFC-AITP-0013 / RFC-AITP-0004 §8.1, feature renewal)
// currentTct is the holder's TCT compact JWS string.
const req = holder.buildRenewalRequest(currentTct);
const { tct: freshTct, grantVoucher } = issuer.processRenewalRequest(
req, manifestExpUnixSecs, newTtlSecs,
);Session Trust Bundle (RFC-AITP-0010, feature session-bundle)
import { SessionBundleBuilder, verifySessionBundle } from 'aitp';
const envelope = new SessionBundleBuilder(coordinator)
.participant(alice.aid, aliceTct) // aliceTct: compact JWS string
.participant(bob.aid, bobTct) // bobTct: compact JWS string
.build();
const outcome = verifySessionBundle(envelope, alice.aid);
// { kind: 'clear' | 'degraded', activeAids: [...], droppedAids: [...] }SPKI cert pinning (HPKP-style, feature spki-pinning)
import { computeSpkiHash, SpkiPinVerifier } from 'aitp';
const pin = computeSpkiHash(certDerBuffer); // 32-byte Buffer
const verifier = new SpkiPinVerifier([pin]);
verifier.isPinned(otherCertDer); // true / falseWire verifier.isPinned() into your HTTP client's checkServerIdentity
hook (e.g. undici.Agent({ connect: { ... } })). The SDK does no HTTP.
Tests + interop
npm install
npm run build:debug
npm test # 25 binding tests (node --test)
cd ../interop && pytest -v # 12 cross-language interop tests (1 deliberately skipped)