@agentidentitytrustprotocol/aitp 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +160 -0
  2. package/index.d.ts +403 -0
  3. package/index.js +327 -0
  4. package/package.json +61 -0
package/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # aitp — Node.js SDK
2
+
3
+ Node.js bindings for the **Agent Identity & Trust Protocol (AITP)**, built on
4
+ the pure-Rust `aitp-rs` protocol crates via [NAPI-rs](https://napi.rs).
5
+
6
+ A thin SDK: an `AitpAgent` plus initiator/responder session objects whose
7
+ methods take and return JSON strings — the HTTP request/response bodies — so
8
+ agent code never handles a Rust type across the FFI boundary. The API is the
9
+ symmetric counterpart of the Python SDK (`buildManifest` ↔ `build_manifest`).
10
+
11
+ ## Build
12
+
13
+ This crate is **not** part of the `aitp-rs` Cargo workspace. Build it with
14
+ the [NAPI-rs CLI](https://napi.rs):
15
+
16
+ ```bash
17
+ npm install
18
+ npm run build:debug # default `.node` (v0.1 surface only)
19
+ npm run build:experimental:debug # default + post-v0.1 features
20
+ # Release:
21
+ npm run build # default release
22
+ npm run build:experimental # default + experimental, release
23
+ ```
24
+
25
+ This produces `aitp.node` and `index.js` in the package root. Generated
26
+ TypeScript typings (`index.d.ts`) include the experimental surface only
27
+ when the artifact was built with the `experimental` feature.
28
+
29
+ ### Cargo features
30
+
31
+ The default `.node` exposes the v0.1 surface (handshake, TCT, delegation,
32
+ manifest verify, revocation-list signing, OIDC identity). Three post-v0.1
33
+ capabilities live behind opt-in Cargo features:
34
+
35
+ | Feature | Enables | RFC |
36
+ |---------------------------|--------------------------------------------------------------------------|----------------------|
37
+ | `experimental-renewal` | `AitpAgent.buildRenewalRequest` / `processRenewalRequest` | RFC-AITP-0005 §10 |
38
+ | `experimental-bundle` | `SessionBundleBuilder`, `verifySessionBundle` | RFC-AITP-0010 |
39
+ | `experimental-pinning` | `computeSpkiHash`, `SpkiPinVerifier` | HPKP (RFC 7469) |
40
+ | `experimental-multihop-delegation` | `verifyDelegationExperimentalMultihop` | RFC-AITP-0011 |
41
+ | `experimental` (umbrella) | All four above | |
42
+
43
+ Each post-v0.1 capability does **not** promise wire stability until the
44
+ underlying RFC graduates.
45
+
46
+ ## Usage
47
+
48
+ ```javascript
49
+ import { AitpAgent } from '@agentidentitytrustprotocol/aitp';
50
+
51
+ const initiator = AitpAgent.generate();
52
+ const responder = AitpAgent.generate();
53
+
54
+ initiator.buildManifest({
55
+ displayName: 'initiator',
56
+ handshakeEndpoint: 'http://localhost:8100/aitp/handshake/',
57
+ offeredCaps: ['demo.echo'],
58
+ });
59
+ const respManifest = responder.buildManifest({
60
+ displayName: 'responder',
61
+ handshakeEndpoint: 'http://localhost:8200/aitp/handshake/',
62
+ offeredCaps: ['demo.write'],
63
+ });
64
+
65
+ // Four-message mutual handshake — each call's output is the next peer's input.
66
+ const sess = initiator.newSession();
67
+ const rsess = responder.newResponder();
68
+
69
+ const hello = sess.buildHello(respManifest, ['demo.write']);
70
+ const { ackJson: helloAck, sessionId } = rsess.processHello(hello);
71
+ const commit = sess.processHelloAck(helloAck, sessionId);
72
+ const { ackJson: commitAck } = rsess.processCommit(commit);
73
+ const initiatorHeldTct = sess.complete(commitAck);
74
+
75
+ // Each peer now holds a TCT the other issued it.
76
+ const ident = initiator.verifyTct(initiatorHeldTct, 'demo.write');
77
+ console.log(ident.peerAid, ident.grants);
78
+ ```
79
+
80
+ In a real deployment each message moves over HTTP: `buildHello` returns the
81
+ `POST /aitp/handshake/hello` body, `processHello` returns the response body
82
+ plus the value for the `X-Aitp-Session-Id` header, and so on.
83
+
84
+ ## API
85
+
86
+ The full public surface is described in the generated `index.d.ts`; below
87
+ is a summary. All `*Json` parameters and return values are JSON strings.
88
+
89
+ | Type | Default? | Notes |
90
+ |---------------------------|:--------:|----------------------------------------------------------------------------------------------------------------|
91
+ | `AitpAgent` | ✅ | `generate(opts?)` / `fromSeed(buffer, opts?)` (`opts.suite = "ed25519" \| "p256"`), `aid`, `buildManifest(opts)`, `newSession(jwks?, opts?)`, `newResponder(jwks?, opts?)`, `verifyTct(...)`, `buildDelegation(...)`, `issueTctForDelegatee(...)`, `signRevocationList(...)` |
92
+ | `InitiatorSession` | ✅ | `buildHello(peerManifest, grants, oidcMintJwt?)`, `processHelloAck(...)`, `complete(...)` |
93
+ | `ResponderSession` | ✅ | `processHello(hello, oidcMintJwt?)` → `{ ackJson, sessionId }`, `processCommit(commit)` → `{ ackJson, tctJson }` |
94
+ | `TctIdentity` | ✅ | `peerAid`, `grants`, `expiresAt`, `jti` |
95
+ | `DelegationVerified` | ✅ | `delegator`, `delegatee`, `issuedBy`, `grants`, `expiresAt`, `cnf` |
96
+ | `JwksProvider` | ✅ | OIDC JWKS map. `upsert(issuer, keys)`, `remove(issuer)`, `issuers()` |
97
+ | `TctStore` / `verifyTctCached()` | ✅ | Hot-path verify cache: skips the signature check for a byte-identical, still-valid TCT (keyed by SHA-256 of the envelope) |
98
+ | `verifyDelegation()` | ✅ | RFC-AITP-0006 — strict v0.1 single-hop; rejects any multi-hop `chain` |
99
+ | `verifyManifestJson()` | ✅ | Control-plane manifest enrollment |
100
+ | `buildRenewalRequest()` / `processRenewalRequest()` | `experimental-renewal` | RFC-AITP-0005 §10 |
101
+ | `SessionBundleBuilder`, `verifySessionBundle()` | `experimental-bundle` | RFC-AITP-0010 |
102
+ | `computeSpkiHash()`, `SpkiPinVerifier` | `experimental-pinning` | HPKP outbound pinning |
103
+ | `verifyDelegationExperimentalMultihop()` | `experimental-multihop-delegation` | RFC-AITP-0011 (draft) multi-hop opt-in |
104
+
105
+ ### OIDC identity (RFC-AITP-0002)
106
+
107
+ ```javascript
108
+ import { AitpAgent, JwksProvider } from '@agentidentitytrustprotocol/aitp';
109
+
110
+ const jwks = new JwksProvider({
111
+ 'https://idp.example/': [{ kty: 'OKP', crv: 'Ed25519', x: '...', kid: 'k1', alg: 'EdDSA' }],
112
+ });
113
+
114
+ const agent = AitpAgent.generate();
115
+ agent.buildManifest({
116
+ displayName: 'alice',
117
+ handshakeEndpoint: 'https://alice.example/aitp/handshake/',
118
+ offeredCaps: ['demo.echo'],
119
+ identityType: 'oidc',
120
+ oidcIssuer: 'https://idp.example/',
121
+ oidcSubject: 'alice',
122
+ });
123
+ const sess = agent.newSession(jwks);
124
+
125
+ const mintJwt = (nonce) => myIdp.mintJwtSync({ nonce, sub: 'alice', aud: peerAid });
126
+ const hello = sess.buildHello(peerManifest, ['demo.echo'], mintJwt);
127
+ ```
128
+
129
+ ### P-256 signing (RFC-AITP-0001 §5.4.3)
130
+
131
+ ```javascript
132
+ const agent = AitpAgent.generate({ suite: 'p256' }); // aid:pubkey:p256:<44>
133
+ // Deterministic from a seed:
134
+ const seeded = AitpAgent.fromSeed(seed, { suite: 'p256' });
135
+ // All other methods identical; signatures emitted as `p256.<86b64u>`.
136
+ ```
137
+
138
+ > **Breaking change in v0.2:** `AitpAgent.generateP256()` and
139
+ > `AitpAgent.fromP256Seed(seed)` were removed in favor of the
140
+ > parameterized `generate({ suite })` / `fromSeed(seed, { suite })`
141
+ > API. This matches the Python SDK's
142
+ > `AitpAgent.generate(suite="p256")` shape — CLAUDE.md mandates SDK
143
+ > symmetry. Migration: replace `generateP256()` with
144
+ > `generate({ suite: 'p256' })` and `fromP256Seed(seed)` with
145
+ > `fromSeed(seed, { suite: 'p256' })`.
146
+
147
+ > **Note.** In v0.1 the `pinned_key` identity_hint embeds an Ed25519 raw
148
+ > public key. P-256 agents must therefore use `identityType: 'oidc'` until
149
+ > the manifest's identity_hint shape is extended.
150
+
151
+ ## Tests
152
+
153
+ ```bash
154
+ npm install
155
+ npm run build:debug
156
+ npm test # node --test tests/*.mjs
157
+ ```
158
+
159
+ The cross-language interop suite (Python ↔ Node) lives in
160
+ [`../interop`](../interop) — run it with `make interop` from the repo root.
package/index.d.ts ADDED
@@ -0,0 +1,403 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /* auto-generated by NAPI-RS */
5
+
6
+ /**
7
+ * Input shape for `signRevocationList`. Field names map to spec
8
+ * `RevocationEntry` (jti, revoked_at, reason).
9
+ */
10
+ export interface RevocationEntryInput {
11
+ /** JTI of the revoked TCT (UUID string). */
12
+ jti: string
13
+ /**
14
+ * Unix seconds when the issuing peer revoked the TCT.
15
+ * Defaults to the current time when omitted.
16
+ */
17
+ revokedAt?: number
18
+ /** Optional human-readable reason. Not used in trust decisions. */
19
+ reason?: string
20
+ }
21
+ /** Options for `buildManifest`. */
22
+ export interface ManifestOpts {
23
+ /** Human-readable agent name; also the pinned-key identity subject. */
24
+ displayName: string
25
+ /** Absolute URL of this agent's handshake endpoint. */
26
+ handshakeEndpoint: string
27
+ /** Capabilities this agent offers to peers. */
28
+ offeredCaps: Array<string>
29
+ /** Capabilities a peer must grant back (optional). */
30
+ requiredCaps?: Array<string>
31
+ /** Manifest TTL in seconds (optional; defaults to 3600). */
32
+ ttlSecs?: number
33
+ /** Identity presentation type — `"pinned_key"` (default) or `"oidc"`. */
34
+ identityType?: string
35
+ /** OIDC issuer URL (required when `identityType="oidc"`). */
36
+ oidcIssuer?: string
37
+ /** OIDC subject identifier (required when `identityType="oidc"`). */
38
+ oidcSubject?: string
39
+ /**
40
+ * Override the manifest's accepted_trust_anchors list. Defaults to
41
+ * `[oidc_issuer]` for OIDC mode and `[]` for pinned-key mode.
42
+ */
43
+ acceptedTrustAnchors?: Array<string>
44
+ }
45
+ /**
46
+ * Trust-anchor override for `newSession` / `newResponder`.
47
+ *
48
+ * JWKS provider is passed as a separate argument (napi-rs can't put a
49
+ * class reference inside an `#[napi(object)]` struct).
50
+ */
51
+ export interface SessionOpts {
52
+ /**
53
+ * Override the agent manifest's accepted_trust_anchors. `null`
54
+ * reuses the manifest's list.
55
+ */
56
+ trustAnchors?: Array<string>
57
+ }
58
+ /**
59
+ * Options for `AitpAgent.generate` and `AitpAgent.fromSeed`.
60
+ *
61
+ * Mirrors the Python SDK's `suite="ed25519"|"p256"` keyword argument so
62
+ * the two SDKs share one shape per operation. `null`/omitted defaults
63
+ * to `"ed25519"`.
64
+ */
65
+ export interface GenerateOpts {
66
+ /**
67
+ * Signing suite. Accepted values: `"ed25519"` (default) or
68
+ * `"p256"` (RFC-AITP-0001 §5.4.3).
69
+ */
70
+ suite?: string
71
+ }
72
+ /**
73
+ * Outcome shape returned by `verifySessionBundle`. `kind` is `"clear"`
74
+ * or `"degraded"`; `droppedAids` is empty in the `"clear"` case.
75
+ */
76
+ export interface JsBundleOutcome {
77
+ kind: string
78
+ activeAids: Array<string>
79
+ droppedAids: Array<string>
80
+ }
81
+ /**
82
+ * Verify a `SessionBundleEnvelope` JSON. `nowUnixSecs` defaults to
83
+ * the system clock. `revocationCheck` receives a JTI string and
84
+ * returns true if revoked.
85
+ */
86
+ export declare function verifySessionBundle(bundleEnvelopeJson: string, verifierAid: string, nowUnixSecs?: number | undefined | null, revocationCheck?: (...args: any[]) => any | undefined | null): JsBundleOutcome
87
+ /**
88
+ * The verified delegation token's salient fields. Returned from
89
+ * `verifyDelegation` and consumed by `AitpAgent.issueTctForDelegatee`.
90
+ */
91
+ export interface JsDelegationVerified {
92
+ /**
93
+ * AID of the ultimate grantor (the original TCT issuer that gave grants
94
+ * to `issuedBy`). The responder side checks this matches its own AID
95
+ * before redeeming.
96
+ */
97
+ delegator: string
98
+ /** AID of the recipient (C) — who will receive a fresh TCT. */
99
+ delegatee: string
100
+ /** AID of the agent that issued this delegation (B in the RFC). */
101
+ issuedBy: string
102
+ /** Capabilities being delegated. Always a subset of the source TCT's grants. */
103
+ grants: Array<string>
104
+ /** Unix-seconds expiry of the delegation token itself. */
105
+ expiresAt: number
106
+ /**
107
+ * Delegatee's raw Ed25519 / P-256 public key, base64url-encoded. This is
108
+ * the `cnf` binding — proves which key the issuer should bind to the
109
+ * fresh TCT it mints.
110
+ */
111
+ cnf: string
112
+ }
113
+ /**
114
+ * Verify a `DelegationEnvelope` JSON under **strict AITP v0.1**
115
+ * (RFC-AITP-0006 single-hop). `verifierAid` is the verifier's own AID
116
+ * string — verification fails if it doesn't match the token's `delegator`
117
+ * field.
118
+ *
119
+ * Any token carrying a non-empty `chain` (a draft RFC-AITP-0011 multi-hop
120
+ * delegation) is **rejected** with `DELEGATION_MULTIHOP_NOT_SUPPORTED`,
121
+ * matching the Rust core default. To opt into multi-hop, build the SDK with
122
+ * the `experimental-multihop-delegation` feature and call
123
+ * `verifyDelegationExperimentalMultihop`.
124
+ */
125
+ export declare function verifyDelegation(envelopeJson: string, verifierAid: string): JsDelegationVerified
126
+ /**
127
+ * Verify a `DelegationEnvelope` JSON allowing **draft RFC-AITP-0011
128
+ * multi-hop** chains up to `maxHops` total hops (`chain.length + 1`).
129
+ *
130
+ * This opts into behavior that is **not** part of AITP v0.1. It is only
131
+ * compiled in under the `experimental-multihop-delegation` feature; a
132
+ * default build exposes only the strict `verifyDelegation`.
133
+ *
134
+ * `maxHops` defaults to `DEFAULT_MAX_HOPS` (3, the RFC-AITP-0011 §2
135
+ * recommended ceiling). Pass a smaller value for a tighter bound;
136
+ * `maxHops = 0` reverts to strict v0.1 (rejects any non-empty chain).
137
+ */
138
+ export declare function verifyDelegationExperimentalMultihop(envelopeJson: string, verifierAid: string, maxHops?: number | undefined | null): JsDelegationVerified
139
+ /**
140
+ * Compute the RFC 7638 JWK thumbprint of the public key embedded in
141
+ * an AID — the value an OIDC IdP MUST place in the JWT's `cnf.jkt`
142
+ * claim when proving the key binding (RFC-AITP-0002 §2.2.1).
143
+ *
144
+ * Works for both Ed25519 (`aid:pubkey:ed25519:...`) and P-256
145
+ * (`aid:pubkey:p256:...`) AIDs. Use this from your OIDC minter
146
+ * closure so the bound peer correctly verifies the cnf binding —
147
+ * implementing curve arithmetic by hand to derive the thumbprint
148
+ * in JavaScript is both error-prone and a divergence risk.
149
+ */
150
+ export declare function computeAidJkt(aid: string): string
151
+ /**
152
+ * Compute the SHA-256 SPKI hash for an X.509 DER certificate. Returns
153
+ * a 32-byte Buffer. Throws if `certDer` is not a parseable certificate.
154
+ */
155
+ export declare function computeSpkiHash(certDer: Buffer): Buffer
156
+ /** Result of `processHello`: response body plus session id. */
157
+ export interface JsHelloAckResult {
158
+ /** `MUTUAL_HELLO_ACK` envelope JSON — set as the HTTP response body. */
159
+ ackJson: string
160
+ /** Correlation id — set as the `X-Aitp-Session-Id` response header. */
161
+ sessionId: string
162
+ }
163
+ /** Result of `processCommit`: response body plus the held TCT. */
164
+ export interface JsCommitAckResult {
165
+ /** `MUTUAL_COMMIT_ACK` envelope JSON — set as the HTTP response body. */
166
+ ackJson: string
167
+ /** `TctEnvelope` JSON the initiator issued to us. */
168
+ tctJson: string
169
+ }
170
+ /** The verified peer identity carried by a TCT. */
171
+ export interface JsTctIdentity {
172
+ /** AID of the agent that issued (and is bound by) the TCT. */
173
+ peerAid: string
174
+ /** Capability grants the TCT authorizes. */
175
+ grants: Array<string>
176
+ /** Expiry, Unix seconds. */
177
+ expiresAt: number
178
+ /** TCT unique identifier (`jti`). */
179
+ jti: string
180
+ }
181
+ /**
182
+ * Verify a `ManifestEnvelope` JSON string. Throws on signature,
183
+ * proof-of-possession, expiry, or identity-hint shape failures.
184
+ * Used by the AITP Control Plane during agent enrollment.
185
+ */
186
+ export declare function verifyManifestJson(manifestEnvelopeJson: string): void
187
+ /** An AITP agent: a signing key and (once built) its Manifest. */
188
+ export declare class AitpAgent {
189
+ /**
190
+ * Generate an agent with a fresh random signing key.
191
+ *
192
+ * `opts.suite` selects the algorithm — `"ed25519"` (default) or
193
+ * `"p256"`. Symmetric with the Python SDK's
194
+ * `AitpAgent.generate(suite="ed25519")`.
195
+ *
196
+ * ```ts
197
+ * const ed = AitpAgent.generate(); // Ed25519
198
+ * const p = AitpAgent.generate({ suite: 'p256' }); // P-256
199
+ * ```
200
+ */
201
+ static generate(opts?: GenerateOpts | undefined | null): AitpAgent
202
+ /**
203
+ * Construct an agent from a 32-byte seed (deterministic).
204
+ *
205
+ * `opts.suite` matches `AitpAgent.generate`. For `"p256"`, a tiny
206
+ * fraction of 32-byte seeds are not valid private scalars (zero
207
+ * or ≥ curve order) and raise.
208
+ *
209
+ * Note: the prior `AitpAgent.fromP256Seed` factory and the
210
+ * no-arg `AitpAgent.generate*` variants were removed in favor of
211
+ * this parameterized API so the Node SDK matches the Python SDK
212
+ * surface (CLAUDE.md mandates SDK symmetry).
213
+ */
214
+ static fromSeed(seed: Buffer, opts?: GenerateOpts | undefined | null): AitpAgent
215
+ /** The agent's AID string (`aid:pubkey:...`). */
216
+ get aid(): string
217
+ /**
218
+ * Build and sign the agent's Manifest. Returns `ManifestEnvelope`
219
+ * JSON and caches the Manifest for `newSession` / `newResponder`.
220
+ *
221
+ * See `ManifestOpts.identityType` for the OIDC switch.
222
+ */
223
+ buildManifest(opts: ManifestOpts): string
224
+ /**
225
+ * Create a new outbound (initiator) handshake session.
226
+ *
227
+ * `jwks` is an optional `JwksProvider` used when the peer's
228
+ * manifest advertises an OIDC identity. `opts.trustAnchors`
229
+ * overrides the manifest's `accepted_trust_anchors`.
230
+ */
231
+ newSession(jwks?: JwksProvider | undefined | null, opts?: SessionOpts | undefined | null): JsInitiatorSession
232
+ /**
233
+ * Create a new inbound (responder) handshake session. Mirror of
234
+ * `newSession`.
235
+ */
236
+ newResponder(jwks?: JwksProvider | undefined | null, opts?: SessionOpts | undefined | null): JsResponderSession
237
+ /**
238
+ * Verify a TCT JSON string and require `requiredGrant`. Rejects on
239
+ * an invalid, mis-audienced, expired, or under-scoped TCT.
240
+ *
241
+ * `expectedAudience` defaults to `null`, which means "verify as the
242
+ * holder" (RFC-AITP-0005 §9 receipt model — `this.aid` is used).
243
+ * Resource servers verifying a TCT presented by a peer should pass
244
+ * the TCT's own `audience` field as `expectedAudience` (in v0.1 this
245
+ * equals `subject`).
246
+ */
247
+ verifyTct(tctJson: string, requiredGrant: string, expectedAudience?: string | undefined | null): JsTctIdentity
248
+ /**
249
+ * Like `verifyTct`, but consults a `TctStore` first: a byte-identical,
250
+ * already-verified, still-valid TCT skips the signature check (the
251
+ * verification hot path for an agent that sees the same TCT on many
252
+ * requests). All cheap policy checks (expiry, audience, required grant)
253
+ * still run on every call; only the signature check is elided.
254
+ */
255
+ verifyTctCached(tctJson: string, requiredGrant: string, store: JsTctStore, expectedAudience?: string | undefined | null): JsTctIdentity
256
+ /** Build a `DelegationEnvelope` JSON from a held TCT (RFC-AITP-0006). */
257
+ buildDelegation(heldTctEnvelopeJson: string, delegateeAid: string, delegateePubkeyB64U: string, scope: Array<string>, ttlSecs?: number | undefined | null): string
258
+ /**
259
+ * Mint a fresh `TctEnvelope` JSON for a delegatee after the verifier has
260
+ * confirmed the delegation. The subject_pubkey binding is taken from the
261
+ * verified token's `cnf` field.
262
+ */
263
+ issueTctForDelegatee(verified: JsDelegationVerified, ttlSecs?: number | undefined | null): string
264
+ /**
265
+ * Sign a `RevocationList` with this agent's key. `entries` carries
266
+ * the revoked-TCT records; `expiresInSecs` defaults to 3600 s.
267
+ * Returns the on-wire `RevocationListEnvelope` JSON.
268
+ */
269
+ signRevocationList(entries: Array<RevocationEntryInput>, expiresInSecs?: number | undefined | null): string
270
+ /**
271
+ * Holder side: build a `TctRenewalPayload` JSON for an in-band
272
+ * renewal of `currentTctEnvelopeJson` (RFC-AITP-0005 §10).
273
+ *
274
+ * **Gated by the `experimental-renewal` Cargo feature.** Off in
275
+ * the default `.node` artifact; build with
276
+ * `napi build --release -- --features experimental-renewal`.
277
+ */
278
+ buildRenewalRequest(currentTctEnvelopeJson: string): string
279
+ /**
280
+ * Issuer side: verify a `TctRenewalPayload` JSON request and mint a
281
+ * fresh `TctEnvelope` JSON.
282
+ *
283
+ * **Gated by the `experimental-renewal` Cargo feature.**
284
+ */
285
+ processRenewalRequest(requestPayloadJson: string, manifestExpUnixSecs: number, newTtlSecs: number): string
286
+ }
287
+ export type SessionBundleBuilderJs = SessionBundleBuilder
288
+ /**
289
+ * Fluent builder for issuing a `SessionBundleEnvelope`. Constructed
290
+ * from the coordinator's `AitpAgent`.
291
+ *
292
+ * The Rust struct is suffixed `Js` to avoid a name collision with the
293
+ * `aitp_session_bundle::SessionBundleBuilder` imported above; the
294
+ * `#[napi(js_name)]` attribute exposes it to JavaScript under the
295
+ * plain name `SessionBundleBuilder` for parity with the Python SDK.
296
+ */
297
+ export declare class SessionBundleBuilder {
298
+ /** Construct a builder backed by `coordinator`'s signing key. */
299
+ constructor(coordinator: AitpAgent)
300
+ /** Set the session ID (UUIDv4 string). Defaults to a fresh one. */
301
+ sessionId(uuidStr: string): this
302
+ /** Override `issued_at` (unix seconds). Defaults to "now" at build. */
303
+ issuedAt(unixSecs: number): this
304
+ /** Add a participant entry. `tctEnvelopeJson` is a TctEnvelope JSON. */
305
+ participant(aid: string, tctEnvelopeJson: string): this
306
+ /** Construct, sign, and return the `SessionBundleEnvelope` JSON. */
307
+ build(): string
308
+ }
309
+ /**
310
+ * In-memory OIDC JWKS provider — issuer URL → list of JWK objects.
311
+ *
312
+ * The Node caller fetches the JWKS (the SDK has no HTTP client) and
313
+ * hands the parsed objects to this provider. Implements `JwksResolver`
314
+ * internally so the handshake state machine can dispatch through it.
315
+ */
316
+ export declare class JwksProvider {
317
+ /**
318
+ * Construct from an object mapping issuer URLs to arrays of JWK
319
+ * objects. Each JWK must be a standard RFC 7517 representation
320
+ * (`kty`, `crv`, `x`, ...). Pass `{}` for an empty provider and
321
+ * `upsert` later.
322
+ */
323
+ constructor(keys?: unknown | undefined | null)
324
+ /**
325
+ * Add or replace the JWKs for an issuer. `keys` is a JS array of
326
+ * JWK objects.
327
+ */
328
+ upsert(issuer: string, keys: unknown): void
329
+ /** Drop all keys for an issuer. */
330
+ remove(issuer: string): void
331
+ /** Return the issuer URLs currently registered. */
332
+ issuers(): Array<string>
333
+ }
334
+ /**
335
+ * Holds a list of 32-byte SPKI pins. `isPinned(certDer)` returns true
336
+ * iff the cert's SPKI hash is in the list.
337
+ */
338
+ export declare class SpkiPinVerifier {
339
+ /**
340
+ * Construct from an array of 32-byte Buffers. Throws if any pin is
341
+ * not exactly 32 bytes.
342
+ */
343
+ constructor(pins: Array<Buffer>)
344
+ /** Returns true iff the SPKI hash of `certDer` is in the pin list. */
345
+ isPinned(certDer: Buffer): boolean
346
+ /** Number of pins currently held. */
347
+ get len(): number
348
+ }
349
+ /** Outbound handshake session — drives the initiator side. */
350
+ export declare class JsInitiatorSession {
351
+ /**
352
+ * Step 1 — build the `MUTUAL_HELLO` envelope.
353
+ *
354
+ * `oidcMintJwt`: required for OIDC agents. Receives the handshake-
355
+ * generated `pop_nonce` (string) and must return the JWT (string).
356
+ */
357
+ buildHello(peerManifestJson: string, requestedGrants: Array<string>, oidcMintJwt?: (...args: any[]) => any | undefined | null): string
358
+ /** Step 2 — process `MUTUAL_HELLO_ACK`, produce `MUTUAL_COMMIT`. */
359
+ processHelloAck(helloAckJson: string, sessionId: string): string
360
+ /**
361
+ * Step 3 — process `MUTUAL_COMMIT_ACK`. Returns the `TctEnvelope`
362
+ * JSON the peer issued to us.
363
+ */
364
+ complete(commitAckJson: string): string
365
+ }
366
+ /** Inbound handshake session — drives the responder side. */
367
+ export declare class JsResponderSession {
368
+ /**
369
+ * Process an incoming `MUTUAL_HELLO` envelope.
370
+ *
371
+ * `oidcMintJwt`: required for OIDC agents (see `JsInitiatorSession.buildHello`).
372
+ */
373
+ processHello(helloJson: string, oidcMintJwt?: (...args: any[]) => any | undefined | null): JsHelloAckResult
374
+ /** Process an incoming `MUTUAL_COMMIT` envelope. */
375
+ processCommit(commitJson: string): JsCommitAckResult
376
+ }
377
+ export type JsTctStore = TctStore
378
+ /**
379
+ * A bounded, in-memory cache of **successful** TCT verifications, keyed by
380
+ * the SHA-256 of the exact TCT envelope JSON bytes.
381
+ *
382
+ * Purpose: a high-throughput verifier (e.g. a writer agent checking a
383
+ * capability on every request) that repeatedly sees the *same* TCT can skip
384
+ * the Ed25519/P-256 signature verification after the first time.
385
+ *
386
+ * **Safety.** The key is a cryptographic hash of the exact signed bytes, so
387
+ * only a byte-identical envelope — whose signature was already verified once
388
+ * — can hit. A tampered token hashes differently, misses, and is fully
389
+ * verified. The cheap policy checks (expiry, audience, required grant) are
390
+ * re-run on **every** hit; only the signature check is elided. Eviction is
391
+ * FIFO once `maxEntries` is reached.
392
+ */
393
+ export declare class TctStore {
394
+ /**
395
+ * Create a cache holding at most `maxEntries` verified TCTs (FIFO
396
+ * eviction). `maxEntries` must be >= 1.
397
+ */
398
+ constructor(maxEntries: number)
399
+ /** Number of cached verifications currently held. */
400
+ len(): number
401
+ /** Drop all cached entries. */
402
+ clear(): void
403
+ }
package/index.js ADDED
@@ -0,0 +1,327 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /* prettier-ignore */
4
+
5
+ /* auto-generated by NAPI-RS */
6
+
7
+ const { existsSync, readFileSync } = require('fs')
8
+ const { join } = require('path')
9
+
10
+ const { platform, arch } = process
11
+
12
+ let nativeBinding = null
13
+ let localFileExisted = false
14
+ let loadError = null
15
+
16
+ function isMusl() {
17
+ // For Node 10
18
+ if (!process.report || typeof process.report.getReport !== 'function') {
19
+ try {
20
+ const lddPath = require('child_process').execSync('which ldd').toString().trim()
21
+ return readFileSync(lddPath, 'utf8').includes('musl')
22
+ } catch (e) {
23
+ return true
24
+ }
25
+ } else {
26
+ const { glibcVersionRuntime } = process.report.getReport().header
27
+ return !glibcVersionRuntime
28
+ }
29
+ }
30
+
31
+ switch (platform) {
32
+ case 'android':
33
+ switch (arch) {
34
+ case 'arm64':
35
+ localFileExisted = existsSync(join(__dirname, 'aitp.android-arm64.node'))
36
+ try {
37
+ if (localFileExisted) {
38
+ nativeBinding = require('./aitp.android-arm64.node')
39
+ } else {
40
+ nativeBinding = require('@agentidentitytrustprotocol/aitp-android-arm64')
41
+ }
42
+ } catch (e) {
43
+ loadError = e
44
+ }
45
+ break
46
+ case 'arm':
47
+ localFileExisted = existsSync(join(__dirname, 'aitp.android-arm-eabi.node'))
48
+ try {
49
+ if (localFileExisted) {
50
+ nativeBinding = require('./aitp.android-arm-eabi.node')
51
+ } else {
52
+ nativeBinding = require('@agentidentitytrustprotocol/aitp-android-arm-eabi')
53
+ }
54
+ } catch (e) {
55
+ loadError = e
56
+ }
57
+ break
58
+ default:
59
+ throw new Error(`Unsupported architecture on Android ${arch}`)
60
+ }
61
+ break
62
+ case 'win32':
63
+ switch (arch) {
64
+ case 'x64':
65
+ localFileExisted = existsSync(
66
+ join(__dirname, 'aitp.win32-x64-msvc.node')
67
+ )
68
+ try {
69
+ if (localFileExisted) {
70
+ nativeBinding = require('./aitp.win32-x64-msvc.node')
71
+ } else {
72
+ nativeBinding = require('@agentidentitytrustprotocol/aitp-win32-x64-msvc')
73
+ }
74
+ } catch (e) {
75
+ loadError = e
76
+ }
77
+ break
78
+ case 'ia32':
79
+ localFileExisted = existsSync(
80
+ join(__dirname, 'aitp.win32-ia32-msvc.node')
81
+ )
82
+ try {
83
+ if (localFileExisted) {
84
+ nativeBinding = require('./aitp.win32-ia32-msvc.node')
85
+ } else {
86
+ nativeBinding = require('@agentidentitytrustprotocol/aitp-win32-ia32-msvc')
87
+ }
88
+ } catch (e) {
89
+ loadError = e
90
+ }
91
+ break
92
+ case 'arm64':
93
+ localFileExisted = existsSync(
94
+ join(__dirname, 'aitp.win32-arm64-msvc.node')
95
+ )
96
+ try {
97
+ if (localFileExisted) {
98
+ nativeBinding = require('./aitp.win32-arm64-msvc.node')
99
+ } else {
100
+ nativeBinding = require('@agentidentitytrustprotocol/aitp-win32-arm64-msvc')
101
+ }
102
+ } catch (e) {
103
+ loadError = e
104
+ }
105
+ break
106
+ default:
107
+ throw new Error(`Unsupported architecture on Windows: ${arch}`)
108
+ }
109
+ break
110
+ case 'darwin':
111
+ localFileExisted = existsSync(join(__dirname, 'aitp.darwin-universal.node'))
112
+ try {
113
+ if (localFileExisted) {
114
+ nativeBinding = require('./aitp.darwin-universal.node')
115
+ } else {
116
+ nativeBinding = require('@agentidentitytrustprotocol/aitp-darwin-universal')
117
+ }
118
+ break
119
+ } catch {}
120
+ switch (arch) {
121
+ case 'x64':
122
+ localFileExisted = existsSync(join(__dirname, 'aitp.darwin-x64.node'))
123
+ try {
124
+ if (localFileExisted) {
125
+ nativeBinding = require('./aitp.darwin-x64.node')
126
+ } else {
127
+ nativeBinding = require('@agentidentitytrustprotocol/aitp-darwin-x64')
128
+ }
129
+ } catch (e) {
130
+ loadError = e
131
+ }
132
+ break
133
+ case 'arm64':
134
+ localFileExisted = existsSync(
135
+ join(__dirname, 'aitp.darwin-arm64.node')
136
+ )
137
+ try {
138
+ if (localFileExisted) {
139
+ nativeBinding = require('./aitp.darwin-arm64.node')
140
+ } else {
141
+ nativeBinding = require('@agentidentitytrustprotocol/aitp-darwin-arm64')
142
+ }
143
+ } catch (e) {
144
+ loadError = e
145
+ }
146
+ break
147
+ default:
148
+ throw new Error(`Unsupported architecture on macOS: ${arch}`)
149
+ }
150
+ break
151
+ case 'freebsd':
152
+ if (arch !== 'x64') {
153
+ throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
154
+ }
155
+ localFileExisted = existsSync(join(__dirname, 'aitp.freebsd-x64.node'))
156
+ try {
157
+ if (localFileExisted) {
158
+ nativeBinding = require('./aitp.freebsd-x64.node')
159
+ } else {
160
+ nativeBinding = require('@agentidentitytrustprotocol/aitp-freebsd-x64')
161
+ }
162
+ } catch (e) {
163
+ loadError = e
164
+ }
165
+ break
166
+ case 'linux':
167
+ switch (arch) {
168
+ case 'x64':
169
+ if (isMusl()) {
170
+ localFileExisted = existsSync(
171
+ join(__dirname, 'aitp.linux-x64-musl.node')
172
+ )
173
+ try {
174
+ if (localFileExisted) {
175
+ nativeBinding = require('./aitp.linux-x64-musl.node')
176
+ } else {
177
+ nativeBinding = require('@agentidentitytrustprotocol/aitp-linux-x64-musl')
178
+ }
179
+ } catch (e) {
180
+ loadError = e
181
+ }
182
+ } else {
183
+ localFileExisted = existsSync(
184
+ join(__dirname, 'aitp.linux-x64-gnu.node')
185
+ )
186
+ try {
187
+ if (localFileExisted) {
188
+ nativeBinding = require('./aitp.linux-x64-gnu.node')
189
+ } else {
190
+ nativeBinding = require('@agentidentitytrustprotocol/aitp-linux-x64-gnu')
191
+ }
192
+ } catch (e) {
193
+ loadError = e
194
+ }
195
+ }
196
+ break
197
+ case 'arm64':
198
+ if (isMusl()) {
199
+ localFileExisted = existsSync(
200
+ join(__dirname, 'aitp.linux-arm64-musl.node')
201
+ )
202
+ try {
203
+ if (localFileExisted) {
204
+ nativeBinding = require('./aitp.linux-arm64-musl.node')
205
+ } else {
206
+ nativeBinding = require('@agentidentitytrustprotocol/aitp-linux-arm64-musl')
207
+ }
208
+ } catch (e) {
209
+ loadError = e
210
+ }
211
+ } else {
212
+ localFileExisted = existsSync(
213
+ join(__dirname, 'aitp.linux-arm64-gnu.node')
214
+ )
215
+ try {
216
+ if (localFileExisted) {
217
+ nativeBinding = require('./aitp.linux-arm64-gnu.node')
218
+ } else {
219
+ nativeBinding = require('@agentidentitytrustprotocol/aitp-linux-arm64-gnu')
220
+ }
221
+ } catch (e) {
222
+ loadError = e
223
+ }
224
+ }
225
+ break
226
+ case 'arm':
227
+ if (isMusl()) {
228
+ localFileExisted = existsSync(
229
+ join(__dirname, 'aitp.linux-arm-musleabihf.node')
230
+ )
231
+ try {
232
+ if (localFileExisted) {
233
+ nativeBinding = require('./aitp.linux-arm-musleabihf.node')
234
+ } else {
235
+ nativeBinding = require('@agentidentitytrustprotocol/aitp-linux-arm-musleabihf')
236
+ }
237
+ } catch (e) {
238
+ loadError = e
239
+ }
240
+ } else {
241
+ localFileExisted = existsSync(
242
+ join(__dirname, 'aitp.linux-arm-gnueabihf.node')
243
+ )
244
+ try {
245
+ if (localFileExisted) {
246
+ nativeBinding = require('./aitp.linux-arm-gnueabihf.node')
247
+ } else {
248
+ nativeBinding = require('@agentidentitytrustprotocol/aitp-linux-arm-gnueabihf')
249
+ }
250
+ } catch (e) {
251
+ loadError = e
252
+ }
253
+ }
254
+ break
255
+ case 'riscv64':
256
+ if (isMusl()) {
257
+ localFileExisted = existsSync(
258
+ join(__dirname, 'aitp.linux-riscv64-musl.node')
259
+ )
260
+ try {
261
+ if (localFileExisted) {
262
+ nativeBinding = require('./aitp.linux-riscv64-musl.node')
263
+ } else {
264
+ nativeBinding = require('@agentidentitytrustprotocol/aitp-linux-riscv64-musl')
265
+ }
266
+ } catch (e) {
267
+ loadError = e
268
+ }
269
+ } else {
270
+ localFileExisted = existsSync(
271
+ join(__dirname, 'aitp.linux-riscv64-gnu.node')
272
+ )
273
+ try {
274
+ if (localFileExisted) {
275
+ nativeBinding = require('./aitp.linux-riscv64-gnu.node')
276
+ } else {
277
+ nativeBinding = require('@agentidentitytrustprotocol/aitp-linux-riscv64-gnu')
278
+ }
279
+ } catch (e) {
280
+ loadError = e
281
+ }
282
+ }
283
+ break
284
+ case 's390x':
285
+ localFileExisted = existsSync(
286
+ join(__dirname, 'aitp.linux-s390x-gnu.node')
287
+ )
288
+ try {
289
+ if (localFileExisted) {
290
+ nativeBinding = require('./aitp.linux-s390x-gnu.node')
291
+ } else {
292
+ nativeBinding = require('@agentidentitytrustprotocol/aitp-linux-s390x-gnu')
293
+ }
294
+ } catch (e) {
295
+ loadError = e
296
+ }
297
+ break
298
+ default:
299
+ throw new Error(`Unsupported architecture on Linux: ${arch}`)
300
+ }
301
+ break
302
+ default:
303
+ throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
304
+ }
305
+
306
+ if (!nativeBinding) {
307
+ if (loadError) {
308
+ throw loadError
309
+ }
310
+ throw new Error(`Failed to load native binding`)
311
+ }
312
+
313
+ const { AitpAgent, SessionBundleBuilder, verifySessionBundle, verifyDelegation, verifyDelegationExperimentalMultihop, computeAidJkt, JwksProvider, computeSpkiHash, SpkiPinVerifier, JsInitiatorSession, JsResponderSession, TctStore, verifyManifestJson } = nativeBinding
314
+
315
+ module.exports.AitpAgent = AitpAgent
316
+ module.exports.SessionBundleBuilder = SessionBundleBuilder
317
+ module.exports.verifySessionBundle = verifySessionBundle
318
+ module.exports.verifyDelegation = verifyDelegation
319
+ module.exports.verifyDelegationExperimentalMultihop = verifyDelegationExperimentalMultihop
320
+ module.exports.computeAidJkt = computeAidJkt
321
+ module.exports.JwksProvider = JwksProvider
322
+ module.exports.computeSpkiHash = computeSpkiHash
323
+ module.exports.SpkiPinVerifier = SpkiPinVerifier
324
+ module.exports.JsInitiatorSession = JsInitiatorSession
325
+ module.exports.JsResponderSession = JsResponderSession
326
+ module.exports.TctStore = TctStore
327
+ module.exports.verifyManifestJson = verifyManifestJson
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@agentidentitytrustprotocol/aitp",
3
+ "version": "0.2.0",
4
+ "description": "Agent Identity Trust Protocol — Node.js SDK",
5
+ "license": "MIT OR Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/agentidentitytrustprotocol/aitp-rs.git",
9
+ "directory": "bindings/aitp-node"
10
+ },
11
+ "homepage": "https://github.com/agentidentitytrustprotocol/aitp-rs/tree/main/bindings/aitp-node",
12
+ "main": "index.js",
13
+ "types": "index.d.ts",
14
+ "type": "commonjs",
15
+ "files": [
16
+ "index.js",
17
+ "index.d.ts"
18
+ ],
19
+ "napi": {
20
+ "name": "aitp",
21
+ "triples": {
22
+ "defaults": false,
23
+ "additional": [
24
+ "x86_64-apple-darwin",
25
+ "aarch64-apple-darwin",
26
+ "x86_64-unknown-linux-gnu",
27
+ "aarch64-unknown-linux-gnu"
28
+ ]
29
+ }
30
+ },
31
+ "scripts": {
32
+ "build": "napi build --platform --release",
33
+ "build:debug": "napi build --platform",
34
+ "build:experimental": "napi build --platform --release --features experimental",
35
+ "build:experimental:debug": "napi build --platform --features experimental",
36
+ "artifacts": "napi artifacts",
37
+ "prepublishOnly": "napi prepublish -t npm",
38
+ "version": "napi version",
39
+ "test": "node --test tests/*.mjs"
40
+ },
41
+ "devDependencies": {
42
+ "@napi-rs/cli": "^2.18.0",
43
+ "node-forge": "^1.4.0"
44
+ },
45
+ "engines": {
46
+ "node": ">=16"
47
+ },
48
+ "dependencies": {
49
+ "jose": "^6.2.3"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public",
53
+ "registry": "https://registry.npmjs.org/"
54
+ },
55
+ "optionalDependencies": {
56
+ "@agentidentitytrustprotocol/aitp-darwin-x64": "0.2.0",
57
+ "@agentidentitytrustprotocol/aitp-darwin-arm64": "0.2.0",
58
+ "@agentidentitytrustprotocol/aitp-linux-x64-gnu": "0.2.0",
59
+ "@agentidentitytrustprotocol/aitp-linux-arm64-gnu": "0.2.0"
60
+ }
61
+ }