@zadq/x402 0.1.0 → 0.3.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 +28 -2
- package/dist/badge.d.ts +51 -0
- package/dist/badge.js +103 -0
- package/dist/brand.d.ts +1 -0
- package/dist/brand.js +3 -0
- package/dist/client.d.ts +26 -0
- package/dist/client.js +46 -0
- package/dist/explorer.d.ts +17 -4
- package/dist/explorer.js +22 -10
- package/dist/extension.d.ts +20 -6
- package/dist/extension.js +55 -10
- package/dist/header.d.ts +15 -5
- package/dist/header.js +29 -16
- package/dist/holder/chain.d.ts +6 -1
- package/dist/holder/chain.js +24 -8
- package/dist/holder/cli.d.ts +2 -0
- package/dist/holder/cli.js +25 -4
- package/dist/holder/holder.d.ts +8 -0
- package/dist/holder/holder.js +81 -15
- package/dist/holder/index.d.ts +6 -6
- package/dist/holder/index.js +5 -5
- package/dist/holder/killswitch.d.ts +128 -45
- package/dist/holder/killswitch.js +236 -56
- package/dist/holder/process.d.ts +9 -0
- package/dist/holder/process.js +9 -0
- package/dist/holder/types.d.ts +52 -15
- package/dist/index.d.ts +7 -5
- package/dist/index.js +7 -5
- package/dist/keys.d.ts +22 -1
- package/dist/keys.js +25 -7
- package/dist/receipt.d.ts +8 -3
- package/dist/receipt.js +11 -5
- package/dist/verdict.d.ts +85 -15
- package/dist/verdict.js +160 -17
- package/dist/webhook.d.ts +4 -2
- package/dist/webhook.js +6 -12
- package/package.json +18 -2
package/dist/holder/chain.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** The chain query path of the issuer-scoped read. */
|
|
2
2
|
export declare const RECORDED_NULLIFIERS_QUERY = "/identity.v1.selfrevoke.Query/RecordedNullifiers";
|
|
3
|
+
/** After how long the chain prunes a record (`retention_seconds`, both networks). */
|
|
4
|
+
export declare const RECORD_RETENTION_SECONDS = 3600;
|
|
3
5
|
/** A recorded self-revocation's state on the chain, by revocation value (hex). */
|
|
4
6
|
export type RecordedState = 'pending' | 'folded';
|
|
5
7
|
export type ChainReadOptions = {
|
|
@@ -12,7 +14,10 @@ export declare function bytesField(field: number, value: Uint8Array): Uint8Array
|
|
|
12
14
|
export declare function varintField(field: number, value: number): Uint8Array;
|
|
13
15
|
export declare function hexToBytes(hex: string): Uint8Array;
|
|
14
16
|
export declare function bytesToHex(bytes: Uint8Array): string;
|
|
15
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* `QueryRecordedNullifiersRequest{issuer_id = 1}` — `0a20‖issuer_id`; names
|
|
19
|
+
* no revocation value. The issuer id is exactly 32 bytes.
|
|
20
|
+
*/
|
|
16
21
|
export declare function encodeIssuerQuery(issuerIdHex: string): Uint8Array;
|
|
17
22
|
/**
|
|
18
23
|
* `QueryRecordedNullifiersResponse{repeated RecordedNullifier records = 1}`
|
package/dist/holder/chain.js
CHANGED
|
@@ -11,15 +11,25 @@
|
|
|
11
11
|
// two protobuf shapes involved are small enough to encode and decode by
|
|
12
12
|
// hand (varints and length-delimited fields only).
|
|
13
13
|
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
14
|
+
// The contract, as MintID delivered it on 2026-10-07 (the chain's
|
|
15
|
+
// issuer-scoped recorded-revocations query, cited by name): service
|
|
16
|
+
// `identity.v1.selfrevoke.Query`, method `RecordedNullifiers`, over CometBFT
|
|
17
|
+
// `abci_query` with `prove: false` (no proven variant exists: the read is as
|
|
18
|
+
// trustworthy as the node — run your own for more). Request
|
|
19
|
+
// `{bytes issuer_id = 1}`, exactly 32 bytes, so `data` is `0a20‖issuer_id`;
|
|
20
|
+
// the answer's `result.response.value` is base64 of
|
|
21
|
+
// `{repeated RecordedNullifier records = 1; uint64 version = 2; uint64 acked = 3}`
|
|
22
|
+
// with `RecordedNullifier{uint64 version = 1; bytes nullifier = 2;
|
|
23
|
+
// uint64 folded_in_epoch = 3}` (0 while pending). Every unpruned record of
|
|
24
|
+
// the issuer comes back; a record is pruned after `retention_seconds`
|
|
25
|
+
// (3 600 s on both networks), so a value absent after it was seen is folded
|
|
26
|
+
// and pruned, not lost. The chain's `Nullifier` query would
|
|
27
|
+
// name the value to the node, and is not used here.
|
|
20
28
|
import { HolderStepError } from "./errors.js";
|
|
21
29
|
/** The chain query path of the issuer-scoped read. */
|
|
22
30
|
export const RECORDED_NULLIFIERS_QUERY = '/identity.v1.selfrevoke.Query/RecordedNullifiers';
|
|
31
|
+
/** After how long the chain prunes a record (`retention_seconds`, both networks). */
|
|
32
|
+
export const RECORD_RETENTION_SECONDS = 3_600;
|
|
23
33
|
function varint(value) {
|
|
24
34
|
const out = [];
|
|
25
35
|
let v = value;
|
|
@@ -46,9 +56,15 @@ export function hexToBytes(hex) {
|
|
|
46
56
|
export function bytesToHex(bytes) {
|
|
47
57
|
return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
48
58
|
}
|
|
49
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* `QueryRecordedNullifiersRequest{issuer_id = 1}` — `0a20‖issuer_id`; names
|
|
61
|
+
* no revocation value. The issuer id is exactly 32 bytes.
|
|
62
|
+
*/
|
|
50
63
|
export function encodeIssuerQuery(issuerIdHex) {
|
|
51
|
-
|
|
64
|
+
const id = hexToBytes(issuerIdHex);
|
|
65
|
+
if (id.length !== 32)
|
|
66
|
+
throw new HolderStepError('chain', 0, { error: 'issuer id is not 32 bytes' });
|
|
67
|
+
return bytesField(1, id);
|
|
52
68
|
}
|
|
53
69
|
function readVarint(data, pos) {
|
|
54
70
|
let result = 0;
|
package/dist/holder/cli.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export declare const CUSTODY_KEY_ENV = "CREDENTIAL_CUSTODY_KEY";
|
|
|
8
8
|
export declare const DASHBOARD_URL_ENV = "VERIFICATION_BASE_URL";
|
|
9
9
|
export declare const DASHBOARD_SELLER_ENV = "VERIFICATION_SELLER_ID";
|
|
10
10
|
export declare const DASHBOARD_SESSION_ENV = "VERIFICATION_SESSION";
|
|
11
|
+
/** `--wait`: the fold grace and two root periods — the issuer has folded by then. */
|
|
12
|
+
export declare const DEFAULT_WAIT_FOR_FOLD_MS: number;
|
|
11
13
|
/** The credential state the seller's record takes at the dashboard service. */
|
|
12
14
|
export declare const DASHBOARD_REVOKED_STATE = "self_revoked";
|
|
13
15
|
export type KillSwitchEnv = Record<string, string | undefined>;
|
package/dist/holder/cli.js
CHANGED
|
@@ -14,20 +14,25 @@
|
|
|
14
14
|
//
|
|
15
15
|
// Flags: --yes (required: the revocation is irreversible), --no-jitter or
|
|
16
16
|
// --jitter-ms <n> (the random delay before the submission, up to a minute
|
|
17
|
-
// by default), --
|
|
17
|
+
// by default), --wait or --wait-ms <n> (keep reading the chain after the
|
|
18
|
+
// record shows, until the issuer has folded it: two minutes by default),
|
|
19
|
+
// --report (tell the dashboard, with VERIFICATION_BASE_URL,
|
|
18
20
|
// VERIFICATION_SELLER_ID and VERIFICATION_SESSION — the dashboard's short-lived session
|
|
19
21
|
// credential — set). Nothing here logs; stdout carries the report only.
|
|
20
22
|
import { pathToFileURL } from 'node:url';
|
|
23
|
+
import { clientHeaders } from "../client.js";
|
|
21
24
|
import { custodyKeyFromHex, fileCustodyStore, sealedCustodyStore, } from "./custody.js";
|
|
22
25
|
import { HolderError } from "./errors.js";
|
|
23
26
|
import { SellerHolder } from "./holder.js";
|
|
24
|
-
import { CHAIN_ID_ENV, CHAIN_RPC_ENV, RELAY_URL_ENV, RelayRefusedError, revokeCredential, } from "./killswitch.js";
|
|
27
|
+
import { CHAIN_ID_ENV, CHAIN_RPC_ENV, EFFECT_BOUND, RELAY_URL_ENV, RelayRefusedError, revokeCredential, } from "./killswitch.js";
|
|
25
28
|
import { HOLDER_BINARY_ENV, HolderProcess } from "./process.js";
|
|
26
29
|
export const CUSTODY_FILE_ENV = 'CREDENTIAL_CUSTODY_FILE';
|
|
27
30
|
export const CUSTODY_KEY_ENV = 'CREDENTIAL_CUSTODY_KEY';
|
|
28
31
|
export const DASHBOARD_URL_ENV = 'VERIFICATION_BASE_URL';
|
|
29
32
|
export const DASHBOARD_SELLER_ENV = 'VERIFICATION_SELLER_ID';
|
|
30
33
|
export const DASHBOARD_SESSION_ENV = 'VERIFICATION_SESSION';
|
|
34
|
+
/** `--wait`: the fold grace and two root periods — the issuer has folded by then. */
|
|
35
|
+
export const DEFAULT_WAIT_FOR_FOLD_MS = (EFFECT_BOUND.foldGraceSeconds + 2 * EFFECT_BOUND.rootPeriodSeconds) * 1000;
|
|
31
36
|
/** The credential state the seller's record takes at the dashboard service. */
|
|
32
37
|
export const DASHBOARD_REVOKED_STATE = 'self_revoked';
|
|
33
38
|
const NEXT_STEP = 'Open the dashboard and press "Revoke my credential" → "My middleware revoked it" ' +
|
|
@@ -47,7 +52,8 @@ function value(argv, name) {
|
|
|
47
52
|
*/
|
|
48
53
|
export async function runKillSwitch(argv, env, io, deps = {}) {
|
|
49
54
|
if (!flag(argv, '--yes')) {
|
|
50
|
-
io.err('This revokes your credential for good:
|
|
55
|
+
io.err('This revokes your credential for good: within ' +
|
|
56
|
+
`${EFFECT_BOUND.boundSeconds} s of the chain recording it, it presents nowhere, ` +
|
|
51
57
|
'and only a new verification gives you a fresh one. Run again with --yes.');
|
|
52
58
|
return 1;
|
|
53
59
|
}
|
|
@@ -90,6 +96,17 @@ export async function runKillSwitch(argv, env, io, deps = {}) {
|
|
|
90
96
|
}
|
|
91
97
|
options.jitterMs = ms;
|
|
92
98
|
}
|
|
99
|
+
if (flag(argv, '--wait'))
|
|
100
|
+
options.waitForFoldMs = DEFAULT_WAIT_FOR_FOLD_MS;
|
|
101
|
+
const wait = value(argv, '--wait-ms');
|
|
102
|
+
if (wait !== undefined) {
|
|
103
|
+
const ms = Number(wait);
|
|
104
|
+
if (!Number.isFinite(ms) || ms < 0) {
|
|
105
|
+
io.err('--wait-ms takes a non-negative number of milliseconds.');
|
|
106
|
+
return 1;
|
|
107
|
+
}
|
|
108
|
+
options.waitForFoldMs = ms;
|
|
109
|
+
}
|
|
93
110
|
const rpc = deps.rpc ?? HolderProcess.start(env[HOLDER_BINARY_ENV]);
|
|
94
111
|
const fetchImpl = deps.fetch ?? fetch;
|
|
95
112
|
let report;
|
|
@@ -152,7 +169,11 @@ export async function reportToDashboard(fetchImpl, baseUrl, sellerId, session) {
|
|
|
152
169
|
try {
|
|
153
170
|
const response = await fetchImpl(`${baseUrl.replace(/\/+$/, '')}/v0/sellers/${encodeURIComponent(sellerId)}/credential`, {
|
|
154
171
|
method: 'POST',
|
|
155
|
-
headers: {
|
|
172
|
+
headers: {
|
|
173
|
+
...clientHeaders(),
|
|
174
|
+
'content-type': 'application/json',
|
|
175
|
+
authorization: `Bearer ${session}`,
|
|
176
|
+
},
|
|
156
177
|
body: JSON.stringify({ state: DASHBOARD_REVOKED_STATE }),
|
|
157
178
|
signal: AbortSignal.timeout(15_000),
|
|
158
179
|
});
|
package/dist/holder/holder.d.ts
CHANGED
|
@@ -106,6 +106,14 @@ export declare class SellerHolder {
|
|
|
106
106
|
/** Forget the credential inside the process (its secret with it). The custody store is untouched. */
|
|
107
107
|
close(): Promise<void>;
|
|
108
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Reads a witness answer's `self_revoke_context` (issuer v1 witness route):
|
|
111
|
+
* the four anchoring members and `status_root`, which must name `root` —
|
|
112
|
+
* the root the witness was adopted on. `undefined` for `null`, for another
|
|
113
|
+
* root, or for anything not of that shape (the holder library would refuse
|
|
114
|
+
* it anyway; nothing is cached that cannot serve).
|
|
115
|
+
*/
|
|
116
|
+
export declare function readSelfRevokeContext(value: unknown, root: string): SelfRevokeMaterial | undefined;
|
|
109
117
|
/**
|
|
110
118
|
* Reads the verification service's `POST` body as an issued challenge:
|
|
111
119
|
* `{request, signature_hex}`, or the bare request. Returns `undefined` for
|
package/dist/holder/holder.js
CHANGED
|
@@ -192,13 +192,10 @@ export class SellerHolder {
|
|
|
192
192
|
});
|
|
193
193
|
await this.persist();
|
|
194
194
|
// The issuer serves the root's self-revocation material with the
|
|
195
|
-
// witness
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
material['status_root'] === String(root.root)) {
|
|
200
|
-
this.material = material;
|
|
201
|
-
}
|
|
195
|
+
// witness (`self_revoke_context`, null unless usable and anchored on the
|
|
196
|
+
// served root). Cached at every refresh, for the adopted root only: a
|
|
197
|
+
// context for another root, or none, clears what an older refresh left.
|
|
198
|
+
this.material = readSelfRevokeContext(body['self_revoke_context'], String(root.root));
|
|
202
199
|
return {
|
|
203
200
|
status: state,
|
|
204
201
|
usable: true,
|
|
@@ -304,26 +301,95 @@ export class SellerHolder {
|
|
|
304
301
|
* primitive under it.
|
|
305
302
|
*/
|
|
306
303
|
async selfRevoke(context) {
|
|
307
|
-
const
|
|
308
|
-
if (!
|
|
304
|
+
const given = context.anchoring ?? this.material;
|
|
305
|
+
if (!given) {
|
|
309
306
|
throw new HolderError('no self-revocation material: refresh the witness first, or pass the issuer anchoring');
|
|
310
307
|
}
|
|
308
|
+
// `anchoring` is `self_revoke_context` without `status_root`; the root
|
|
309
|
+
// travels beside it (optional for the library, checked against both
|
|
310
|
+
// witnesses when given).
|
|
311
|
+
const { status_root: servedRoot, ...anchoring } = given;
|
|
311
312
|
const full = { chain_id: context.chain_id, anchoring };
|
|
312
|
-
const root = context.status_root ??
|
|
313
|
+
const root = context.status_root ?? servedRoot;
|
|
313
314
|
if (typeof root === 'string' && root !== '')
|
|
314
315
|
full['status_root'] = root;
|
|
315
|
-
const out =
|
|
316
|
-
|
|
317
|
-
context: full,
|
|
318
|
-
}));
|
|
316
|
+
const out = await this.rpc.call('self_revoke', { holder: this.handle, context: full });
|
|
317
|
+
const revocation = readSelfRevocation(out);
|
|
319
318
|
await this.persist();
|
|
320
|
-
return
|
|
319
|
+
return revocation;
|
|
321
320
|
}
|
|
322
321
|
/** Forget the credential inside the process (its secret with it). The custody store is untouched. */
|
|
323
322
|
async close() {
|
|
324
323
|
await this.rpc.call('drop', { holder: this.handle });
|
|
325
324
|
}
|
|
326
325
|
}
|
|
326
|
+
const HEX = /^([0-9a-f]{2})*$/;
|
|
327
|
+
const isHex = (v, bytes) => typeof v === 'string' && HEX.test(v) && (bytes === undefined || v.length === bytes * 2);
|
|
328
|
+
function isLeafWitness(v) {
|
|
329
|
+
if (typeof v !== 'object' || v === null)
|
|
330
|
+
return false;
|
|
331
|
+
const w = v;
|
|
332
|
+
return (typeof w['status'] === 'string' &&
|
|
333
|
+
typeof w['status_epoch'] === 'number' &&
|
|
334
|
+
isHex(w['root']) &&
|
|
335
|
+
typeof w['index'] === 'number' &&
|
|
336
|
+
Array.isArray(w['path']) &&
|
|
337
|
+
w['path'].every((p) => isHex(p, 32)));
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Reads a witness answer's `self_revoke_context` (issuer v1 witness route):
|
|
341
|
+
* the four anchoring members and `status_root`, which must name `root` —
|
|
342
|
+
* the root the witness was adopted on. `undefined` for `null`, for another
|
|
343
|
+
* root, or for anything not of that shape (the holder library would refuse
|
|
344
|
+
* it anyway; nothing is cached that cannot serve).
|
|
345
|
+
*/
|
|
346
|
+
export function readSelfRevokeContext(value, root) {
|
|
347
|
+
if (typeof value !== 'object' || value === null)
|
|
348
|
+
return undefined;
|
|
349
|
+
const c = value;
|
|
350
|
+
const key = c['selfrevoke_encryption_key'];
|
|
351
|
+
if (c['status_root'] !== root ||
|
|
352
|
+
!isHex(c['issuer_definition']) ||
|
|
353
|
+
!isLeafWitness(c['definition_witness']) ||
|
|
354
|
+
!isLeafWitness(c['selfrevoke_encryption_key_witness']) ||
|
|
355
|
+
typeof key !== 'object' ||
|
|
356
|
+
key === null ||
|
|
357
|
+
typeof key['version'] !== 'number' ||
|
|
358
|
+
!isHex(key['public_key'], 48)) {
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
return value;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* The library's `self_revoke` result as delivered: `{message (five
|
|
365
|
+
* fields), nullifier, ciphertext}`. `HolderError` on anything else — the
|
|
366
|
+
* holder has marked itself revoked by then, but nothing goes to a relay.
|
|
367
|
+
*/
|
|
368
|
+
function readSelfRevocation(out) {
|
|
369
|
+
const m = out['message'];
|
|
370
|
+
if (typeof m !== 'object' ||
|
|
371
|
+
m === null ||
|
|
372
|
+
!isHex(m['issuer_id'], 32) ||
|
|
373
|
+
!isHex(m['nullifier'], 32) ||
|
|
374
|
+
typeof m['status_epoch'] !== 'number' ||
|
|
375
|
+
!isHex(m['zk_authorization_proof']) ||
|
|
376
|
+
typeof m['proof_system_version'] !== 'string' ||
|
|
377
|
+
!isHex(out['nullifier'], 32) ||
|
|
378
|
+
typeof out['ciphertext'] !== 'string') {
|
|
379
|
+
throw new HolderError('the holder library answered self_revoke with an unexpected shape');
|
|
380
|
+
}
|
|
381
|
+
return {
|
|
382
|
+
message: {
|
|
383
|
+
issuer_id: m['issuer_id'],
|
|
384
|
+
nullifier: m['nullifier'],
|
|
385
|
+
status_epoch: m['status_epoch'],
|
|
386
|
+
zk_authorization_proof: m['zk_authorization_proof'],
|
|
387
|
+
proof_system_version: m['proof_system_version'],
|
|
388
|
+
},
|
|
389
|
+
nullifier: out['nullifier'],
|
|
390
|
+
ciphertext: out['ciphertext'],
|
|
391
|
+
};
|
|
392
|
+
}
|
|
327
393
|
/**
|
|
328
394
|
* Reads the verification service's `POST` body as an issued challenge:
|
|
329
395
|
* `{request, signature_hex}`, or the bare request. Returns `undefined` for
|
package/dist/holder/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { CredentialRevokedError, HolderError, HolderLibraryError, HolderStepError, OfferError, } from './errors.js';
|
|
2
|
-
export { HOLDER_BINARY_ENV, HOLDER_PROTOCOL, HolderProcess, type HolderParams, type HolderRpc, } from './process.js';
|
|
2
|
+
export { HOLDER_BINARY_ENV, HOLDER_PROTOCOL, HOLDER_RELEASE, HolderProcess, type HolderParams, type HolderRpc, } from './process.js';
|
|
3
3
|
export { custodyKeyFromHex, fileCustodyStore, memoryCustodyStore, sealedCustodyStore, type CustodyKey, type CustodyStore, } from './custody.js';
|
|
4
|
-
export { BUILD_INFO_PATH, CREDENTIAL_FORMAT, IDENTITY_CONFIGURATION_ID, METADATA_PATH, PRE_AUTHORIZED_GRANT, PRESENTATION_WINDOW_MS, PROOF_TYPE, SellerHolder, readCredentialOffer, readIssuedChallenge, type CredentialOffer, type Fetch, type HolderOptions, } from './holder.js';
|
|
5
|
-
export type { ChallengeAnswer, ClaimPredicate, HolderInfo, IssuedChallenge, PresentationEnvelope, PresentationRequest, SelfRevocation, SelfRevokeContext, SelfRevokeMaterial, WitnessState, } from './types.js';
|
|
6
|
-
export { CHAIN_ID_ENV, CHAIN_RPC_ENV, CONFIRM_ATTEMPTS, CONFIRM_INTERVAL_MS, DEFAULT_JITTER_MS, EFFECT_BOUND, RELAY_SUBMIT_PATH, RELAY_URL_ENV, RelayRefusedError, confirmSelfRevocation, revokeCredential, submitSelfRevocation, type Confirmation, type ConfirmOptions, type KillSwitchOptions, type KillSwitchReport, type RelayAnswer, type RelayOutcome, type SubmitOptions, } from './killswitch.js';
|
|
7
|
-
export { RECORDED_NULLIFIERS_QUERY, decodeRecordedNullifiers, encodeIssuerQuery, readRecordedNullifiers, type ChainReadOptions, type RecordedState, } from './chain.js';
|
|
8
|
-
export { CUSTODY_FILE_ENV, CUSTODY_KEY_ENV, DASHBOARD_REVOKED_STATE, DASHBOARD_SELLER_ENV, DASHBOARD_SESSION_ENV, DASHBOARD_URL_ENV, reportToDashboard, runKillSwitch, type CliReport, type KillSwitchDeps, type KillSwitchEnv, type KillSwitchIO, } from './cli.js';
|
|
4
|
+
export { BUILD_INFO_PATH, CREDENTIAL_FORMAT, IDENTITY_CONFIGURATION_ID, METADATA_PATH, PRE_AUTHORIZED_GRANT, PRESENTATION_WINDOW_MS, PROOF_TYPE, SellerHolder, readCredentialOffer, readIssuedChallenge, readSelfRevokeContext, type CredentialOffer, type Fetch, type HolderOptions, } from './holder.js';
|
|
5
|
+
export type { ChallengeAnswer, ClaimPredicate, HolderInfo, IssuedChallenge, PresentationEnvelope, PresentationRequest, AnchoredLeafWitness, SelfRevocation, SelfRevokeAnchoring, SelfRevokeContext, SelfRevokeMaterial, WitnessState, } from './types.js';
|
|
6
|
+
export { AGENT_CREDENTIAL_REFUSAL, CHAIN_ID_ENV, CHAIN_RPC_ENV, CONFIRM_ATTEMPTS, CONFIRM_INTERVAL_MS, DEFAULT_JITTER_MS, DEFAULT_RELAY_ATTEMPTS, EFFECT_BOUND, MAX_RETRY_AFTER_MS, RELAY_API_VERSION, RELAY_BODY_CAP_BYTES, RELAY_REASON_CODES, RELAY_RETRY_AFTER_SECONDS, RELAY_SUBMIT_PATH, RELAY_UNREACHABLE_RETRY_SECONDS, RELAY_URL_ENV, RelayRefusedError, confirmSelfRevocation, isRelayReasonCode, revokeCredential, submitSelfRevocation, type ClientReasonCode, type Confirmation, type ConfirmOptions, type KillSwitchOptions, type KillSwitchReport, type RelayAnswer, type RelayOutcome, type RelayReasonCode, type SubmitOptions, } from './killswitch.js';
|
|
7
|
+
export { RECORD_RETENTION_SECONDS, RECORDED_NULLIFIERS_QUERY, decodeRecordedNullifiers, encodeIssuerQuery, readRecordedNullifiers, type ChainReadOptions, type RecordedState, } from './chain.js';
|
|
8
|
+
export { CUSTODY_FILE_ENV, CUSTODY_KEY_ENV, DASHBOARD_REVOKED_STATE, DASHBOARD_SELLER_ENV, DASHBOARD_SESSION_ENV, DASHBOARD_URL_ENV, DEFAULT_WAIT_FOR_FOLD_MS, reportToDashboard, runKillSwitch, type CliReport, type KillSwitchDeps, type KillSwitchEnv, type KillSwitchIO, } from './cli.js';
|
package/dist/holder/index.js
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
// process. Node-only (it spawns the holder process); the rest of this
|
|
4
4
|
// repository stays platform-neutral.
|
|
5
5
|
export { CredentialRevokedError, HolderError, HolderLibraryError, HolderStepError, OfferError, } from "./errors.js";
|
|
6
|
-
export { HOLDER_BINARY_ENV, HOLDER_PROTOCOL, HolderProcess, } from "./process.js";
|
|
6
|
+
export { HOLDER_BINARY_ENV, HOLDER_PROTOCOL, HOLDER_RELEASE, HolderProcess, } from "./process.js";
|
|
7
7
|
export { custodyKeyFromHex, fileCustodyStore, memoryCustodyStore, sealedCustodyStore, } from "./custody.js";
|
|
8
|
-
export { BUILD_INFO_PATH, CREDENTIAL_FORMAT, IDENTITY_CONFIGURATION_ID, METADATA_PATH, PRE_AUTHORIZED_GRANT, PRESENTATION_WINDOW_MS, PROOF_TYPE, SellerHolder, readCredentialOffer, readIssuedChallenge, } from "./holder.js";
|
|
8
|
+
export { BUILD_INFO_PATH, CREDENTIAL_FORMAT, IDENTITY_CONFIGURATION_ID, METADATA_PATH, PRE_AUTHORIZED_GRANT, PRESENTATION_WINDOW_MS, PROOF_TYPE, SellerHolder, readCredentialOffer, readIssuedChallenge, readSelfRevokeContext, } from "./holder.js";
|
|
9
9
|
// The kill switch: self-revocation through the privacy relay, confirmed on
|
|
10
10
|
// the chain, reported value-free (killswitch.ts); the issuer-scoped chain
|
|
11
11
|
// read under it (chain.ts).
|
|
12
|
-
export { CHAIN_ID_ENV, CHAIN_RPC_ENV, CONFIRM_ATTEMPTS, CONFIRM_INTERVAL_MS, DEFAULT_JITTER_MS, EFFECT_BOUND, RELAY_SUBMIT_PATH, RELAY_URL_ENV, RelayRefusedError, confirmSelfRevocation, revokeCredential, submitSelfRevocation, } from "./killswitch.js";
|
|
13
|
-
export { RECORDED_NULLIFIERS_QUERY, decodeRecordedNullifiers, encodeIssuerQuery, readRecordedNullifiers, } from "./chain.js";
|
|
14
|
-
export { CUSTODY_FILE_ENV, CUSTODY_KEY_ENV, DASHBOARD_REVOKED_STATE, DASHBOARD_SELLER_ENV, DASHBOARD_SESSION_ENV, DASHBOARD_URL_ENV, reportToDashboard, runKillSwitch, } from "./cli.js";
|
|
12
|
+
export { AGENT_CREDENTIAL_REFUSAL, CHAIN_ID_ENV, CHAIN_RPC_ENV, CONFIRM_ATTEMPTS, CONFIRM_INTERVAL_MS, DEFAULT_JITTER_MS, DEFAULT_RELAY_ATTEMPTS, EFFECT_BOUND, MAX_RETRY_AFTER_MS, RELAY_API_VERSION, RELAY_BODY_CAP_BYTES, RELAY_REASON_CODES, RELAY_RETRY_AFTER_SECONDS, RELAY_SUBMIT_PATH, RELAY_UNREACHABLE_RETRY_SECONDS, RELAY_URL_ENV, RelayRefusedError, confirmSelfRevocation, isRelayReasonCode, revokeCredential, submitSelfRevocation, } from "./killswitch.js";
|
|
13
|
+
export { RECORD_RETENTION_SECONDS, RECORDED_NULLIFIERS_QUERY, decodeRecordedNullifiers, encodeIssuerQuery, readRecordedNullifiers, } from "./chain.js";
|
|
14
|
+
export { CUSTODY_FILE_ENV, CUSTODY_KEY_ENV, DASHBOARD_REVOKED_STATE, DASHBOARD_SELLER_ENV, DASHBOARD_SESSION_ENV, DASHBOARD_URL_ENV, DEFAULT_WAIT_FOR_FOLD_MS, reportToDashboard, runKillSwitch, } from "./cli.js";
|
|
@@ -5,27 +5,67 @@ import type { SelfRevocation } from './types.js';
|
|
|
5
5
|
export type Fetch = typeof fetch;
|
|
6
6
|
/** The relay's submission path (relay contract v0). */
|
|
7
7
|
export declare const RELAY_SUBMIT_PATH = "/v1/self-revocations";
|
|
8
|
+
/** The relay contract this module implements (`X-Relay-Api-Version` on every answer). */
|
|
9
|
+
export declare const RELAY_API_VERSION = "0.1.0";
|
|
10
|
+
/** The relay's body cap: 16 KiB. A larger body is refused here, before it travels. */
|
|
11
|
+
export declare const RELAY_BODY_CAP_BYTES: number;
|
|
8
12
|
/** Environment variables the middleware host is configured with. */
|
|
9
13
|
export declare const RELAY_URL_ENV = "MINTID_RELAY_URL";
|
|
10
14
|
export declare const CHAIN_RPC_ENV = "MINTID_CHAIN_RPC";
|
|
11
15
|
export declare const CHAIN_ID_ENV = "MINTID_CHAIN_ID";
|
|
16
|
+
/**
|
|
17
|
+
* The relay's closed reason vocabulary (relay contract v0 0.1.0): its own
|
|
18
|
+
* codes, the shape and lookup codes, the proof-check codes and the chain's
|
|
19
|
+
* `selfrevoke` codes (any other chain code arrives as `chain_refused`). An
|
|
20
|
+
* answer naming anything else is read as `unexpected_answer`.
|
|
21
|
+
*/
|
|
22
|
+
export declare const RELAY_REASON_CODES: readonly ["request_too_large", "malformed_json", "duplicate_submission", "busy", "chain_unavailable", "inclusion_timeout", "chain_refused", "proof_refused", "not_found", "method_not_allowed", "internal_error", "proof_system_version", "msg_shape", "nullifier", "nullifier_recorded", "unknown_root", "wire_version", "schema", "wire_shape", "path_depth", "anchor_definition", "anchor_encryption_key", "definition_shape", "encoding", "identity_point", "bbs_schnorr", "bbs_pairing", "acc_schnorr", "acc_pairing", "ve_c1", "ve_c2", "selfrevoke_disabled", "block_cap", "issuer_state", "issuer_cap", "pending_cap", "tx_shape", "params"];
|
|
23
|
+
export type RelayReasonCode = (typeof RELAY_REASON_CODES)[number];
|
|
24
|
+
/** True for a code of the relay's closed vocabulary. */
|
|
25
|
+
export declare function isRelayReasonCode(value: unknown): value is RelayReasonCode;
|
|
26
|
+
/**
|
|
27
|
+
* Codes decided on this side, never by the relay: the relay could not be
|
|
28
|
+
* reached, it answered outside its contract, or the message was refused
|
|
29
|
+
* before it left (`msg_shape`, `request_too_large` with stage `client`).
|
|
30
|
+
*/
|
|
31
|
+
export type ClientReasonCode = 'relay_unreachable' | 'unexpected_answer';
|
|
32
|
+
/**
|
|
33
|
+
* The contract's `Retry-After` table, by code — used only when an answer
|
|
34
|
+
* that should carry the header does not (the header, when present, wins):
|
|
35
|
+
* `429 busy` 2 or 5 s, `503 chain_unavailable` 5 or 10 s, `503 block_cap` /
|
|
36
|
+
* `issuer_cap` 10 s, `503 busy` 10 s, `503 pending_cap` 60 s. The fallback
|
|
37
|
+
* takes the longer value of each row.
|
|
38
|
+
*/
|
|
39
|
+
export declare const RELAY_RETRY_AFTER_SECONDS: Readonly<Record<string, number>>;
|
|
40
|
+
/** A relay that cannot be reached is tried again after this long. */
|
|
41
|
+
export declare const RELAY_UNREACHABLE_RETRY_SECONDS = 5;
|
|
42
|
+
/** The kill switch's submission attempts at one relay (the primitive's default is one). */
|
|
43
|
+
export declare const DEFAULT_RELAY_ATTEMPTS = 3;
|
|
44
|
+
/** No single wait is longer than the contract's longest `Retry-After` (pending_cap). */
|
|
45
|
+
export declare const MAX_RETRY_AFTER_MS = 60000;
|
|
12
46
|
/** A random delay before the revocation is built and sent: up to a minute by default. */
|
|
13
47
|
export declare const DEFAULT_JITTER_MS = 60000;
|
|
14
48
|
/** The confirmation read: about one block, in a few short reads. */
|
|
15
49
|
export declare const CONFIRM_ATTEMPTS = 4;
|
|
16
50
|
export declare const CONFIRM_INTERVAL_MS = 750;
|
|
17
51
|
/**
|
|
18
|
-
* The published effect bound
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
52
|
+
* The published effect bound (the network's revocation rule, per path; the
|
|
53
|
+
* same parameters on production and the public testnet): a holder
|
|
54
|
+
* self-revocation — the kill switch — is effective everywhere within
|
|
55
|
+
* G + A + K = 245 s of the chain recording it (fold grace 60 s, a root
|
|
56
|
+
* accepted for at most 180 s, 5 s of future skew; a root every 30 s). A
|
|
57
|
+
* revoked principal's agents follow within 7·H + A + K = 395 s of the root
|
|
58
|
+
* that carries the trigger. The bound is the guarantee: a measured figure
|
|
59
|
+
* is only ever quoted next to it.
|
|
24
60
|
*/
|
|
25
61
|
export declare const EFFECT_BOUND: {
|
|
26
|
-
readonly
|
|
27
|
-
readonly
|
|
28
|
-
readonly
|
|
62
|
+
readonly boundSeconds: 245;
|
|
63
|
+
readonly countedFrom: "recorded_on_chain";
|
|
64
|
+
readonly principalCascadeSeconds: 395;
|
|
65
|
+
readonly rootPeriodSeconds: 30;
|
|
66
|
+
readonly acceptanceWindowSeconds: 180;
|
|
67
|
+
readonly futureSkewSeconds: 5;
|
|
68
|
+
readonly foldGraceSeconds: 60;
|
|
29
69
|
};
|
|
30
70
|
/** What the relay answered, in the contract's own words. */
|
|
31
71
|
export type RelayOutcome = 'recorded' | 'queued' | 'inclusion_timeout' | 'nullifier_recorded' | 'duplicate_submission';
|
|
@@ -33,29 +73,38 @@ export type RelayAnswer = {
|
|
|
33
73
|
outcome: RelayOutcome;
|
|
34
74
|
txHash?: string;
|
|
35
75
|
height?: number;
|
|
76
|
+
/** What the relay says the chain holds (200 only) — never the receipt: the chain read decides. */
|
|
77
|
+
relayNullifierState?: RecordedState;
|
|
36
78
|
};
|
|
37
|
-
/** A relay refusal: the
|
|
79
|
+
/** A relay refusal: the bare reason code and stage, nothing of the message. */
|
|
38
80
|
export declare class RelayRefusedError extends HolderError {
|
|
39
81
|
readonly status: number;
|
|
40
|
-
readonly code:
|
|
41
|
-
|
|
42
|
-
|
|
82
|
+
readonly code: RelayReasonCode | ClientReasonCode;
|
|
83
|
+
/** `client`: decided on this side (unreachable, outside the contract, refused before sending). */
|
|
84
|
+
readonly stage: 'relay' | 'precheck' | 'chain' | 'client';
|
|
85
|
+
/** Worth a retry, here or at another relay (`busy`, `chain_unavailable`, caps, unreachable). */
|
|
43
86
|
readonly retryable: boolean;
|
|
44
87
|
readonly retryAfterMs: number | undefined;
|
|
45
|
-
constructor(status: number, code:
|
|
88
|
+
constructor(status: number, code: RelayReasonCode | ClientReasonCode, stage: 'relay' | 'precheck' | 'chain' | 'client', retryAfterMs?: number);
|
|
46
89
|
}
|
|
47
90
|
export type SubmitOptions = {
|
|
48
91
|
/** The relay's own transport — never the holder's issuer transport. */
|
|
49
92
|
fetch?: Fetch;
|
|
50
93
|
timeoutMs?: number;
|
|
94
|
+
/** Attempts at this relay; a retryable refusal waits its `Retry-After` first. Default 1. */
|
|
95
|
+
attempts?: number;
|
|
96
|
+
sleep?: (ms: number) => Promise<void>;
|
|
51
97
|
};
|
|
52
98
|
/**
|
|
53
99
|
* Hands the self-revocation to the privacy relay: `{message}` with the five
|
|
54
100
|
* message fields only, no header beyond the content type. The relay checks
|
|
55
|
-
* the proof
|
|
56
|
-
* it; this side signs nothing and holds no account.
|
|
57
|
-
* a refusal
|
|
58
|
-
*
|
|
101
|
+
* the proof against the issuer's anchored root, pays for the transaction
|
|
102
|
+
* and submits it; this side signs nothing and holds no account.
|
|
103
|
+
* `RelayRefusedError` on a refusal (the code from the closed vocabulary);
|
|
104
|
+
* every accepted answer (recorded, queued, already recorded, already in
|
|
105
|
+
* flight, broadcast but not yet seen) still needs the chain read. With
|
|
106
|
+
* `attempts` above one, a retryable refusal (429, 503, unreachable) is
|
|
107
|
+
* tried again after the relay's `Retry-After` (or the contract's table).
|
|
59
108
|
*/
|
|
60
109
|
export declare function submitSelfRevocation(relayUrl: string, revocation: SelfRevocation, options?: SubmitOptions): Promise<RelayAnswer>;
|
|
61
110
|
export type ConfirmOptions = {
|
|
@@ -64,31 +113,45 @@ export type ConfirmOptions = {
|
|
|
64
113
|
attempts?: number;
|
|
65
114
|
intervalMs?: number;
|
|
66
115
|
sleep?: (ms: number) => Promise<void>;
|
|
116
|
+
/**
|
|
117
|
+
* `recorded` (default): stop at the first read that shows the record.
|
|
118
|
+
* `folded`: keep reading while it is pending, until folded or the
|
|
119
|
+
* attempts run out (the report is then `pending`).
|
|
120
|
+
*/
|
|
121
|
+
until?: 'recorded' | 'folded';
|
|
122
|
+
/**
|
|
123
|
+
* What an earlier read showed for this revocation. A value seen once and
|
|
124
|
+
* absent now has been folded and pruned (the chain keeps a record for
|
|
125
|
+
* `RECORD_RETENTION_SECONDS`), so it reads `folded`.
|
|
126
|
+
*/
|
|
127
|
+
seen?: RecordedState;
|
|
67
128
|
};
|
|
68
129
|
export type Confirmation = {
|
|
69
130
|
state: RecordedState;
|
|
131
|
+
pruned?: true;
|
|
70
132
|
} | {
|
|
71
133
|
state: 'unconfirmed';
|
|
72
134
|
reason: 'no_chain_endpoint' | 'chain_unreachable' | 'not_recorded';
|
|
73
135
|
};
|
|
74
136
|
/**
|
|
75
|
-
* Asks a node of the chain whether the revocation is there
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* `unconfirmed` with the reason (no node named, node
|
|
80
|
-
* recorded yet).
|
|
137
|
+
* Asks a node of the chain whether the revocation is there, reading the
|
|
138
|
+
* issuer's recorded set and finding the holder's own value locally, so the
|
|
139
|
+
* node never learns which one. `pending` once recorded, `folded` once the
|
|
140
|
+
* issuer has folded it (or, after an earlier sighting, once the chain has
|
|
141
|
+
* pruned it); otherwise `unconfirmed` with the reason (no node named, node
|
|
142
|
+
* unreachable, not recorded yet). The relay's answer never enters here.
|
|
81
143
|
*/
|
|
82
144
|
export declare function confirmSelfRevocation(rpcUrl: string | undefined, revocation: SelfRevocation, options?: ConfirmOptions): Promise<Confirmation>;
|
|
83
145
|
/** What the kill switch reports to the seller — value-free. */
|
|
84
146
|
export type KillSwitchReport = {
|
|
85
147
|
/**
|
|
86
|
-
* `revoked`: the chain holds the record and the issuer has folded it
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
148
|
+
* `revoked`: the chain holds the record and the issuer has folded it.
|
|
149
|
+
* `pending`: the chain holds the record and the issuer folds it at a next
|
|
150
|
+
* root. Either way the credential is refused everywhere within the
|
|
151
|
+
* published bound (`effect`, 245 s from the record). `unconfirmed`: the
|
|
152
|
+
* relay accepted the submission but no node showed the record within the
|
|
153
|
+
* read window (or none was named) — check again, or resubmit through a
|
|
154
|
+
* relay; both are safe.
|
|
92
155
|
*/
|
|
93
156
|
state: 'revoked' | 'pending' | 'unconfirmed';
|
|
94
157
|
relay: RelayOutcome;
|
|
@@ -98,6 +161,7 @@ export type KillSwitchReport = {
|
|
|
98
161
|
height?: number;
|
|
99
162
|
/** The holder refuses to present from now on, whatever the chain shows. */
|
|
100
163
|
holderRefusesToPresent: true;
|
|
164
|
+
/** The published bound — the guarantee; never a measured figure. */
|
|
101
165
|
effect: typeof EFFECT_BOUND;
|
|
102
166
|
};
|
|
103
167
|
export type KillSwitchOptions = {
|
|
@@ -109,31 +173,50 @@ export type KillSwitchOptions = {
|
|
|
109
173
|
/** The relay's transport; the chain read's transport. Neither is the issuer's. */
|
|
110
174
|
relayFetch?: Fetch;
|
|
111
175
|
chainFetch?: Fetch;
|
|
176
|
+
/** Submission attempts at the relay, `Retry-After` honoured between them (default 3). */
|
|
177
|
+
relayAttempts?: number;
|
|
112
178
|
confirmAttempts?: number;
|
|
113
179
|
confirmIntervalMs?: number;
|
|
180
|
+
/**
|
|
181
|
+
* Keep reading after the record shows, until the issuer has folded it,
|
|
182
|
+
* for up to this long (0, the default: report at the first sighting).
|
|
183
|
+
*/
|
|
184
|
+
waitForFoldMs?: number;
|
|
114
185
|
sleep?: (ms: number) => Promise<void>;
|
|
115
186
|
/** Uniform in [0, 1); injected by tests. */
|
|
116
187
|
random?: () => number;
|
|
117
188
|
};
|
|
189
|
+
/**
|
|
190
|
+
* What the seller path answers for an agent credential: agent credentials
|
|
191
|
+
* are revoked through the verification service's agent kill route (the
|
|
192
|
+
* operator console's kill action), which keeps the operator view's state —
|
|
193
|
+
* never through this path.
|
|
194
|
+
*/
|
|
195
|
+
export declare const AGENT_CREDENTIAL_REFUSAL: string;
|
|
118
196
|
/**
|
|
119
197
|
* The product kill switch, end to end, where the key is:
|
|
120
198
|
*
|
|
121
199
|
* 1. a random delay, so the moment the seller decided is not the moment
|
|
122
|
-
* the chain records (the relay
|
|
123
|
-
* seller's
|
|
124
|
-
* 2. a fresh witness from the issuer,
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* the holder refuses to present,
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
200
|
+
* the chain records (the relay adds its own 0–6 s and shuffles each
|
|
201
|
+
* block's batch; this adds the seller's slack);
|
|
202
|
+
* 2. a fresh witness from the issuer, whose answer carries the root's
|
|
203
|
+
* `self_revoke_context` (no other call);
|
|
204
|
+
* 3. the holder library's `self_revoke` — `context = {chain_id,
|
|
205
|
+
* status_root, anchoring}` with `anchoring` the served context without
|
|
206
|
+
* `status_root` — and from that moment the holder refuses to present,
|
|
207
|
+
* also after a reload from custody;
|
|
208
|
+
* 4. the submission through the privacy relay, `Retry-After` honoured; a
|
|
209
|
+
* refusal for a root the chain no longer holds (`unknown_root`)
|
|
210
|
+
* rebuilds the context once from a fresh witness;
|
|
211
|
+
* 5. the confirmation read on the chain, issuer-scoped — the relay's
|
|
212
|
+
* answer is never the receipt.
|
|
132
213
|
*
|
|
133
|
-
*
|
|
134
|
-
* `
|
|
135
|
-
* `
|
|
136
|
-
*
|
|
137
|
-
*
|
|
214
|
+
* The seller's own credential only: an agent credential is refused
|
|
215
|
+
* (`AGENT_CREDENTIAL_REFUSAL`). Configuration: `relayUrl` (or
|
|
216
|
+
* `MINTID_RELAY_URL`), `chainRpc` (or `MINTID_CHAIN_RPC`; without it the
|
|
217
|
+
* report is `unconfirmed`, `no_chain_endpoint`), `chainId` (or
|
|
218
|
+
* `MINTID_CHAIN_ID`, else the issuer's public build information). The
|
|
219
|
+
* client never bypasses the relay to the chain: it holds no account and
|
|
220
|
+
* signs nothing.
|
|
138
221
|
*/
|
|
139
222
|
export declare function revokeCredential(holder: SellerHolder, options?: KillSwitchOptions): Promise<KillSwitchReport>;
|