agent-passport-system 7.2.0 → 8.0.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/README.md +4 -5
- package/SECURITY.md +2 -2
- package/dist/src/cli/index.js +2 -2
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/core/delegation.d.ts.map +1 -1
- package/dist/src/core/delegation.js +52 -3
- package/dist/src/core/delegation.js.map +1 -1
- package/dist/src/core/external-action-ref.d.ts +31 -0
- package/dist/src/core/external-action-ref.d.ts.map +1 -1
- package/dist/src/core/external-action-ref.js +251 -17
- package/dist/src/core/external-action-ref.js.map +1 -1
- package/dist/src/core/reputation-authority.d.ts +5 -2
- package/dist/src/core/reputation-authority.d.ts.map +1 -1
- package/dist/src/core/reputation-authority.js +6 -3
- package/dist/src/core/reputation-authority.js.map +1 -1
- package/dist/src/core-exports.d.ts +1 -0
- package/dist/src/core-exports.d.ts.map +1 -1
- package/dist/src/core-exports.js +1 -0
- package/dist/src/core-exports.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/types/reputation-authority.d.ts +12 -7
- package/dist/src/types/reputation-authority.d.ts.map +1 -1
- package/dist/src/v2/claim-verifier.d.ts +10 -0
- package/dist/src/v2/claim-verifier.d.ts.map +1 -1
- package/dist/src/v2/claim-verifier.js +10 -0
- package/dist/src/v2/claim-verifier.js.map +1 -1
- package/dist/src/v2/identity-binding/passport.d.ts.map +1 -1
- package/dist/src/v2/identity-binding/passport.js +28 -10
- package/dist/src/v2/identity-binding/passport.js.map +1 -1
- package/dist/src/v2/identity-binding/principal-binding.d.ts.map +1 -1
- package/dist/src/v2/identity-binding/principal-binding.js +28 -11
- package/dist/src/v2/identity-binding/principal-binding.js.map +1 -1
- package/dist/src/v2/identity-binding/profile-recognition.d.ts +25 -0
- package/dist/src/v2/identity-binding/profile-recognition.d.ts.map +1 -0
- package/dist/src/v2/identity-binding/profile-recognition.js +67 -0
- package/dist/src/v2/identity-binding/profile-recognition.js.map +1 -0
- package/dist/src/v2/identity-binding/resolution-outcome.d.ts +23 -0
- package/dist/src/v2/identity-binding/resolution-outcome.d.ts.map +1 -0
- package/dist/src/v2/identity-binding/resolution-outcome.js +69 -0
- package/dist/src/v2/identity-binding/resolution-outcome.js.map +1 -0
- package/dist/src/v2/identity-binding/revocation.d.ts.map +1 -1
- package/dist/src/v2/identity-binding/revocation.js +26 -10
- package/dist/src/v2/identity-binding/revocation.js.map +1 -1
- package/dist/src/v2/identity-binding/types.d.ts +9 -0
- package/dist/src/v2/identity-binding/types.d.ts.map +1 -1
- package/dist/src/v2/payment-rails/cycles/evidence-resolution.d.ts +5 -0
- package/dist/src/v2/payment-rails/cycles/evidence-resolution.d.ts.map +1 -1
- package/dist/src/v2/payment-rails/cycles/evidence-resolution.js +46 -10
- package/dist/src/v2/payment-rails/cycles/evidence-resolution.js.map +1 -1
- package/dist/src/v2/payment-rails/cycles/index.d.ts +14 -3
- package/dist/src/v2/payment-rails/cycles/index.d.ts.map +1 -1
- package/dist/src/v2/payment-rails/cycles/index.js +391 -29
- package/dist/src/v2/payment-rails/cycles/index.js.map +1 -1
- package/dist/src/v2/payment-rails/cycles/types.d.ts +53 -2
- package/dist/src/v2/payment-rails/cycles/types.d.ts.map +1 -1
- package/dist/src/v2/payment-rails/index.d.ts +1 -1
- package/dist/src/v2/payment-rails/index.d.ts.map +1 -1
- package/package.json +21 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/v2/payment-rails/cycles/index.ts"],"names":[],"mappings":"AAoFA,OAAO,KAAK,EAAE,YAAY,IAAI,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAQzE,OAAO,EACL,uBAAuB,EACvB,8BAA8B,EAC9B,yCAAyC,EACzC,0CAA0C,EAC1C,yCAAyC,GAC1C,MAAM,YAAY,CAAA;AACnB,OAAO,KAAK,EACV,YAAY,EACZ,kBAAkB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/v2/payment-rails/cycles/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -79,7 +79,7 @@ import { publicKeyFromPrivate, sign, verify as edVerify } from '../../../crypto/
|
|
|
79
79
|
import { parseDidUri, publicKeyHexFromMethod, resolveVerificationMethod, } from '../../../core/did-uri.js';
|
|
80
80
|
import { RAIL_BUDGET_RESERVATION_PERMIT_CLAIM_TYPE, RAIL_BUDGET_RESERVATION_RELEASE_CLAIM_TYPE, RAIL_BUDGET_RESERVATION_DENIAL_CLAIM_TYPE, } from './types.js';
|
|
81
81
|
export { CYCLES_PROTOCOL_VERSION, CYCLES_EVIDENCE_SCHEMA_VERSION, RAIL_BUDGET_RESERVATION_PERMIT_CLAIM_TYPE, RAIL_BUDGET_RESERVATION_RELEASE_CLAIM_TYPE, RAIL_BUDGET_RESERVATION_DENIAL_CLAIM_TYPE, } from './types.js';
|
|
82
|
-
import { resolveEvidenceRef, toEvidenceDescriptorInput, } from './evidence-resolution.js';
|
|
82
|
+
import { readEvidenceFetchResult, resolveEvidenceRef, toEvidenceDescriptorInput, } from './evidence-resolution.js';
|
|
83
83
|
import { cyclesJwksUrl, isDIDCycles } from '../../key-resolution/index.js';
|
|
84
84
|
// ── Internal helpers ──────────────────────────────────────────────
|
|
85
85
|
function nowIso() {
|
|
@@ -269,18 +269,26 @@ export function signCyclesPermitReceipt(input, signerPrivateKeyHex) {
|
|
|
269
269
|
return { ...unsigned, signature };
|
|
270
270
|
}
|
|
271
271
|
/** Verify a Cycles permit receipt (sync). With `options.evidence`, also
|
|
272
|
-
* checks the receipt↔envelope hash binding
|
|
272
|
+
* checks the receipt↔envelope hash binding and that the envelope is the
|
|
273
|
+
* referenced stage for the same reservation_id (see `_checkEvidenceJoin`).
|
|
274
|
+
* Amount, unit, action and server identity are not compared.
|
|
273
275
|
* NOTE: `valid: true` does NOT assert the envelope's own Ed25519
|
|
274
276
|
* authenticity or signer authority — that is the v0.2 item (#43). Pin
|
|
275
277
|
* `expected_signer` for issuer trust until then. */
|
|
276
278
|
export function verifyCyclesPermitReceipt(receipt, options = {}) {
|
|
277
|
-
|
|
279
|
+
const captured = _captureInputs(receipt, options);
|
|
280
|
+
if (!captured.ok)
|
|
281
|
+
return captured.failure;
|
|
282
|
+
return _verifyReceiptCore(captured.receipt, RAIL_BUDGET_RESERVATION_PERMIT_CLAIM_TYPE, captured.options);
|
|
278
283
|
}
|
|
279
284
|
/** Verify a Cycles permit receipt (async, DID-URI signers). Join-integrity
|
|
280
285
|
* caveat as in `verifyCyclesPermitReceipt`: `valid: true` does NOT assert
|
|
281
286
|
* the envelope's own Ed25519 authenticity (v0.2 — #43). */
|
|
282
287
|
export async function verifyCyclesPermitReceiptWithDID(receipt, options = {}) {
|
|
283
|
-
|
|
288
|
+
const captured = _captureInputs(receipt, options);
|
|
289
|
+
if (!captured.ok)
|
|
290
|
+
return captured.failure;
|
|
291
|
+
return _verifyReceiptWithDID(captured.receipt, RAIL_BUDGET_RESERVATION_PERMIT_CLAIM_TYPE, captured.options);
|
|
284
292
|
}
|
|
285
293
|
// ── Sign / verify: release receipt ────────────────────────────────
|
|
286
294
|
export function signCyclesReleaseReceipt(input, signerPrivateKeyHex) {
|
|
@@ -309,18 +317,26 @@ export function signCyclesReleaseReceipt(input, signerPrivateKeyHex) {
|
|
|
309
317
|
return { ...unsigned, signature };
|
|
310
318
|
}
|
|
311
319
|
/** Verify a Cycles release receipt (sync). With `options.evidence`, also
|
|
312
|
-
* checks the receipt↔envelope hash binding
|
|
320
|
+
* checks the receipt↔envelope hash binding and that the envelope is the
|
|
321
|
+
* referenced stage for the same reservation_id (see `_checkEvidenceJoin`).
|
|
322
|
+
* Amount, unit, action and server identity are not compared.
|
|
313
323
|
* NOTE: `valid: true` does NOT assert the envelope's own Ed25519
|
|
314
324
|
* authenticity or signer authority — that is the v0.2 item (#43). Pin
|
|
315
325
|
* `expected_signer` for issuer trust until then. */
|
|
316
326
|
export function verifyCyclesReleaseReceipt(receipt, options = {}) {
|
|
317
|
-
|
|
327
|
+
const captured = _captureInputs(receipt, options);
|
|
328
|
+
if (!captured.ok)
|
|
329
|
+
return captured.failure;
|
|
330
|
+
return _verifyReceiptCore(captured.receipt, RAIL_BUDGET_RESERVATION_RELEASE_CLAIM_TYPE, captured.options);
|
|
318
331
|
}
|
|
319
332
|
/** Verify a Cycles release receipt (async, DID-URI signers). Join-integrity
|
|
320
333
|
* caveat as in `verifyCyclesReleaseReceipt`: `valid: true` does NOT assert
|
|
321
334
|
* the envelope's own Ed25519 authenticity (v0.2 — #43). */
|
|
322
335
|
export async function verifyCyclesReleaseReceiptWithDID(receipt, options = {}) {
|
|
323
|
-
|
|
336
|
+
const captured = _captureInputs(receipt, options);
|
|
337
|
+
if (!captured.ok)
|
|
338
|
+
return captured.failure;
|
|
339
|
+
return _verifyReceiptWithDID(captured.receipt, RAIL_BUDGET_RESERVATION_RELEASE_CLAIM_TYPE, captured.options);
|
|
324
340
|
}
|
|
325
341
|
// ── Sign / verify: denial ─────────────────────────────────────────
|
|
326
342
|
export function signCyclesDenial(input, signerPrivateKeyHex) {
|
|
@@ -385,6 +401,15 @@ export async function verifyCyclesDenialWithDID(denial, options = {}) {
|
|
|
385
401
|
* but `evidence_id` POPULATED — per cycles-evidence-v0.1's normative
|
|
386
402
|
* "Signature derivation". Failure → EVIDENCE_SIGNATURE_INVALID.
|
|
387
403
|
*
|
|
404
|
+
* 3. CORRESPONDENCE (8.0.0, permit and release only): the envelope is the
|
|
405
|
+
* event the receipt references, same stage and same reservation_id (see
|
|
406
|
+
* `_evaluateCorrespondence`). Failure → EVIDENCE_CORRESPONDENCE_FIELD_MISSING
|
|
407
|
+
* or EVIDENCE_CORRESPONDENCE_MISMATCH. Denials are not checked here.
|
|
408
|
+
* The permit and release entry points pass captured inputs (see
|
|
409
|
+
* `_captureInputs`), so checks 1 to 3 read one value whose fields are
|
|
410
|
+
* exactly those in the hashed and signed bytes. The stage comes from the
|
|
411
|
+
* entry point's receipt kind.
|
|
412
|
+
*
|
|
388
413
|
* Scope boundary (the (a)/(b) split): check 2 proves the bytes were signed
|
|
389
414
|
* by the key in `signer_did` — it does NOT prove that key is the legitimate
|
|
390
415
|
* Cycles signer rather than an attacker key inside a self-consistent forgery.
|
|
@@ -398,7 +423,7 @@ export async function verifyCyclesDenialWithDID(denial, options = {}) {
|
|
|
398
423
|
* fixture, so the empty-string-sentinel rules here are the SAME
|
|
399
424
|
* canonicalizations the Cycles server used. Field omission would differ.
|
|
400
425
|
*/
|
|
401
|
-
function _checkEvidenceJoin(obj, options) {
|
|
426
|
+
function _checkEvidenceJoin(obj, options, stage) {
|
|
402
427
|
const env = options.evidence;
|
|
403
428
|
if (!env)
|
|
404
429
|
return null;
|
|
@@ -407,14 +432,14 @@ function _checkEvidenceJoin(obj, options) {
|
|
|
407
432
|
return {
|
|
408
433
|
valid: false,
|
|
409
434
|
reason: 'EVIDENCE_REF_HASH_MISMATCH',
|
|
410
|
-
detail: `envelope evidence_id ${
|
|
435
|
+
detail: `envelope evidence_id ${_show(env.evidence_id)} != recomputed ${recomputed}`,
|
|
411
436
|
};
|
|
412
437
|
}
|
|
413
438
|
if (recomputed !== obj.cycles_evidence.cycles_evidence_id_sha256) {
|
|
414
439
|
return {
|
|
415
440
|
valid: false,
|
|
416
441
|
reason: 'EVIDENCE_REF_HASH_MISMATCH',
|
|
417
|
-
detail: `receipt cycles_evidence_id_sha256 ${obj.cycles_evidence.cycles_evidence_id_sha256} != envelope ${recomputed}`,
|
|
442
|
+
detail: `receipt cycles_evidence_id_sha256 ${_show(obj.cycles_evidence.cycles_evidence_id_sha256)} != envelope ${recomputed}`,
|
|
418
443
|
};
|
|
419
444
|
}
|
|
420
445
|
// (a) Envelope authenticity: verify the envelope's own Ed25519 signature
|
|
@@ -438,8 +463,275 @@ function _checkEvidenceJoin(obj, options) {
|
|
|
438
463
|
detail: 'envelope Ed25519 signature does not verify against signer_did',
|
|
439
464
|
};
|
|
440
465
|
}
|
|
466
|
+
// (c) Correspondence (permit and release): the envelope must be the event
|
|
467
|
+
// the receipt references. Runs only after the hash and signature checks
|
|
468
|
+
// pass, so a semantic failure is reported as such. The stage comes from
|
|
469
|
+
// the entry point's receipt kind, never from a field of the input.
|
|
470
|
+
if (!stage)
|
|
471
|
+
return null;
|
|
472
|
+
const correspondence = _evaluateCorrespondence(stage, obj, env);
|
|
473
|
+
if (correspondence.status === 'field_missing') {
|
|
474
|
+
return {
|
|
475
|
+
valid: false,
|
|
476
|
+
reason: 'EVIDENCE_CORRESPONDENCE_FIELD_MISSING',
|
|
477
|
+
detail: `${correspondence.field}: ${correspondence.detail}`,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
if (correspondence.status === 'mismatch') {
|
|
481
|
+
return {
|
|
482
|
+
valid: false,
|
|
483
|
+
reason: 'EVIDENCE_CORRESPONDENCE_MISMATCH',
|
|
484
|
+
detail: `${correspondence.field}: ${correspondence.detail}`,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
441
487
|
return null;
|
|
442
488
|
}
|
|
489
|
+
/** Fields deliberately outside the correspondence check. No equivalence rule
|
|
490
|
+
* is defined for them yet (estimate vs reserved vs actual amounts, unit
|
|
491
|
+
* families, action naming, server identity). Reported, never compared. */
|
|
492
|
+
const CORRESPONDENCE_NOT_COMPARED = Object.freeze([
|
|
493
|
+
'amount',
|
|
494
|
+
'unit',
|
|
495
|
+
'action',
|
|
496
|
+
'server_id',
|
|
497
|
+
]);
|
|
498
|
+
function _isPlainObject(v) {
|
|
499
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
500
|
+
}
|
|
501
|
+
/** The stage a receipt kind references. Taken from the verifier entry point,
|
|
502
|
+
* never from the input, so a changing `claim_type` cannot switch the check off. */
|
|
503
|
+
function _stageFor(claimType) {
|
|
504
|
+
if (claimType === RAIL_BUDGET_RESERVATION_PERMIT_CLAIM_TYPE)
|
|
505
|
+
return 'reserve';
|
|
506
|
+
if (claimType === RAIL_BUDGET_RESERVATION_RELEASE_CLAIM_TYPE)
|
|
507
|
+
return 'release';
|
|
508
|
+
return undefined;
|
|
509
|
+
}
|
|
510
|
+
/** Detail text for any value. `String()` throws on a prototype-free object,
|
|
511
|
+
* so fall back to JSON. Ordinary values print exactly as before. */
|
|
512
|
+
function _show(v) {
|
|
513
|
+
try {
|
|
514
|
+
return String(v);
|
|
515
|
+
}
|
|
516
|
+
catch {
|
|
517
|
+
try {
|
|
518
|
+
return JSON.stringify(v) ?? 'unprintable';
|
|
519
|
+
}
|
|
520
|
+
catch {
|
|
521
|
+
return 'unprintable';
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
function _parseTime(v) {
|
|
526
|
+
try {
|
|
527
|
+
return Date.parse(v);
|
|
528
|
+
}
|
|
529
|
+
catch {
|
|
530
|
+
return NaN;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
/** JSON.parse reviver: every object becomes a prototype-free record, so a
|
|
534
|
+
* property lookup finds own fields only, even if Object.prototype carries
|
|
535
|
+
* extra properties. Arrays stay arrays. */
|
|
536
|
+
function _prototypeFree(_key, value) {
|
|
537
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
538
|
+
return Object.assign(Object.create(null), value);
|
|
539
|
+
}
|
|
540
|
+
return value;
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Capture an input as the data its canonical bytes encode: one JCS
|
|
544
|
+
* canonicalization, parsed back into prototype-free records. Inherited,
|
|
545
|
+
* non-enumerable and accessor properties and non-JSON containers such as Date
|
|
546
|
+
* are absent or already reduced to their JSON form, and later reads cannot
|
|
547
|
+
* change. Hashing, signature checks and correspondence on the permit and
|
|
548
|
+
* release paths read only captured values.
|
|
549
|
+
*/
|
|
550
|
+
function _capture(value) {
|
|
551
|
+
let bytes;
|
|
552
|
+
try {
|
|
553
|
+
bytes = canonicalizeJCS(value);
|
|
554
|
+
}
|
|
555
|
+
catch (err) {
|
|
556
|
+
return { ok: false, detail: `has no canonical JSON form (${err instanceof Error ? err.message : _show(err)})` };
|
|
557
|
+
}
|
|
558
|
+
let parsed;
|
|
559
|
+
try {
|
|
560
|
+
parsed = JSON.parse(bytes, _prototypeFree);
|
|
561
|
+
}
|
|
562
|
+
catch (err) {
|
|
563
|
+
return { ok: false, detail: `has no capturable JSON form (${err instanceof Error ? err.message : _show(err)})` };
|
|
564
|
+
}
|
|
565
|
+
if (!_isPlainObject(parsed))
|
|
566
|
+
return { ok: false, detail: 'is not a JSON object' };
|
|
567
|
+
return { ok: true, value: parsed };
|
|
568
|
+
}
|
|
569
|
+
/** Every supported option, read by name. The check below fails the build if
|
|
570
|
+
* VerifyCyclesOptions gains a key that is not listed here. */
|
|
571
|
+
const VERIFY_OPTION_KEYS = [
|
|
572
|
+
'now',
|
|
573
|
+
'ttl_seconds',
|
|
574
|
+
'expected_signer',
|
|
575
|
+
'resolveDidDocument',
|
|
576
|
+
'resolveEvidence',
|
|
577
|
+
'evidenceFailurePolicy',
|
|
578
|
+
'cyclesKeyResolver',
|
|
579
|
+
'expectedEvidenceSigner',
|
|
580
|
+
'maxEvidenceClockSkewMs',
|
|
581
|
+
'evidence',
|
|
582
|
+
];
|
|
583
|
+
const _allOptionKeysListed = true;
|
|
584
|
+
void _allOptionKeysListed;
|
|
585
|
+
/** Snapshot of the caller's options: each supported option read once by
|
|
586
|
+
* ordinary property lookup, as verification always has (own, non-enumerable
|
|
587
|
+
* or inherited), into a prototype-free object. Later reads and writes touch
|
|
588
|
+
* only this snapshot, so an accessor on Object.prototype cannot intercept
|
|
589
|
+
* them, and no constraint the caller supplied is dropped. */
|
|
590
|
+
function _snapshotOptions(options) {
|
|
591
|
+
const out = Object.create(null);
|
|
592
|
+
for (const key of VERIFY_OPTION_KEYS) {
|
|
593
|
+
const value = options[key];
|
|
594
|
+
if (value !== undefined)
|
|
595
|
+
out[key] = value;
|
|
596
|
+
}
|
|
597
|
+
return out;
|
|
598
|
+
}
|
|
599
|
+
function _isFetchFailureStatus(v) {
|
|
600
|
+
return v === 'not_found' || v === 'unreachable' || v === 'malformed';
|
|
601
|
+
}
|
|
602
|
+
/** The envelope a resolution result actually carries: an own property only,
|
|
603
|
+
* so a value on Object.prototype is never mistaken for a fetched envelope. */
|
|
604
|
+
function _ownEnvelope(evidence) {
|
|
605
|
+
return Object.prototype.hasOwnProperty.call(evidence, 'envelope') ? evidence.envelope : undefined;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Entry step for every permit and release verifier. Captures the receipt, the
|
|
609
|
+
* supplied envelope, and (through the resolver) any fetched envelope. A failed
|
|
610
|
+
* capture is a structured rejection. Verification never resumes on the
|
|
611
|
+
* uncaptured object. Denial verifiers do not use this.
|
|
612
|
+
*/
|
|
613
|
+
function _captureInputs(receipt, options) {
|
|
614
|
+
// One read of each supported option into a prototype-free snapshot (see
|
|
615
|
+
// `_snapshotOptions`). Captured values below replace the originals there.
|
|
616
|
+
const out = _snapshotOptions(options);
|
|
617
|
+
const supplied = out.evidence;
|
|
618
|
+
const inner = out.resolveEvidence;
|
|
619
|
+
delete out.evidence;
|
|
620
|
+
delete out.resolveEvidence;
|
|
621
|
+
const r = _capture(receipt);
|
|
622
|
+
if (!r.ok) {
|
|
623
|
+
const detail = `receipt ${r.detail}, so its signature cannot be checked`;
|
|
624
|
+
return { ok: false, field: 'receipt', detail, options: out, failure: { valid: false, reason: 'SIGNATURE_INVALID', detail } };
|
|
625
|
+
}
|
|
626
|
+
if (supplied) {
|
|
627
|
+
const e = _capture(supplied);
|
|
628
|
+
if (!e.ok) {
|
|
629
|
+
const detail = `envelope ${e.detail}, so its content hash cannot be recomputed`;
|
|
630
|
+
return { ok: false, field: 'envelope', detail, options: out, failure: { valid: false, reason: 'EVIDENCE_REF_HASH_MISMATCH', detail } };
|
|
631
|
+
}
|
|
632
|
+
out.evidence = e.value;
|
|
633
|
+
}
|
|
634
|
+
if (inner) {
|
|
635
|
+
out.resolveEvidence = {
|
|
636
|
+
canResolve: (url) => inner.canResolve(url),
|
|
637
|
+
resolve: async (url) => {
|
|
638
|
+
// Read the caller's result once and return a fresh object, never the
|
|
639
|
+
// caller's own, so resolution cannot re-read a changing field.
|
|
640
|
+
const got = readEvidenceFetchResult(await inner.resolve(url));
|
|
641
|
+
const ok = got.ok;
|
|
642
|
+
const envelope = got.envelope;
|
|
643
|
+
const status = got.status;
|
|
644
|
+
const reason = got.reason;
|
|
645
|
+
if (!ok || !envelope) {
|
|
646
|
+
// A failure keeps only a status that means failure. Anything else,
|
|
647
|
+
// including a value that is not the resolver's own, becomes malformed.
|
|
648
|
+
const failed = Object.create(null);
|
|
649
|
+
failed.ok = false;
|
|
650
|
+
if (status !== undefined)
|
|
651
|
+
failed.status = _isFetchFailureStatus(status) ? status : 'malformed';
|
|
652
|
+
if (typeof reason === 'string')
|
|
653
|
+
failed.reason = reason;
|
|
654
|
+
return failed;
|
|
655
|
+
}
|
|
656
|
+
const e = _capture(envelope);
|
|
657
|
+
return e.ok
|
|
658
|
+
? { ok: true, envelope: e.value }
|
|
659
|
+
: { ok: false, status: 'malformed', reason: `envelope ${e.detail}` };
|
|
660
|
+
},
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
return { ok: true, receipt: r.value, options: out };
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Receipt-to-envelope correspondence (8.0.0). A permit references the reserve
|
|
667
|
+
* event for its reservation, a release references the release event for its
|
|
668
|
+
* reservation. Checks, in order: the receipt's reservation_id, the envelope's
|
|
669
|
+
* `artifact_type`, agreement of the single payload variant with that
|
|
670
|
+
* `artifact_type`, and the envelope's reservation_id at the stage path
|
|
671
|
+
* (reserve: payload.reserve.response.reservation_id, release:
|
|
672
|
+
* payload.release.reservation_id). A missing or malformed field is
|
|
673
|
+
* `field_missing`, never a pass. Callers pass the stage from the verifier
|
|
674
|
+
* entry point and captured inputs only (see `_captureInputs`).
|
|
675
|
+
*/
|
|
676
|
+
function _evaluateCorrespondence(stage, obj, env) {
|
|
677
|
+
const receiptReservation = obj.reservation_id;
|
|
678
|
+
if (typeof receiptReservation !== 'string' || receiptReservation.length === 0) {
|
|
679
|
+
return {
|
|
680
|
+
status: 'field_missing',
|
|
681
|
+
field: 'receipt.reservation_id',
|
|
682
|
+
detail: 'absent or not a non-empty string',
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
const artifactType = env.artifact_type;
|
|
686
|
+
if (typeof artifactType !== 'string' || artifactType.length === 0) {
|
|
687
|
+
return { status: 'field_missing', field: 'artifact_type', detail: 'absent or not a non-empty string' };
|
|
688
|
+
}
|
|
689
|
+
if (artifactType !== stage) {
|
|
690
|
+
return {
|
|
691
|
+
status: 'mismatch',
|
|
692
|
+
field: 'artifact_type',
|
|
693
|
+
detail: `receipt references a ${stage} event, envelope is ${artifactType}`,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
const payload = env.payload;
|
|
697
|
+
if (!_isPlainObject(payload)) {
|
|
698
|
+
return { status: 'field_missing', field: 'payload', detail: 'absent or not an object' };
|
|
699
|
+
}
|
|
700
|
+
const variants = Object.keys(payload);
|
|
701
|
+
if (variants.length === 0) {
|
|
702
|
+
return { status: 'field_missing', field: 'payload', detail: 'no payload variant' };
|
|
703
|
+
}
|
|
704
|
+
if (variants.length !== 1 || variants[0] !== stage) {
|
|
705
|
+
return {
|
|
706
|
+
status: 'mismatch',
|
|
707
|
+
field: 'payload',
|
|
708
|
+
detail: `payload variant [${variants.join(', ')}] does not agree with artifact_type ${stage}`,
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
const variant = payload[stage];
|
|
712
|
+
if (!_isPlainObject(variant)) {
|
|
713
|
+
return { status: 'field_missing', field: `payload.${stage}`, detail: 'absent or not an object' };
|
|
714
|
+
}
|
|
715
|
+
const holder = stage === 'reserve' ? variant.response : variant;
|
|
716
|
+
const path = stage === 'reserve' ? 'payload.reserve.response.reservation_id' : 'payload.release.reservation_id';
|
|
717
|
+
const envReservation = _isPlainObject(holder) ? holder.reservation_id : undefined;
|
|
718
|
+
if (typeof envReservation !== 'string' || envReservation.length === 0) {
|
|
719
|
+
return { status: 'field_missing', field: path, detail: 'absent or not a non-empty string' };
|
|
720
|
+
}
|
|
721
|
+
if (envReservation !== receiptReservation) {
|
|
722
|
+
return {
|
|
723
|
+
status: 'mismatch',
|
|
724
|
+
field: 'reservation_id',
|
|
725
|
+
detail: `receipt ${receiptReservation} != envelope ${envReservation}`,
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
return {
|
|
729
|
+
status: 'matched',
|
|
730
|
+
stage,
|
|
731
|
+
reservation_id: receiptReservation,
|
|
732
|
+
not_compared: CORRESPONDENCE_NOT_COMPARED,
|
|
733
|
+
};
|
|
734
|
+
}
|
|
443
735
|
/**
|
|
444
736
|
* Build the passing result, tagging envelope authenticity (APS#43) when an
|
|
445
737
|
* envelope was supplied. Reaching here with `options.evidence` set means the
|
|
@@ -460,28 +752,32 @@ function _validResult(options) {
|
|
|
460
752
|
function _verifyReceiptCore(obj, expectedClaimType, options) {
|
|
461
753
|
const ttl = options.ttl_seconds ?? 24 * 60 * 60;
|
|
462
754
|
if (obj.claim_type !== expectedClaimType) {
|
|
463
|
-
return { valid: false, reason: 'INVALID_CLAIM_TYPE', detail: `expected ${expectedClaimType} got ${obj.claim_type}` };
|
|
755
|
+
return { valid: false, reason: 'INVALID_CLAIM_TYPE', detail: `expected ${expectedClaimType} got ${_show(obj.claim_type)}` };
|
|
464
756
|
}
|
|
465
757
|
if (!obj.receipt_id || !obj.signer || !obj.agent_id || !obj.delegation_ref || !obj.action_ref) {
|
|
466
758
|
return { valid: false, reason: 'MISSING_REQUIRED_FIELD' };
|
|
467
759
|
}
|
|
468
760
|
if (obj.rail_name !== 'cycles') {
|
|
469
|
-
return { valid: false, reason: 'INVALID_RAIL_NAME', detail: `expected 'cycles' got ${
|
|
761
|
+
return { valid: false, reason: 'INVALID_RAIL_NAME', detail: `expected 'cycles' got ${_show(obj.rail_name)}` };
|
|
470
762
|
}
|
|
471
|
-
|
|
763
|
+
// cycles_evidence must be an object. An array or other value would let its
|
|
764
|
+
// fields be looked up on a prototype instead of in the signed bytes.
|
|
765
|
+
if (!_isPlainObject(obj.cycles_evidence) ||
|
|
766
|
+
!obj.cycles_evidence.cycles_evidence_url ||
|
|
767
|
+
!obj.cycles_evidence.cycles_evidence_id_sha256) {
|
|
472
768
|
return { valid: false, reason: 'MISSING_REQUIRED_FIELD', detail: 'cycles_evidence' };
|
|
473
769
|
}
|
|
474
770
|
// Join-integrity: when the fetched envelope is supplied, bind it to the
|
|
475
771
|
// receipt. Runs before signature/DID checks so a broken receipt↔envelope
|
|
476
772
|
// join surfaces regardless of signer type (the DID path calls this core
|
|
477
773
|
// first and propagates any non-DID_RESOLVER_MISSING failure unchanged).
|
|
478
|
-
const joinFailure = _checkEvidenceJoin(obj, options);
|
|
774
|
+
const joinFailure = _checkEvidenceJoin(obj, options, _stageFor(expectedClaimType));
|
|
479
775
|
if (joinFailure)
|
|
480
776
|
return joinFailure;
|
|
481
777
|
if (options.expected_signer && obj.signer !== options.expected_signer) {
|
|
482
778
|
return { valid: false, reason: 'SIGNATURE_INVALID', detail: 'signer mismatch' };
|
|
483
779
|
}
|
|
484
|
-
const issuedMs =
|
|
780
|
+
const issuedMs = _parseTime(obj.issued_at);
|
|
485
781
|
if (!Number.isFinite(issuedMs)) {
|
|
486
782
|
return { valid: false, reason: 'MISSING_REQUIRED_FIELD', detail: 'issued_at' };
|
|
487
783
|
}
|
|
@@ -495,7 +791,7 @@ function _verifyReceiptCore(obj, expectedClaimType, options) {
|
|
|
495
791
|
if (obj.timestamp !== obj.issued_at) {
|
|
496
792
|
return { valid: false, reason: 'MISSING_REQUIRED_FIELD', detail: 'timestamp != issued_at' };
|
|
497
793
|
}
|
|
498
|
-
if (!obj.scope_of_claim ||
|
|
794
|
+
if (!_isPlainObject(obj.scope_of_claim) ||
|
|
499
795
|
typeof obj.scope_of_claim.asserts !== 'string' ||
|
|
500
796
|
obj.scope_of_claim.asserts.length === 0) {
|
|
501
797
|
return { valid: false, reason: 'MISSING_REQUIRED_FIELD', detail: 'scope_of_claim' };
|
|
@@ -530,7 +826,7 @@ async function _verifyReceiptWithDID(obj, expectedClaimType, options) {
|
|
|
530
826
|
const didDoc = await options.resolveDidDocument(parsed.agentId);
|
|
531
827
|
if (!didDoc)
|
|
532
828
|
return { valid: false, reason: 'DID_DOC_NOT_FOUND' };
|
|
533
|
-
const issuedMs =
|
|
829
|
+
const issuedMs = _parseTime(obj.issued_at);
|
|
534
830
|
const result = resolveVerificationMethod(didDoc, obj.signer, options.now ? options.now.getTime() : undefined, Number.isFinite(issuedMs) ? issuedMs : undefined);
|
|
535
831
|
if (!result)
|
|
536
832
|
return { valid: false, reason: 'DID_KEY_NOT_IN_DOC' };
|
|
@@ -559,6 +855,22 @@ const _verified = (disposition, signerDid, options, extra = {}) => {
|
|
|
559
855
|
return { disposition, signerDid, ...extra };
|
|
560
856
|
};
|
|
561
857
|
const RAW_HEX_KEY = /^[0-9a-fA-F]{64}$/;
|
|
858
|
+
/** The supported fields of a key resolver answer, each read once by name and
|
|
859
|
+
* only when it is the answer's own property (enumerable or not), into a
|
|
860
|
+
* prototype-free record. No other field is read. Undefined for anything that
|
|
861
|
+
* is not an object. Callers read this inside their error guard, so a
|
|
862
|
+
* throwing accessor on a supported field becomes a structured failure. */
|
|
863
|
+
const KEY_ANSWER_FIELDS = ['ok', 'publicKeyHex', 'kid', 'status', 'reason'];
|
|
864
|
+
function _readKeyAnswer(value) {
|
|
865
|
+
if (value === null || typeof value !== 'object')
|
|
866
|
+
return undefined;
|
|
867
|
+
const out = Object.create(null);
|
|
868
|
+
for (const key of KEY_ANSWER_FIELDS) {
|
|
869
|
+
if (Object.prototype.hasOwnProperty.call(value, key))
|
|
870
|
+
out[key] = value[key];
|
|
871
|
+
}
|
|
872
|
+
return out;
|
|
873
|
+
}
|
|
562
874
|
/** Map a non-ok KeyResolution status to the spec disposition. Failure to
|
|
563
875
|
* OBTAIN/parse the SET (network, or an unparseable set body) is
|
|
564
876
|
* resolution_failed; a parsed set with no single authorized key — incl. the
|
|
@@ -644,7 +956,7 @@ async function _resolveEnvelopeAuthority(env, options) {
|
|
|
644
956
|
return _authority('signature_invalid', `envelope canonicalization failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
645
957
|
}
|
|
646
958
|
if (recomputedId !== env.evidence_id) {
|
|
647
|
-
return _authority('signature_invalid', `evidence_id does not re-derive (${recomputedId} != ${
|
|
959
|
+
return _authority('signature_invalid', `evidence_id does not re-derive (${recomputedId} != ${_show(env.evidence_id)}) — envelope tampered`);
|
|
648
960
|
}
|
|
649
961
|
const resolver = options.cyclesKeyResolver;
|
|
650
962
|
if (!resolver) {
|
|
@@ -664,44 +976,94 @@ async function _resolveEnvelopeAuthority(env, options) {
|
|
|
664
976
|
: { jwksUrl: cyclesJwksUrl(serverId), publicKeyHex: signerDid, issuedAtMs };
|
|
665
977
|
// The verify path must never throw — a non-conformant resolver that rejects
|
|
666
978
|
// is treated as a resolution failure, never authentic.
|
|
667
|
-
|
|
979
|
+
// Read the key resolver's answer from its own supported properties only,
|
|
980
|
+
// once, inside the guard. A key or success flag found only on
|
|
981
|
+
// Object.prototype is not a resolution.
|
|
982
|
+
let res;
|
|
668
983
|
try {
|
|
669
|
-
|
|
984
|
+
res = _readKeyAnswer(await resolver.resolve(locator));
|
|
670
985
|
}
|
|
671
986
|
catch (err) {
|
|
672
987
|
return _authority('signer_resolution_failed', `authority resolution threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
673
988
|
}
|
|
674
|
-
if (!
|
|
675
|
-
return _authority(
|
|
989
|
+
if (!res) {
|
|
990
|
+
return _authority('signer_resolution_failed', 'key resolver did not return an object');
|
|
991
|
+
}
|
|
992
|
+
const resStatus = typeof res.status === 'string' ? res.status : '';
|
|
993
|
+
const resReason = typeof res.reason === 'string' ? res.reason : undefined;
|
|
994
|
+
const publicKeyHex = typeof res.publicKeyHex === 'string' ? res.publicKeyHex : '';
|
|
995
|
+
if (res.ok !== true || !publicKeyHex) {
|
|
996
|
+
return _authority(_authorityFromResolution(resStatus, resReason), `signer-key resolution failed (status=${resStatus || 'none'}${resReason ? `: ${resReason}` : ''})`);
|
|
676
997
|
}
|
|
677
|
-
if (!edVerify(signingInput, signature,
|
|
998
|
+
if (!edVerify(signingInput, signature, publicKeyHex)) {
|
|
678
999
|
return _authority('signature_invalid', 'envelope signature did not verify under the resolved key');
|
|
679
1000
|
}
|
|
680
|
-
return _verified('authentic', signerDid, options, { resolvedKid:
|
|
1001
|
+
return _verified('authentic', signerDid, options, typeof res.kid === 'string' ? { resolvedKid: res.kid } : {});
|
|
681
1002
|
}
|
|
682
1003
|
async function _verifyWithEvidence(obj, expectedClaimType, options) {
|
|
683
1004
|
// Run the full receipt verification (DID-aware) first.
|
|
684
1005
|
const verify = await _verifyReceiptWithDID(obj, expectedClaimType, options);
|
|
1006
|
+
const stage = _stageFor(expectedClaimType);
|
|
685
1007
|
// Resolve the evidence ref regardless of the receipt verdict, so the
|
|
686
1008
|
// descriptor always reports the claimed-vs-resolved basis. Resolution
|
|
687
|
-
// records failure, never throws.
|
|
688
|
-
|
|
1009
|
+
// records failure, never throws. On the permit and release paths a ref that
|
|
1010
|
+
// is not an object is not read (its fields could come from a prototype).
|
|
1011
|
+
const evidence = stage && !_isPlainObject(obj.cycles_evidence)
|
|
1012
|
+
? await _unreadEvidence('receipt cycles_evidence is not an object', options)
|
|
1013
|
+
: await resolveEvidenceRef(obj.cycles_evidence, options.resolveEvidence, { failurePolicy: options.evidenceFailurePolicy });
|
|
689
1014
|
// Signer authority (#43): resolve the fetched envelope's signer key and
|
|
690
1015
|
// verify the envelope's own signature. Independent of the receipt verdict
|
|
691
1016
|
// and the hash join; reports one of the five distinct dispositions, never throws.
|
|
692
|
-
const
|
|
1017
|
+
const fetchedEnvelope = _ownEnvelope(evidence);
|
|
1018
|
+
const authority = await _resolveEnvelopeAuthority(fetchedEnvelope, options);
|
|
1019
|
+
// Correspondence (8.0.0) on the FETCHED envelope, reported separately so
|
|
1020
|
+
// verify / evidence / authority keep their meanings.
|
|
1021
|
+
// Stage from the entry point. The fetched envelope reaching here on the
|
|
1022
|
+
// permit and release paths is the captured one (see `_captureInputs`).
|
|
1023
|
+
const correspondence = !stage
|
|
1024
|
+
? { status: 'not_applicable', reason: 'correspondence is evaluated for permit and release receipts only' }
|
|
1025
|
+
: evidence.resolved === true && evidence.status === 'matched' && _isPlainObject(fetchedEnvelope)
|
|
1026
|
+
? _evaluateCorrespondence(stage, obj, fetchedEnvelope)
|
|
1027
|
+
: { status: 'not_applicable', reason: `no matched envelope (status=${evidence.status})` };
|
|
693
1028
|
return {
|
|
694
1029
|
verify,
|
|
695
1030
|
evidence,
|
|
696
1031
|
descriptor: toEvidenceDescriptorInput(evidence),
|
|
697
1032
|
authority,
|
|
1033
|
+
correspondence,
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
/** The fetched-path result when capture failed. Nothing further is read from
|
|
1037
|
+
* the uncaptured input: verify carries the structured rejection, the evidence
|
|
1038
|
+
* reference is reported malformed and unresolved, authority is not evaluated,
|
|
1039
|
+
* and correspondence names the field that could not be captured. */
|
|
1040
|
+
/** An evidence result reported as malformed and unresolved, built without
|
|
1041
|
+
* reading any caller input. */
|
|
1042
|
+
function _unreadEvidence(detail, options) {
|
|
1043
|
+
const reason = `not read: ${detail}`;
|
|
1044
|
+
return resolveEvidenceRef({ cycles_evidence_url: '', cycles_evidence_id_sha256: '', action_ref: '', delegation_ref: '' }, { canResolve: () => true, resolve: async () => ({ ok: false, status: 'malformed', reason }) }, { failurePolicy: options.evidenceFailurePolicy });
|
|
1045
|
+
}
|
|
1046
|
+
async function _rejectedWithEvidence(captured) {
|
|
1047
|
+
const evidence = await _unreadEvidence(captured.detail, captured.options);
|
|
1048
|
+
return {
|
|
1049
|
+
verify: captured.failure,
|
|
1050
|
+
evidence,
|
|
1051
|
+
descriptor: toEvidenceDescriptorInput(evidence),
|
|
1052
|
+
authority: _authority('signer_resolution_failed', `not evaluated: ${captured.detail}`),
|
|
1053
|
+
correspondence: { status: 'field_missing', field: captured.field, detail: captured.detail },
|
|
698
1054
|
};
|
|
699
1055
|
}
|
|
700
1056
|
export async function verifyCyclesPermitReceiptWithEvidence(receipt, options = {}) {
|
|
701
|
-
|
|
1057
|
+
const captured = _captureInputs(receipt, options);
|
|
1058
|
+
if (!captured.ok)
|
|
1059
|
+
return _rejectedWithEvidence(captured);
|
|
1060
|
+
return _verifyWithEvidence(captured.receipt, RAIL_BUDGET_RESERVATION_PERMIT_CLAIM_TYPE, captured.options);
|
|
702
1061
|
}
|
|
703
1062
|
export async function verifyCyclesReleaseReceiptWithEvidence(receipt, options = {}) {
|
|
704
|
-
|
|
1063
|
+
const captured = _captureInputs(receipt, options);
|
|
1064
|
+
if (!captured.ok)
|
|
1065
|
+
return _rejectedWithEvidence(captured);
|
|
1066
|
+
return _verifyWithEvidence(captured.receipt, RAIL_BUDGET_RESERVATION_RELEASE_CLAIM_TYPE, captured.options);
|
|
705
1067
|
}
|
|
706
1068
|
/**
|
|
707
1069
|
* Verify a FETCHED CyclesEvidence envelope's own signer authority directly
|