@adastracomputing/ink 0.3.0 → 0.4.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.
- package/CHANGELOG.md +52 -0
- package/README.md +10 -1
- package/bin/verify-inclusion-impl.mjs +65 -22
- package/dist/audit/inclusion-receipt.d.ts +18 -8
- package/dist/audit/inclusion-receipt.js +29 -5
- package/dist/crypto/ink.js +12 -4
- package/dist/crypto/keys.d.ts +19 -0
- package/dist/crypto/keys.js +39 -0
- package/dist/crypto/sign.d.ts +21 -0
- package/dist/crypto/sign.js +26 -2
- package/dist/discovery/agent-card.d.ts +9 -7
- package/dist/index.d.ts +3 -3
- package/dist/index.js +5 -5
- package/dist/ink/checkpoint.d.ts +21 -0
- package/dist/ink/checkpoint.js +79 -0
- package/dist/ink/discovery-gating.js +4 -4
- package/dist/ink/receipts.d.ts +33 -1
- package/dist/ink/receipts.js +45 -1
- package/dist/middleware/ink-auth.d.ts +1 -0
- package/dist/middleware/ink-auth.js +7 -4
- package/dist/models/agent-card.js +22 -22
- package/dist/models/ink-audit.js +40 -36
- package/dist/models/ink-handshake.js +13 -13
- package/dist/models/intent.d.ts +2 -2
- package/dist/models/intent.js +9 -0
- package/docs/maturity.md +7 -1
- package/package.json +8 -7
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,58 @@ All notable changes to INK are recorded
|
|
|
4
4
|
here. Pre-1.0 releases follow `0.Y.Z` semantics, see
|
|
5
5
|
[`docs/maturity.md`](docs/maturity.md) for the versioning policy.
|
|
6
6
|
|
|
7
|
+
## 0.4.0, stricter verification, message-size bounds, checkpoint and receipt verification
|
|
8
|
+
|
|
9
|
+
This release tightens signature verification and input validation and adds
|
|
10
|
+
several verification helpers. It is published on the `next` dist-tag.
|
|
11
|
+
|
|
12
|
+
### Potentially breaking validation tightenings
|
|
13
|
+
|
|
14
|
+
These reject inputs that `0.3.0` accepted. Legitimate signer and receiver
|
|
15
|
+
traffic is unaffected; the rejected inputs are malformed, malicious, or outside
|
|
16
|
+
the documented profile.
|
|
17
|
+
|
|
18
|
+
- Ed25519 signatures are now verified in strict RFC 8032 mode at every
|
|
19
|
+
verification site. Small-order public keys and non-canonical point encodings
|
|
20
|
+
are rejected.
|
|
21
|
+
- Signed JSON numbers are constrained to the forms every canonicalizer
|
|
22
|
+
serializes identically: non-finite values, negative zero, and values whose
|
|
23
|
+
shortest form uses exponential notation are rejected at signing and
|
|
24
|
+
verification.
|
|
25
|
+
- The agent card, audit, handshake, and discovery schemas now enforce maximum
|
|
26
|
+
field lengths and array sizes.
|
|
27
|
+
- The `Authorization: INK-Ed25519` header is matched against single literal
|
|
28
|
+
spaces; a tab, carriage return, or line feed in the separator is rejected.
|
|
29
|
+
|
|
30
|
+
### Additions
|
|
31
|
+
|
|
32
|
+
- `verifyCheckpoint(signed, witnessPublicKey, expectedOrigin)` verifies a signed
|
|
33
|
+
C2SP checkpoint: the witness Ed25519 signature over the checkpoint body and the
|
|
34
|
+
log origin. A checkpoint used for the inclusion-receipt cross-check must be
|
|
35
|
+
verified this way first.
|
|
36
|
+
- `verifyReceipt({ receipt, senderPublicKey, expected })` binds a delivery
|
|
37
|
+
receipt to the exact message it acknowledges: issuer key, `from`/`to`/
|
|
38
|
+
`messageId`, the recomputed message hash, and an optional `disposition`.
|
|
39
|
+
- `verifyInclusionReceipt` accepts an `event` option that recomputes the leaf
|
|
40
|
+
hash and binds it to `receipt.eventId`. The legacy `eventHash` is retained but
|
|
41
|
+
does not provide that binding.
|
|
42
|
+
- `verifyInkAuth` returns a prefix-independent `principal` alongside the raw
|
|
43
|
+
sender id; per-sender security state (blocks, rate limits) should key on
|
|
44
|
+
`principal`. `canonicalAgentPrincipal(agentId)` is exported for the same use.
|
|
45
|
+
|
|
46
|
+
Per the pre-1.0 policy this release publishes under the `next` dist-tag; `latest`
|
|
47
|
+
is unchanged.
|
|
48
|
+
|
|
49
|
+
## 0.3.0, accept the ink: agentId alias for key extraction
|
|
50
|
+
|
|
51
|
+
`extractPublicKeyFromAgentId` now accepts either the canonical `tulpa:` prefix or the `ink:` alias introduced in ink/0.4. Both carry the identical multibase Ed25519 key, so the bootstrap verification key is byte-identical and a signature made with that key verifies regardless of which accepted prefix carried it. The prefix is identity syntax, not signing authority.
|
|
52
|
+
|
|
53
|
+
Emission is unchanged: `deriveAgentId` still returns `tulpa:` (accept both, emit one). The new `AGENT_ID_KEY_PREFIXES` export is frozen so a consumer cannot widen the accepted set at runtime. The change is additive and backward compatible. Existing `tulpa:` inputs behave exactly as before, and every previously rejected prefix other than `ink:` is still rejected. The wire protocol version is unchanged.
|
|
54
|
+
|
|
55
|
+
A receiver that keys per-sender security state (blocks, rate limits, duplicate-payload checks, cached verification keys, connection identity) MUST collapse the two spellings to one prefix-independent principal so a sender cannot switch prefix to dodge a block or split a rate-limit window. See [Identity](https://ink.tulpa.network/spec/identity/).
|
|
56
|
+
|
|
57
|
+
Per the pre-1.0 policy this release publishes under the `next` dist-tag.
|
|
58
|
+
|
|
7
59
|
## 0.2.0, version-keyed body-signature domain
|
|
8
60
|
|
|
9
61
|
Version-keyed body-signature domain. The body message signature is now domain-separated by protocol version. ink/0.1 messages, and any object with no explicit ink/0.2 protocol, keep the legacy `tulpa/sign` domain so every signature produced to date still verifies. ink/0.2 messages are signed and verified under the neutral `ink/sign` domain. The verifier selects exactly one domain from the signed `protocol` field and never tries an alternate, so a signature made under one version's domain cannot be replayed under another.
|
package/README.md
CHANGED
|
@@ -98,6 +98,13 @@ For consumers of bilateral audit-exchange responses (`network.tulpa.audit_respon
|
|
|
98
98
|
|
|
99
99
|
For consumers of witness audit-query responses (`network.tulpa.audit_query_response`, Auditability §7.3, added in `0.1.0-alpha.3`), call `verifyAuditQueryResponse({response, witnessPublicKey, expectedRequester, expectedMessageId, verifyEventSignature, expectedServiceDid?, laterCheckpoint?})`. The `verifyEventSignature` callback is REQUIRED: it resolves the submitting agent's Ed25519 keys (typically via Agent Card §2) and validates each event's `agentSignature`. Without it, the verifier refuses to return valid, because Merkle inclusion alone does not prove a real agent produced the event (§7.5). The verifier enforces envelope shape, the `requester` binding (prevents cross-requester replay), events/proofs strict one-to-one alignment, the §7.4 per-event scope rule, walks every Merkle proof via `computeAuditMerkleLeafHash` up to the response's `rootHash`, runs `verifyEventSignature` on every event and supports an optional later-checkpoint cross-check. The lower-level `verifyAuditQueryResponseSignature` is signature-only and is not sufficient to accept a witness response on its own.
|
|
100
100
|
|
|
101
|
+
Verification helpers added in `0.4.0`:
|
|
102
|
+
|
|
103
|
+
- `verifyCheckpoint(signed, witnessPublicKey, expectedOrigin)` verifies a signed C2SP checkpoint's witness Ed25519 signature and binds its log origin, returning the parsed `{origin, treeSize, rootHash}` or `null`. Any checkpoint passed to `verifyInclusionReceipt`'s `laterCheckpoint` cross-check must be verified this way first; an unverified checkpoint body is attacker-controllable and provides no anti-rollback value.
|
|
104
|
+
- `verifyReceipt({receipt, senderPublicKey, expected})` verifies a delivery receipt against the message it acknowledges: the issuer's signature plus `from`/`to`/`messageId`, the recomputed message hash, and an optional `disposition`. It returns `{valid, reason?}`.
|
|
105
|
+
- `verifyInclusionReceipt` accepts an `event` option that recomputes the leaf hash and binds `event.id` to `receipt.eventId`, so the proof attests the named event's inclusion. Prefer it over the legacy unbound `eventHash`.
|
|
106
|
+
- `verifyInkAuth` returns a prefix-independent `principal` alongside the raw `senderAgentId`. Per-sender security state (block lists, rate limits) MUST key on `principal`, because the `tulpa:` and `ink:` spellings of one key are the same actor; `canonicalAgentPrincipal(agentId)` exposes the same mapping.
|
|
107
|
+
|
|
101
108
|
## Agent-assisted implementation
|
|
102
109
|
|
|
103
110
|
If you are asking an AI coding agent to add INK support to an existing service, the canonical packet for that workflow is the [Agent-assisted implementation](https://ink.tulpa.network/guides/agent-assisted-implementation/) guide. It contains the curated implementer prompt, a mandatory traceability matrix, the conformance checklist, and a human-review checklist. The guide is updated as the protocol evolves; treat it as the live source rather than copying its contents into your repo.
|
|
@@ -152,9 +159,11 @@ Subject to change before v1.0:
|
|
|
152
159
|
|
|
153
160
|
You will see `network.tulpa.*` on the wire (e.g. `network.tulpa.intent`) and `ink.tulpa.network` for the docs site. Both are historical artifacts of the protocol's origin and do not imply a runtime dependency on Tulpa. A vendor-neutral namespace may be introduced in a future revision.
|
|
154
161
|
|
|
162
|
+
As a first, non-breaking step in that direction, agentIds may use either the canonical `tulpa:` method prefix or the `ink:` alias; both encode the same Ed25519 key and denote the same actor. `deriveAgentId` still emits `tulpa:`, and `extractPublicKeyFromAgentId` accepts both (accept-both, emit-one). A receiver MUST collapse the two spellings to one prefix-independent principal for all per-sender security state (blocks, rate limits, duplicate-payload checks, cached keys, connection identity).
|
|
163
|
+
|
|
155
164
|
## Relationship to Tulpa
|
|
156
165
|
|
|
157
|
-
INK is developed by [Ad Astra Computing](https://adastracomputing.com) as the underlying protocol for [Tulpa](https://tulpa.network). The spec and the library in this repo are deliberately free of Tulpa product code so other agent platforms can adopt INK without inheriting Tulpa's surface area. Tulpa's product integration (message orchestration, marketplace, user-facing APIs) lives in a separate
|
|
166
|
+
INK is developed by [Ad Astra Computing](https://adastracomputing.com) as the underlying protocol for [Tulpa](https://tulpa.network). The spec and the library in this repo are deliberately free of Tulpa product code so other agent platforms can adopt INK without inheriting Tulpa's surface area. Tulpa's product integration (message orchestration, marketplace, user-facing APIs) lives in a separate codebase.
|
|
158
167
|
|
|
159
168
|
## Interoperability
|
|
160
169
|
|
|
@@ -38,6 +38,7 @@ function parseArgs(argv) {
|
|
|
38
38
|
const a = argv[i];
|
|
39
39
|
if (a === "--file" || a === "-f") out.file = argv[++i];
|
|
40
40
|
else if (a === "--witness" || a === "-w") out.witness = argv[++i];
|
|
41
|
+
else if (a === "--origin") out.origin = argv[++i];
|
|
41
42
|
else if (a === "--event-hash" || a === "-e") out.eventHash = argv[++i];
|
|
42
43
|
else if (a === "--allow-http") out.allowHttp = true;
|
|
43
44
|
else if (a === "--help" || a === "-h") out.help = true;
|
|
@@ -75,6 +76,10 @@ Usage:
|
|
|
75
76
|
|
|
76
77
|
Options:
|
|
77
78
|
-w, --witness <url> Witness base URL (e.g. https://witness.tulpa.network)
|
|
79
|
+
--origin <name> Optional. Expected checkpoint origin (log identity)
|
|
80
|
+
to bind the signed checkpoint to. When omitted, the
|
|
81
|
+
checkpoint signature is still verified against the
|
|
82
|
+
witness key and the body/signature origins must agree.
|
|
78
83
|
-f, --file <path> Receipt JSON file. Omit to read from stdin.
|
|
79
84
|
-e, --event-hash <hex> Optional. RFC 6962 leaf hash for the audit event:
|
|
80
85
|
SHA-256(0x00 || JCS(event-without-agentSignature)),
|
|
@@ -226,7 +231,8 @@ async function verifyReceipt(receipt, witnessPublicKey, eventHash, laterCheckpoi
|
|
|
226
231
|
let sigValid = false;
|
|
227
232
|
try {
|
|
228
233
|
const sig = base64urlDecode(receipt.serviceSignature);
|
|
229
|
-
|
|
234
|
+
// RFC 8032 strict verification, matching the library (reject small-order keys).
|
|
235
|
+
sigValid = await ed.verifyAsync(sig, new TextEncoder().encode(sigBase), witnessPublicKey, { zip215: false });
|
|
230
236
|
} catch (e) {
|
|
231
237
|
steps.push({ name: "signature", pass: false, detail: e instanceof Error ? e.message : "signature decode failed" });
|
|
232
238
|
return { valid: false, steps };
|
|
@@ -343,28 +349,59 @@ async function fetchWitnessPublicKey(witnessUrl) {
|
|
|
343
349
|
}
|
|
344
350
|
|
|
345
351
|
/**
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
+
* Verify a signed C2SP tlog-checkpoint and return { treeSize, rootHash,
|
|
353
|
+
* origin }, or null if the signature, origin, or format is invalid. The
|
|
354
|
+
* Ed25519 signature covers the body bytes `<origin>\n<treeSize>\n<rootHash>`
|
|
355
|
+
* (no trailing newline). The anti-rollback cross-check below only means
|
|
356
|
+
* anything against a checkpoint whose signature we have verified against the
|
|
357
|
+
* witness key, so this MUST verify, not just parse.
|
|
358
|
+
*
|
|
359
|
+
* Mirrors verifyCheckpoint() in src/ink/checkpoint.ts — keep them in sync.
|
|
352
360
|
*/
|
|
353
|
-
function
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const
|
|
357
|
-
|
|
361
|
+
async function verifyCheckpointBody(signed, witnessPublicKey, expectedOrigin) {
|
|
362
|
+
if (typeof signed !== "string" || signed.length === 0 || signed.length > 4096) return null;
|
|
363
|
+
const SEP = "\n\n-- ";
|
|
364
|
+
const idx = signed.indexOf(SEP);
|
|
365
|
+
if (idx < 0) return null;
|
|
366
|
+
const body = signed.slice(0, idx);
|
|
367
|
+
const lines = body.split("\n");
|
|
358
368
|
if (lines.length !== 3) return null;
|
|
359
|
-
|
|
360
|
-
if (
|
|
361
|
-
|
|
369
|
+
const [origin, sizeLine, rootHash] = lines;
|
|
370
|
+
if (!origin || origin.length > 256) return null;
|
|
371
|
+
if (!/^\d+$/.test(sizeLine)) return null;
|
|
372
|
+
const treeSize = parseInt(sizeLine, 10);
|
|
362
373
|
if (!Number.isInteger(treeSize) || treeSize < 0 || treeSize > Number.MAX_SAFE_INTEGER) return null;
|
|
363
|
-
if (!/^[0-9a-f]{64}$/.test(
|
|
364
|
-
|
|
374
|
+
if (!/^[0-9a-f]{64}$/.test(rootHash)) return null;
|
|
375
|
+
// The expected origin must be supplied by the caller (a trusted value), not
|
|
376
|
+
// taken from the checkpoint body, so a witness key that signs several origins
|
|
377
|
+
// cannot substitute a checkpoint for a different log than the receipt's.
|
|
378
|
+
if (typeof expectedOrigin !== "string" || expectedOrigin.length === 0) return null;
|
|
379
|
+
if (origin !== expectedOrigin) return null;
|
|
380
|
+
const sigLines = signed.slice(idx + 2).split("\n").filter((l) => l.length > 0);
|
|
381
|
+
if (sigLines.length === 0 || sigLines.length > 8) return null;
|
|
382
|
+
const bodyBytes = new TextEncoder().encode(body);
|
|
383
|
+
for (const line of sigLines) {
|
|
384
|
+
if (!line.startsWith("-- ")) return null;
|
|
385
|
+
const rest = line.slice(3);
|
|
386
|
+
const sp = rest.indexOf(" ");
|
|
387
|
+
if (sp < 0) return null;
|
|
388
|
+
if (rest.slice(0, sp) !== expectedOrigin) continue;
|
|
389
|
+
try {
|
|
390
|
+
const sig = base64urlDecode(rest.slice(sp + 1));
|
|
391
|
+
if (sig.length !== 64) return null;
|
|
392
|
+
const ok = await ed.verifyAsync(sig, bodyBytes, witnessPublicKey, { zip215: false });
|
|
393
|
+
return ok ? { treeSize, rootHash, origin } : null;
|
|
394
|
+
} catch {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return null;
|
|
365
399
|
}
|
|
366
400
|
|
|
367
|
-
async function fetchCurrentCheckpoint(witnessUrl) {
|
|
401
|
+
async function fetchCurrentCheckpoint(witnessUrl, witnessPublicKey, expectedOrigin) {
|
|
402
|
+
// No trusted origin, no cross-check: refuse to trust the checkpoint body's
|
|
403
|
+
// self-asserted origin. Pass --origin <witness-origin> to enable it.
|
|
404
|
+
if (typeof expectedOrigin !== "string" || expectedOrigin.length === 0) return null;
|
|
368
405
|
const url = `${witnessUrl.replace(/\/$/, "")}/ink/v1/checkpoint`;
|
|
369
406
|
let body;
|
|
370
407
|
try {
|
|
@@ -374,7 +411,7 @@ async function fetchCurrentCheckpoint(witnessUrl) {
|
|
|
374
411
|
// 'not available' rather than crashing the verifier.
|
|
375
412
|
return null;
|
|
376
413
|
}
|
|
377
|
-
return
|
|
414
|
+
return verifyCheckpointBody(body, witnessPublicKey, expectedOrigin);
|
|
378
415
|
}
|
|
379
416
|
|
|
380
417
|
async function readStdin() {
|
|
@@ -451,16 +488,22 @@ async function main() {
|
|
|
451
488
|
process.exit(2);
|
|
452
489
|
}
|
|
453
490
|
|
|
454
|
-
|
|
491
|
+
// The checkpoint cross-check only carries weight against a checkpoint whose
|
|
492
|
+
// Ed25519 signature we have verified against the witness key. An unverified
|
|
493
|
+
// checkpoint (bad/absent signature, or origin mismatch) is dropped so the
|
|
494
|
+
// cross-check is skipped rather than trusting attacker-controlled values.
|
|
495
|
+
const laterCheckpoint = await fetchCurrentCheckpoint(witnessBase, witnessPublicKey, args.origin);
|
|
455
496
|
|
|
456
497
|
const result = await verifyReceipt(receipt, witnessPublicKey, args.eventHash, laterCheckpoint ?? undefined);
|
|
457
498
|
|
|
458
499
|
console.log(`Receipt: eventId=${receipt?.eventId} leafIndex=${receipt?.leafIndex} treeSize=${receipt?.treeSize}`);
|
|
459
500
|
console.log(`Witness: ${witnessBase}`);
|
|
460
501
|
if (laterCheckpoint) {
|
|
461
|
-
console.log(`Current checkpoint: treeSize=${laterCheckpoint.treeSize} rootHash=${laterCheckpoint.rootHash}`);
|
|
502
|
+
console.log(`Current checkpoint (signature verified): treeSize=${laterCheckpoint.treeSize} rootHash=${laterCheckpoint.rootHash}`);
|
|
503
|
+
} else if (!args.origin) {
|
|
504
|
+
console.log("Current checkpoint: cross-check skipped (pass --origin <witness-origin> to enable the anti-rollback check)");
|
|
462
505
|
} else {
|
|
463
|
-
console.log("Current checkpoint: not available (skipping checkpoint cross-check)");
|
|
506
|
+
console.log("Current checkpoint: not available or signature unverified (skipping checkpoint cross-check)");
|
|
464
507
|
}
|
|
465
508
|
console.log("");
|
|
466
509
|
for (const step of result.steps) {
|
|
@@ -26,21 +26,31 @@ export interface InclusionReceiptVerifyResult {
|
|
|
26
26
|
* - Service signature verification against `witnessPublicKey`
|
|
27
27
|
*
|
|
28
28
|
* Optionally performs (when the corresponding input is provided):
|
|
29
|
-
* - Leaf-to-root proof walk
|
|
29
|
+
* - Leaf-to-root proof walk: pass `event` (recommended — recomputes the leaf
|
|
30
|
+
* hash and binds it to `receipt.eventId`) or `eventHash` (legacy, unbound)
|
|
30
31
|
* - Cross-check against a later signed checkpoint (`laterCheckpoint`)
|
|
31
32
|
*/
|
|
32
33
|
export declare function verifyInclusionReceipt(opts: {
|
|
33
34
|
receipt: InclusionReceipt;
|
|
34
35
|
/** Raw 32-byte Ed25519 public key of the witness service. */
|
|
35
36
|
witnessPublicKey: Uint8Array;
|
|
36
|
-
/** Optional
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
37
|
+
/** Optional audit event the receipt claims inclusion for. This is the
|
|
38
|
+
* RECOMMENDED way to verify the proof: the leaf hash is recomputed from the
|
|
39
|
+
* event with `computeAuditMerkleLeafHash`, and `event.id` is bound to
|
|
40
|
+
* `receipt.eventId`, so the proof attests that the event named by the
|
|
41
|
+
* receipt is in the tree — not merely that some caller-chosen hash is. */
|
|
42
|
+
event?: Record<string, unknown>;
|
|
43
|
+
/** Optional pre-computed RFC 6962 leaf hash (hex). LEGACY / lower-assurance:
|
|
44
|
+
* unlike `event`, a bare hash is NOT bound to `receipt.eventId`, so the proof
|
|
45
|
+
* only attests "this hash is in the tree", not "the event the receipt names
|
|
46
|
+
* is in the tree". Prefer `event`. Ignored when `event` is provided. */
|
|
40
47
|
eventHash?: string;
|
|
41
|
-
/** Optional later checkpoint to cross-check the receipt against.
|
|
42
|
-
*
|
|
43
|
-
* has
|
|
48
|
+
/** Optional later checkpoint to cross-check the receipt against. This MUST be
|
|
49
|
+
* the parsed body of a checkpoint whose Ed25519 signature and origin the
|
|
50
|
+
* caller has already verified with `verifyCheckpoint` against the witness
|
|
51
|
+
* key. Passing an unverified (merely parsed) checkpoint gives the
|
|
52
|
+
* anti-rollback / fork cross-check no security, because the treeSize and
|
|
53
|
+
* rootHash would then be attacker-controllable. */
|
|
44
54
|
laterCheckpoint?: {
|
|
45
55
|
treeSize: number;
|
|
46
56
|
rootHash: string;
|
|
@@ -30,12 +30,13 @@ import { base64urlDecode, jcsCanonicalize, hexToBytes, bytesToHex, computeAuditM
|
|
|
30
30
|
* - Service signature verification against `witnessPublicKey`
|
|
31
31
|
*
|
|
32
32
|
* Optionally performs (when the corresponding input is provided):
|
|
33
|
-
* - Leaf-to-root proof walk
|
|
33
|
+
* - Leaf-to-root proof walk: pass `event` (recommended — recomputes the leaf
|
|
34
|
+
* hash and binds it to `receipt.eventId`) or `eventHash` (legacy, unbound)
|
|
34
35
|
* - Cross-check against a later signed checkpoint (`laterCheckpoint`)
|
|
35
36
|
*/
|
|
36
37
|
export async function verifyInclusionReceipt(opts) {
|
|
37
38
|
const steps = [];
|
|
38
|
-
const { receipt, witnessPublicKey, eventHash, laterCheckpoint } = opts;
|
|
39
|
+
const { receipt, witnessPublicKey, event, eventHash, laterCheckpoint } = opts;
|
|
39
40
|
// ── Step 1: structural validation ──
|
|
40
41
|
const structuralProblem = checkReceiptShape(receipt);
|
|
41
42
|
if (structuralProblem) {
|
|
@@ -55,7 +56,7 @@ export async function verifyInclusionReceipt(opts) {
|
|
|
55
56
|
let sigValid = false;
|
|
56
57
|
try {
|
|
57
58
|
const sig = base64urlDecode(receipt.serviceSignature);
|
|
58
|
-
sigValid = await ed.verifyAsync(sig, new TextEncoder().encode(sigBase), witnessPublicKey);
|
|
59
|
+
sigValid = await ed.verifyAsync(sig, new TextEncoder().encode(sigBase), witnessPublicKey, { zip215: false });
|
|
59
60
|
}
|
|
60
61
|
catch (e) {
|
|
61
62
|
steps.push({
|
|
@@ -71,12 +72,35 @@ export async function verifyInclusionReceipt(opts) {
|
|
|
71
72
|
}
|
|
72
73
|
steps.push({ name: "signature", pass: true });
|
|
73
74
|
// ── Step 3: inclusion-proof walk (optional) ──
|
|
74
|
-
|
|
75
|
+
// Prefer the `event` path: recompute the leaf hash from the event and bind it
|
|
76
|
+
// to receipt.eventId, so the proof attests the named event's inclusion.
|
|
77
|
+
let leafHash;
|
|
78
|
+
if (event !== undefined) {
|
|
79
|
+
if (typeof event.id !== "string") {
|
|
80
|
+
steps.push({ name: "proof", pass: false, detail: "event.id is missing or not a string" });
|
|
81
|
+
return { valid: false, steps };
|
|
82
|
+
}
|
|
83
|
+
if (event.id !== receipt.eventId) {
|
|
84
|
+
steps.push({ name: "proof", pass: false, detail: "event.id does not match receipt.eventId" });
|
|
85
|
+
return { valid: false, steps };
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
leafHash = await computeAuditMerkleLeafHash(event);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
steps.push({ name: "proof", pass: false, detail: "could not compute leaf hash from event" });
|
|
92
|
+
return { valid: false, steps };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else if (eventHash !== undefined) {
|
|
75
96
|
if (!/^[0-9a-f]{64}$/.test(eventHash)) {
|
|
76
97
|
steps.push({ name: "proof", pass: false, detail: "eventHash must be 64 lowercase hex chars" });
|
|
77
98
|
return { valid: false, steps };
|
|
78
99
|
}
|
|
79
|
-
|
|
100
|
+
leafHash = eventHash;
|
|
101
|
+
}
|
|
102
|
+
if (leafHash !== undefined) {
|
|
103
|
+
const verified = await verifyInclusionProof(leafHash, receipt.inclusionProof, receipt.leafIndex, receipt.treeSize, receipt.rootHash);
|
|
80
104
|
if (!verified) {
|
|
81
105
|
steps.push({ name: "proof", pass: false, detail: "leaf-to-root walk did not reach claimed rootHash" });
|
|
82
106
|
return { valid: false, steps };
|
package/dist/crypto/ink.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as ed from "@noble/ed25519";
|
|
2
2
|
import { x25519 } from "@noble/curves/ed25519.js";
|
|
3
3
|
import canonicalize from "canonicalize";
|
|
4
|
+
import { isJcsSafeNumber } from "./sign.js";
|
|
4
5
|
// ── Encoding helpers ──
|
|
5
6
|
const MAX_ENCODE_INPUT_BYTES = 2_000_000;
|
|
6
7
|
function base64urlEncode(bytes) {
|
|
@@ -137,6 +138,11 @@ function isWithinCanonicalizeBounds(value) {
|
|
|
137
138
|
if (chars > MAX_PRECHECK_CHARS)
|
|
138
139
|
return false;
|
|
139
140
|
}
|
|
141
|
+
else if (typeof v === "number" && !isJcsSafeNumber(v)) {
|
|
142
|
+
// Reject numbers that don't canonicalize identically across JSON
|
|
143
|
+
// serializers (non-finite, -0, exponential notation). See sign.ts.
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
140
146
|
return true;
|
|
141
147
|
}
|
|
142
148
|
if (Array.isArray(v)) {
|
|
@@ -236,7 +242,9 @@ export async function verifyInkSignature(input, signatureBase64url, publicKey) {
|
|
|
236
242
|
const bytes = new TextEncoder().encode(sigBase);
|
|
237
243
|
try {
|
|
238
244
|
const sig = base64urlDecode(signatureBase64url);
|
|
239
|
-
|
|
245
|
+
// RFC 8032 strict verification (not the default ZIP-215): reject
|
|
246
|
+
// small-order keys and non-canonical encodings. See verifyMessage.
|
|
247
|
+
return await ed.verifyAsync(sig, bytes, publicKey, { zip215: false });
|
|
240
248
|
}
|
|
241
249
|
catch {
|
|
242
250
|
return false;
|
|
@@ -629,7 +637,7 @@ export async function verifyAuditEventSignature(event, publicKey) {
|
|
|
629
637
|
if (bytes.length > MAX_SIGBASE_BODY_BYTES)
|
|
630
638
|
return false;
|
|
631
639
|
const sig = base64urlDecode(signature);
|
|
632
|
-
return await ed.verifyAsync(sig, bytes, publicKey);
|
|
640
|
+
return await ed.verifyAsync(sig, bytes, publicKey, { zip215: false });
|
|
633
641
|
}
|
|
634
642
|
catch {
|
|
635
643
|
return false;
|
|
@@ -739,7 +747,7 @@ export async function verifyAuditResponseSignature(events, signature, publicKey)
|
|
|
739
747
|
if (bytes.length > MAX_SIGBASE_BODY_BYTES)
|
|
740
748
|
return false;
|
|
741
749
|
const sig = base64urlDecode(signature);
|
|
742
|
-
return await ed.verifyAsync(sig, bytes, publicKey);
|
|
750
|
+
return await ed.verifyAsync(sig, bytes, publicKey, { zip215: false });
|
|
743
751
|
}
|
|
744
752
|
catch {
|
|
745
753
|
return false;
|
|
@@ -905,7 +913,7 @@ export async function verifyAuditQueryResponseSignature(responseWithoutSignature
|
|
|
905
913
|
if (bytes.length > MAX_SIGBASE_BODY_BYTES)
|
|
906
914
|
return false;
|
|
907
915
|
const sig = base64urlDecode(signature);
|
|
908
|
-
return await ed.verifyAsync(sig, bytes, publicKey);
|
|
916
|
+
return await ed.verifyAsync(sig, bytes, publicKey, { zip215: false });
|
|
909
917
|
}
|
|
910
918
|
catch {
|
|
911
919
|
return false;
|
package/dist/crypto/keys.d.ts
CHANGED
|
@@ -53,3 +53,22 @@ export declare function deriveAgentId(publicKey: Uint8Array): string;
|
|
|
53
53
|
* same way for both, so a malformed tail is rejected identically.
|
|
54
54
|
*/
|
|
55
55
|
export declare function extractPublicKeyFromAgentId(agentId: string): Uint8Array;
|
|
56
|
+
/**
|
|
57
|
+
* Collapse an agent ID to a single, prefix-independent principal string that
|
|
58
|
+
* per-sender security state (block lists, rate limits, duplicate-payload
|
|
59
|
+
* checks, cached verification keys, connection identity) MUST key on.
|
|
60
|
+
*
|
|
61
|
+
* The accepted spellings `tulpa:zKEY` and `ink:zKEY` encode the same Ed25519
|
|
62
|
+
* key and are therefore the same actor; this maps both — and any non-canonical
|
|
63
|
+
* multibase encoding of that key — to `key:<canonical-multibase>`, so a sender
|
|
64
|
+
* cannot switch prefix or re-encode to dodge a block or split a rate-limit
|
|
65
|
+
* window. DIDs (and any other identifier) are returned unchanged. A raw `key:`
|
|
66
|
+
* input — never a legitimate agent ID — is escaped to `raw:key:…` so a sender
|
|
67
|
+
* cannot forge a collision with a canonicalized key principal.
|
|
68
|
+
*
|
|
69
|
+
* Not idempotent: call exactly once, at the storage boundary, on the raw
|
|
70
|
+
* agent ID. Total over well-formed string input (it never throws on a
|
|
71
|
+
* malformed key body — that is escaped to `raw:…` so a principal is always
|
|
72
|
+
* derivable); throws only on a non-string, empty, or over-length argument.
|
|
73
|
+
*/
|
|
74
|
+
export declare function canonicalAgentPrincipal(agentId: string): string;
|
package/dist/crypto/keys.js
CHANGED
|
@@ -190,3 +190,42 @@ export function extractPublicKeyFromAgentId(agentId) {
|
|
|
190
190
|
}
|
|
191
191
|
return decodePublicKeyMultibase(agentId.slice(prefix.length));
|
|
192
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Collapse an agent ID to a single, prefix-independent principal string that
|
|
195
|
+
* per-sender security state (block lists, rate limits, duplicate-payload
|
|
196
|
+
* checks, cached verification keys, connection identity) MUST key on.
|
|
197
|
+
*
|
|
198
|
+
* The accepted spellings `tulpa:zKEY` and `ink:zKEY` encode the same Ed25519
|
|
199
|
+
* key and are therefore the same actor; this maps both — and any non-canonical
|
|
200
|
+
* multibase encoding of that key — to `key:<canonical-multibase>`, so a sender
|
|
201
|
+
* cannot switch prefix or re-encode to dodge a block or split a rate-limit
|
|
202
|
+
* window. DIDs (and any other identifier) are returned unchanged. A raw `key:`
|
|
203
|
+
* input — never a legitimate agent ID — is escaped to `raw:key:…` so a sender
|
|
204
|
+
* cannot forge a collision with a canonicalized key principal.
|
|
205
|
+
*
|
|
206
|
+
* Not idempotent: call exactly once, at the storage boundary, on the raw
|
|
207
|
+
* agent ID. Total over well-formed string input (it never throws on a
|
|
208
|
+
* malformed key body — that is escaped to `raw:…` so a principal is always
|
|
209
|
+
* derivable); throws only on a non-string, empty, or over-length argument.
|
|
210
|
+
*/
|
|
211
|
+
export function canonicalAgentPrincipal(agentId) {
|
|
212
|
+
if (typeof agentId !== "string" || agentId.length === 0 || agentId.length > 512) {
|
|
213
|
+
throw new Error("Invalid agent ID");
|
|
214
|
+
}
|
|
215
|
+
const prefix = AGENT_ID_KEY_PREFIXES.find((p) => agentId.startsWith(p));
|
|
216
|
+
if (prefix) {
|
|
217
|
+
try {
|
|
218
|
+
return "key:" + encodePublicKeyMultibase(decodePublicKeyMultibase(agentId.slice(prefix.length)));
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Malformed multibase body: keep the function total by treating it as an
|
|
222
|
+
// opaque identifier. Such an ID cannot authenticate via the bootstrap
|
|
223
|
+
// path anyway, so it never collides with a real key principal.
|
|
224
|
+
return "raw:" + agentId;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (agentId.startsWith("key:")) {
|
|
228
|
+
return "raw:" + agentId;
|
|
229
|
+
}
|
|
230
|
+
return agentId;
|
|
231
|
+
}
|
package/dist/crypto/sign.d.ts
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A number is safe for canonical JSON only if every conforming canonicalizer
|
|
3
|
+
* serializes it identically. We reject non-finite values (not valid JSON),
|
|
4
|
+
* negative zero (serializes as `0`, losing the sign), and any value whose
|
|
5
|
+
* shortest decimal uses exponential notation (`1e21`, `1e-7`) — exponential
|
|
6
|
+
* forms are exactly where JSON serializers and strict RFC 8785 disagree.
|
|
7
|
+
* Rejecting them keeps the signed-byte representation unambiguous across
|
|
8
|
+
* implementations (the reference and a future second implementation), without
|
|
9
|
+
* affecting the small integers and plain decimals INK payloads actually carry.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isJcsSafeNumber(n: number): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Cheap depth/node/byte walk over a value before it is handed to
|
|
14
|
+
* `canonicalize`. Bails before the recursive sort+serialize runs, so an
|
|
15
|
+
* attacker who supplies a syntactically valid-shape signature with a
|
|
16
|
+
* pathological message body cannot burn CPU/memory inside the verify
|
|
17
|
+
* path. Mirrors src/crypto/ink.ts:isWithinCanonicalizeBounds, including
|
|
18
|
+
* the byte counter that stops a single huge string from sneaking past
|
|
19
|
+
* the node check.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isWithinBounds(value: unknown): boolean;
|
|
1
22
|
/**
|
|
2
23
|
* Sign a message object using Ed25519.
|
|
3
24
|
*
|
package/dist/crypto/sign.js
CHANGED
|
@@ -10,6 +10,23 @@ const MAX_MESSAGE_CHARS = 1_200_000;
|
|
|
10
10
|
* walk: a message can be small in node count but still expand to huge
|
|
11
11
|
* canonical bytes via long string values. */
|
|
12
12
|
const MAX_MESSAGE_CANONICAL_BYTES = 1_048_576;
|
|
13
|
+
/**
|
|
14
|
+
* A number is safe for canonical JSON only if every conforming canonicalizer
|
|
15
|
+
* serializes it identically. We reject non-finite values (not valid JSON),
|
|
16
|
+
* negative zero (serializes as `0`, losing the sign), and any value whose
|
|
17
|
+
* shortest decimal uses exponential notation (`1e21`, `1e-7`) — exponential
|
|
18
|
+
* forms are exactly where JSON serializers and strict RFC 8785 disagree.
|
|
19
|
+
* Rejecting them keeps the signed-byte representation unambiguous across
|
|
20
|
+
* implementations (the reference and a future second implementation), without
|
|
21
|
+
* affecting the small integers and plain decimals INK payloads actually carry.
|
|
22
|
+
*/
|
|
23
|
+
export function isJcsSafeNumber(n) {
|
|
24
|
+
if (!Number.isFinite(n))
|
|
25
|
+
return false;
|
|
26
|
+
if (Object.is(n, -0))
|
|
27
|
+
return false;
|
|
28
|
+
return !/[eE]/.test(String(n));
|
|
29
|
+
}
|
|
13
30
|
/**
|
|
14
31
|
* Cheap depth/node/byte walk over a value before it is handed to
|
|
15
32
|
* `canonicalize`. Bails before the recursive sort+serialize runs, so an
|
|
@@ -19,7 +36,7 @@ const MAX_MESSAGE_CANONICAL_BYTES = 1_048_576;
|
|
|
19
36
|
* the byte counter that stops a single huge string from sneaking past
|
|
20
37
|
* the node check.
|
|
21
38
|
*/
|
|
22
|
-
function isWithinBounds(value) {
|
|
39
|
+
export function isWithinBounds(value) {
|
|
23
40
|
let nodes = 0;
|
|
24
41
|
let chars = 0;
|
|
25
42
|
function walk(v, depth) {
|
|
@@ -33,6 +50,9 @@ function isWithinBounds(value) {
|
|
|
33
50
|
if (chars > MAX_MESSAGE_CHARS)
|
|
34
51
|
return false;
|
|
35
52
|
}
|
|
53
|
+
else if (typeof v === "number" && !isJcsSafeNumber(v)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
36
56
|
return true;
|
|
37
57
|
}
|
|
38
58
|
if (Array.isArray(v)) {
|
|
@@ -162,7 +182,11 @@ export async function verifyMessage(message, publicKey) {
|
|
|
162
182
|
const prefixedBytes = new TextEncoder().encode(prefixed);
|
|
163
183
|
try {
|
|
164
184
|
const sig = base64urlDecode(signature);
|
|
165
|
-
|
|
185
|
+
// RFC 8032 strict verification, not the library default ZIP-215 mode:
|
|
186
|
+
// reject small-order public keys and non-canonical point encodings so a
|
|
187
|
+
// signature binds to exactly one (key, message). Identity is the embedded
|
|
188
|
+
// public key and signatures feed the audit log, so strictness is required.
|
|
189
|
+
return await ed.verifyAsync(sig, prefixedBytes, publicKey, { zip215: false });
|
|
166
190
|
}
|
|
167
191
|
catch {
|
|
168
192
|
// Malformed signature (invalid base64url, wrong byte length, bad key) — treat as invalid
|
|
@@ -24,13 +24,15 @@ export interface FetchAgentCardOptions {
|
|
|
24
24
|
* connect targets (e.g. undici with a custom dispatcher on Node, or
|
|
25
25
|
* `cf: { resolveOverride: validatedIp }` on Cloudflare Workers). */
|
|
26
26
|
fetch?: typeof fetch;
|
|
27
|
-
/** Strict mode: require that the caller supply `options.fetch
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
27
|
+
/** Strict mode: require that the caller supply `options.fetch`, returning
|
|
28
|
+
* null (without fetching) if it is absent. This only guarantees that *some*
|
|
29
|
+
* fetch override was provided; it does NOT and cannot verify that the
|
|
30
|
+
* override pins connect-time IPs, so passing `requireSafeFetch: true` with
|
|
31
|
+
* the plain global `fetch` does not close the DNS-rebinding window. The
|
|
32
|
+
* literal-private-IP allowlist this module applies to `baseUrl` does not stop
|
|
33
|
+
* a public hostname that resolves to a private address at fetch time; only a
|
|
34
|
+
* connect-time-IP-pinning `options.fetch` (for example a custom undici
|
|
35
|
+
* dispatcher) does. Off by default for backwards compatibility. */
|
|
34
36
|
requireSafeFetch?: boolean;
|
|
35
37
|
}
|
|
36
38
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
export { signInkMessage, verifyInkSignature, buildSignatureBase, buildAuthHeader, computeMessageHash, computeEventHash, computeAuditMerkleLeafHash, signAuditEvent, verifyAuditEventSignature, signAuditResponse, verifyAuditResponseSignature, verifyAuditEventChain, signAuditQueryResponse, verifyAuditQueryResponseSignature, encryptInkPayload, decryptInkPayload, checkReplay, base64urlEncode, base64urlDecode, hexToBytes, bytesToHex, jcsCanonicalize, MAX_TIMESTAMP_AGE_MS, MAX_FUTURE_TIMESTAMP_MS, } from "./crypto/ink.js";
|
|
2
2
|
export { signMessage, verifyMessage } from "./crypto/sign.js";
|
|
3
3
|
export { verifyInkSignatureWithKeys } from "./crypto/multi-key-verify.js";
|
|
4
|
-
export { generateKeypair, generateEncryptionKeypair, deriveAgentId, encodePublicKeyMultibase, encodeEncryptionKeyMultibase, decodePublicKeyMultibase, decodeEncryptionKeyMultibase, extractPublicKeyFromAgentId, AGENT_ID_KEY_PREFIXES, } from "./crypto/keys.js";
|
|
4
|
+
export { generateKeypair, generateEncryptionKeypair, deriveAgentId, encodePublicKeyMultibase, encodeEncryptionKeyMultibase, decodePublicKeyMultibase, decodeEncryptionKeyMultibase, extractPublicKeyFromAgentId, canonicalAgentPrincipal, AGENT_ID_KEY_PREFIXES, } from "./crypto/keys.js";
|
|
5
5
|
export { fetchAgentCard, extractCandidateKeys, resolveBaseUrl, } from "./discovery/agent-card.js";
|
|
6
6
|
export { verifyInkAuth, type NonceStore } from "./middleware/ink-auth.js";
|
|
7
7
|
export { verifyInclusionReceipt, verifyAuditQueryResponse, type InclusionReceipt, type InclusionReceiptVerifyResult, type AuditQueryResponse, type AuditQueryResponseVerifyResult, type VerifyStep, } from "./audit/inclusion-receipt.js";
|
|
8
8
|
export { HandshakeBudgetTracker } from "./ink/handshake-budget.js";
|
|
9
|
-
export { buildReceipt, shouldSendReceipt, sendReceiptFireAndForget, } from "./ink/receipts.js";
|
|
9
|
+
export { buildReceipt, verifyReceipt, shouldSendReceipt, sendReceiptFireAndForget, } from "./ink/receipts.js";
|
|
10
10
|
export { resolveEffectiveTransports, checkTransportAllowed, } from "./ink/transport-auth.js";
|
|
11
11
|
export { buildRedactedCard, shouldRedactOnGet, AgentCardQuerySchema, } from "./ink/discovery-gating.js";
|
|
12
|
-
export { parseCheckpoint, formatCheckpoint, } from "./ink/checkpoint.js";
|
|
12
|
+
export { parseCheckpoint, formatCheckpoint, verifyCheckpoint, } from "./ink/checkpoint.js";
|
|
13
13
|
export type { CheckpointData } from "./ink/checkpoint.js";
|
|
14
14
|
export { InkAuditEventTypeSchema, InkAuditEventSchema, InkAuditInclusionSchema, InkReceiptSchema, InkAuditQuerySchema, InkIntroductionReceiptSchema, } from "./models/ink-audit.js";
|
|
15
15
|
export type { InkAuditEventType, InkAuditEvent, InkAuditInclusion, InkReceipt, InkAuditQuery, InkAuditResponse, InkIntroductionReceiptStatus, } from "./models/ink-audit.js";
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
export { signInkMessage, verifyInkSignature, buildSignatureBase, buildAuthHeader, computeMessageHash, computeEventHash, computeAuditMerkleLeafHash, signAuditEvent, verifyAuditEventSignature, signAuditResponse, verifyAuditResponseSignature, verifyAuditEventChain, signAuditQueryResponse, verifyAuditQueryResponseSignature, encryptInkPayload, decryptInkPayload, checkReplay, base64urlEncode, base64urlDecode, hexToBytes, bytesToHex, jcsCanonicalize, MAX_TIMESTAMP_AGE_MS, MAX_FUTURE_TIMESTAMP_MS, } from "./crypto/ink.js";
|
|
5
5
|
export { signMessage, verifyMessage } from "./crypto/sign.js";
|
|
6
6
|
export { verifyInkSignatureWithKeys } from "./crypto/multi-key-verify.js";
|
|
7
|
-
export { generateKeypair, generateEncryptionKeypair, deriveAgentId, encodePublicKeyMultibase, encodeEncryptionKeyMultibase, decodePublicKeyMultibase, decodeEncryptionKeyMultibase, extractPublicKeyFromAgentId, AGENT_ID_KEY_PREFIXES, } from "./crypto/keys.js";
|
|
7
|
+
export { generateKeypair, generateEncryptionKeypair, deriveAgentId, encodePublicKeyMultibase, encodeEncryptionKeyMultibase, decodePublicKeyMultibase, decodeEncryptionKeyMultibase, extractPublicKeyFromAgentId, canonicalAgentPrincipal, AGENT_ID_KEY_PREFIXES, } from "./crypto/keys.js";
|
|
8
8
|
// Discovery: Agent Card fetch + candidate-key extraction
|
|
9
9
|
export { fetchAgentCard, extractCandidateKeys, resolveBaseUrl, } from "./discovery/agent-card.js";
|
|
10
10
|
// Middleware: transport-level INK auth
|
|
@@ -13,14 +13,14 @@ export { verifyInkAuth } from "./middleware/ink-auth.js";
|
|
|
13
13
|
export { verifyInclusionReceipt, verifyAuditQueryResponse, } from "./audit/inclusion-receipt.js";
|
|
14
14
|
// Optional containment / governance primitives
|
|
15
15
|
export { HandshakeBudgetTracker } from "./ink/handshake-budget.js";
|
|
16
|
-
// Receipts: build and send INK delivery receipts
|
|
17
|
-
export { buildReceipt, shouldSendReceipt, sendReceiptFireAndForget, } from "./ink/receipts.js";
|
|
16
|
+
// Receipts: build, verify, and send INK delivery receipts
|
|
17
|
+
export { buildReceipt, verifyReceipt, shouldSendReceipt, sendReceiptFireAndForget, } from "./ink/receipts.js";
|
|
18
18
|
// Transport-auth: token-level transport allowlist for extension tokens
|
|
19
19
|
export { resolveEffectiveTransports, checkTransportAllowed, } from "./ink/transport-auth.js";
|
|
20
20
|
// Discovery-gating: visibility-aware Agent Card redaction
|
|
21
21
|
export { buildRedactedCard, shouldRedactOnGet, AgentCardQuerySchema, } from "./ink/discovery-gating.js";
|
|
22
|
-
// Checkpoint parsing for transparency-log
|
|
23
|
-
export { parseCheckpoint, formatCheckpoint, } from "./ink/checkpoint.js";
|
|
22
|
+
// Checkpoint parsing and signature verification for transparency-log checkpoints
|
|
23
|
+
export { parseCheckpoint, formatCheckpoint, verifyCheckpoint, } from "./ink/checkpoint.js";
|
|
24
24
|
// Audit event schemas + types for receipts, query, inclusion proofs
|
|
25
25
|
export { InkAuditEventTypeSchema, InkAuditEventSchema, InkAuditInclusionSchema, InkReceiptSchema, InkAuditQuerySchema, InkIntroductionReceiptSchema, } from "./models/ink-audit.js";
|
|
26
26
|
// Handshake message schemas
|