signify-ts 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/dist/keri/app/aiding.js +1 -1
- package/dist/keri/app/coring.d.ts +8 -1
- package/dist/keri/app/coring.js +11 -0
- package/dist/keri/app/didwebing.d.ts +146 -0
- package/dist/keri/app/didwebing.js +359 -0
- package/dist/keri/app/signaling.d.ts +50 -0
- package/dist/keri/app/signaling.js +52 -0
- package/dist/keri/app/w3cing.d.ts +137 -0
- package/dist/keri/app/w3cing.js +337 -0
- package/dist/keri/core/keeping.d.ts +95 -7
- package/dist/keri/core/keeping.js +115 -18
- package/dist/keri/core/keyState.d.ts +1 -0
- package/dist/keri/core/manager.d.ts +1 -1
- package/dist/keri/core/manager.js +3 -3
- package/package.json +2 -2
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { SignifyClient } from './clienting.ts';
|
|
2
|
+
import { SignedReplyEnvelope } from './signaling.ts';
|
|
3
|
+
export declare const W3C_SIGNING_ROUTE = "/w3c/signing/request";
|
|
4
|
+
export declare const W3C_REQUEST_DATA_INTEGRITY_PROOF = "data_integrity_proof";
|
|
5
|
+
export declare const W3C_REQUEST_VC_JWT = "vc_jwt";
|
|
6
|
+
export declare const W3C_DEDUPE_IN_FLIGHT = "in_flight";
|
|
7
|
+
export declare const W3C_DEDUPE_SUBMITTED = "submitted";
|
|
8
|
+
export declare const W3C_DEDUPE_COMPLETE = "complete";
|
|
9
|
+
export declare const W3C_DEDUPE_FAILED = "failed";
|
|
10
|
+
export declare const W3C_DEDUPE_REJECTED = "rejected";
|
|
11
|
+
export declare const W3C_DEDUPE_TERMINAL_RETENTION_MS: number;
|
|
12
|
+
export declare const W3C_DEDUPE_IN_FLIGHT_RETENTION_MS: number;
|
|
13
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
14
|
+
export type W3CProjectionDedupeStatus = typeof W3C_DEDUPE_IN_FLIGHT | typeof W3C_DEDUPE_SUBMITTED | typeof W3C_DEDUPE_COMPLETE | typeof W3C_DEDUPE_FAILED | typeof W3C_DEDUPE_REJECTED;
|
|
15
|
+
export interface W3CVerifier {
|
|
16
|
+
id: string;
|
|
17
|
+
label: string;
|
|
18
|
+
kind: string;
|
|
19
|
+
verifyUrl: string;
|
|
20
|
+
healthUrl?: string | null;
|
|
21
|
+
}
|
|
22
|
+
export interface W3CProjectionSession {
|
|
23
|
+
d: string;
|
|
24
|
+
aid: string;
|
|
25
|
+
name: string;
|
|
26
|
+
credentialSaid: string;
|
|
27
|
+
issuerDid: string;
|
|
28
|
+
verifierId: string;
|
|
29
|
+
state: string;
|
|
30
|
+
created: string;
|
|
31
|
+
updated: string;
|
|
32
|
+
expires: string;
|
|
33
|
+
proofRequest?: string | null;
|
|
34
|
+
jwtRequest?: string | null;
|
|
35
|
+
verifierStatus?: number | null;
|
|
36
|
+
verifierResponse?: unknown;
|
|
37
|
+
error?: string | null;
|
|
38
|
+
}
|
|
39
|
+
export interface W3CSigningRequest {
|
|
40
|
+
d: string;
|
|
41
|
+
session: string;
|
|
42
|
+
type: string;
|
|
43
|
+
kind: string;
|
|
44
|
+
agent: string;
|
|
45
|
+
aid: string;
|
|
46
|
+
name: string;
|
|
47
|
+
credentialSaid: string;
|
|
48
|
+
signingInputB64: string;
|
|
49
|
+
encoding: 'base64url';
|
|
50
|
+
verificationMethod: string;
|
|
51
|
+
created: string;
|
|
52
|
+
expires: string;
|
|
53
|
+
state?: string;
|
|
54
|
+
lastSignaled?: string | null;
|
|
55
|
+
error?: string | null;
|
|
56
|
+
envelope?: SignedReplyEnvelope;
|
|
57
|
+
}
|
|
58
|
+
export interface W3CProjectionDedupeRecord {
|
|
59
|
+
id: string;
|
|
60
|
+
aid: string;
|
|
61
|
+
kind: string;
|
|
62
|
+
status: W3CProjectionDedupeStatus;
|
|
63
|
+
updated: string;
|
|
64
|
+
error?: string | null;
|
|
65
|
+
}
|
|
66
|
+
export interface W3CProjectionDedupeStore {
|
|
67
|
+
get(id: string): MaybePromise<W3CProjectionDedupeRecord | undefined>;
|
|
68
|
+
put(record: W3CProjectionDedupeRecord): MaybePromise<void>;
|
|
69
|
+
delete(id: string): MaybePromise<void>;
|
|
70
|
+
purgeTerminal(maxAgeMs?: number, now?: string): MaybePromise<number>;
|
|
71
|
+
}
|
|
72
|
+
export declare class MemoryW3CProjectionDedupeStore implements W3CProjectionDedupeStore {
|
|
73
|
+
records: Map<string, W3CProjectionDedupeRecord>;
|
|
74
|
+
get(id: string): W3CProjectionDedupeRecord | undefined;
|
|
75
|
+
put(record: W3CProjectionDedupeRecord): void;
|
|
76
|
+
delete(id: string): void;
|
|
77
|
+
purgeTerminal(maxAgeMs?: number, now?: string): number;
|
|
78
|
+
}
|
|
79
|
+
export declare class IndexedDbW3CProjectionDedupeStore implements W3CProjectionDedupeStore {
|
|
80
|
+
private readonly factory;
|
|
81
|
+
private readonly dbName;
|
|
82
|
+
private readonly storeName;
|
|
83
|
+
private dbPromise?;
|
|
84
|
+
constructor(factory?: IDBFactory, dbName?: string, storeName?: string);
|
|
85
|
+
get(id: string): Promise<W3CProjectionDedupeRecord | undefined>;
|
|
86
|
+
put(record: W3CProjectionDedupeRecord): Promise<void>;
|
|
87
|
+
delete(id: string): Promise<void>;
|
|
88
|
+
purgeTerminal(maxAgeMs?: number, now?: string): Promise<number>;
|
|
89
|
+
private request;
|
|
90
|
+
private open;
|
|
91
|
+
}
|
|
92
|
+
export declare function defaultW3CProjectionDedupeStore(): W3CProjectionDedupeStore;
|
|
93
|
+
export declare class W3C {
|
|
94
|
+
private readonly client;
|
|
95
|
+
constructor(client: SignifyClient);
|
|
96
|
+
verifiers(): Promise<W3CVerifier[]>;
|
|
97
|
+
project(name: string, credentialSaid: string, verifierId: string): Promise<W3CProjectionSession>;
|
|
98
|
+
projection(name: string, sessionId: string): Promise<W3CProjectionSession>;
|
|
99
|
+
requests(name?: string, includeComplete?: boolean): Promise<W3CSigningRequest[]>;
|
|
100
|
+
submitSignature(request: W3CSigningRequest, signature: string): Promise<W3CSigningRequest>;
|
|
101
|
+
private managedIdentifierNames;
|
|
102
|
+
}
|
|
103
|
+
export type W3CProjectionRequestSource = 'sse' | 'polling';
|
|
104
|
+
export type W3CProjectionAutoApproveOutcome = 'submitted' | 'skipped' | 'complete' | 'failed' | 'rejected';
|
|
105
|
+
export interface W3CProjectionAutoApproveResult {
|
|
106
|
+
outcome: W3CProjectionAutoApproveOutcome;
|
|
107
|
+
requestId?: string;
|
|
108
|
+
source?: W3CProjectionRequestSource;
|
|
109
|
+
record?: W3CProjectionDedupeRecord;
|
|
110
|
+
error?: string;
|
|
111
|
+
}
|
|
112
|
+
export interface W3CProjectionAutoApproverOptions {
|
|
113
|
+
store?: W3CProjectionDedupeStore;
|
|
114
|
+
now?: () => string;
|
|
115
|
+
terminalRetentionMs?: number;
|
|
116
|
+
inFlightRetentionMs?: number;
|
|
117
|
+
}
|
|
118
|
+
export declare class W3CProjectionAutoApprover {
|
|
119
|
+
private readonly client;
|
|
120
|
+
private readonly w3c;
|
|
121
|
+
private readonly store;
|
|
122
|
+
private readonly now;
|
|
123
|
+
private readonly terminalRetentionMs;
|
|
124
|
+
private readonly inFlightRetentionMs;
|
|
125
|
+
private activeApprovals;
|
|
126
|
+
constructor(client: SignifyClient, options?: W3CProjectionAutoApproverOptions);
|
|
127
|
+
handleEnvelope(envelope: SignedReplyEnvelope): Promise<W3CProjectionAutoApproveResult>;
|
|
128
|
+
handleRequest(request: W3CSigningRequest | undefined, source?: W3CProjectionRequestSource): Promise<W3CProjectionAutoApproveResult>;
|
|
129
|
+
pollOnce(name?: string): Promise<W3CProjectionAutoApproveResult[]>;
|
|
130
|
+
reconcile(name?: string): Promise<W3CProjectionDedupeRecord[]>;
|
|
131
|
+
private handleRequestOnce;
|
|
132
|
+
private signRequest;
|
|
133
|
+
private localOwnershipError;
|
|
134
|
+
private shouldSkip;
|
|
135
|
+
private putRecord;
|
|
136
|
+
}
|
|
137
|
+
export {};
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { decodeBase64Url } from "../core/base64.js";
|
|
2
|
+
export const W3C_SIGNING_ROUTE = '/w3c/signing/request';
|
|
3
|
+
export const W3C_REQUEST_DATA_INTEGRITY_PROOF = 'data_integrity_proof';
|
|
4
|
+
export const W3C_REQUEST_VC_JWT = 'vc_jwt';
|
|
5
|
+
export const W3C_DEDUPE_IN_FLIGHT = 'in_flight';
|
|
6
|
+
export const W3C_DEDUPE_SUBMITTED = 'submitted';
|
|
7
|
+
export const W3C_DEDUPE_COMPLETE = 'complete';
|
|
8
|
+
export const W3C_DEDUPE_FAILED = 'failed';
|
|
9
|
+
export const W3C_DEDUPE_REJECTED = 'rejected';
|
|
10
|
+
export const W3C_DEDUPE_TERMINAL_RETENTION_MS = 10 * 60 * 1000;
|
|
11
|
+
export const W3C_DEDUPE_IN_FLIGHT_RETENTION_MS = 10 * 60 * 1000;
|
|
12
|
+
const W3C_DEDUPE_DB_NAME = 'signify-w3c-projection';
|
|
13
|
+
const W3C_DEDUPE_STORE_NAME = 'requestDedupe';
|
|
14
|
+
const W3C_TERMINAL_DEDUPE_STATUSES = new Set([
|
|
15
|
+
W3C_DEDUPE_COMPLETE,
|
|
16
|
+
W3C_DEDUPE_FAILED,
|
|
17
|
+
W3C_DEDUPE_REJECTED,
|
|
18
|
+
]);
|
|
19
|
+
export class MemoryW3CProjectionDedupeStore {
|
|
20
|
+
constructor() {
|
|
21
|
+
this.records = new Map();
|
|
22
|
+
}
|
|
23
|
+
get(id) {
|
|
24
|
+
return this.records.get(id);
|
|
25
|
+
}
|
|
26
|
+
put(record) {
|
|
27
|
+
this.records.set(record.id, record);
|
|
28
|
+
}
|
|
29
|
+
delete(id) {
|
|
30
|
+
this.records.delete(id);
|
|
31
|
+
}
|
|
32
|
+
purgeTerminal(maxAgeMs = W3C_DEDUPE_TERMINAL_RETENTION_MS, now = new Date().toISOString()) {
|
|
33
|
+
let deleted = 0;
|
|
34
|
+
for (const record of this.records.values()) {
|
|
35
|
+
if (shouldPurgeTerminalRecord(record, maxAgeMs, now)) {
|
|
36
|
+
this.records.delete(record.id);
|
|
37
|
+
deleted += 1;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return deleted;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export class IndexedDbW3CProjectionDedupeStore {
|
|
44
|
+
constructor(factory = globalThis.indexedDB, dbName = W3C_DEDUPE_DB_NAME, storeName = W3C_DEDUPE_STORE_NAME) {
|
|
45
|
+
this.factory = factory;
|
|
46
|
+
this.dbName = dbName;
|
|
47
|
+
this.storeName = storeName;
|
|
48
|
+
if (factory === undefined) {
|
|
49
|
+
throw new Error('IndexedDB is unavailable');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async get(id) {
|
|
53
|
+
return await this.request('readonly', (store) => store.get(id));
|
|
54
|
+
}
|
|
55
|
+
async put(record) {
|
|
56
|
+
await this.request('readwrite', (store) => store.put(record));
|
|
57
|
+
}
|
|
58
|
+
async delete(id) {
|
|
59
|
+
await this.request('readwrite', (store) => store.delete(id));
|
|
60
|
+
}
|
|
61
|
+
async purgeTerminal(maxAgeMs = W3C_DEDUPE_TERMINAL_RETENTION_MS, now = new Date().toISOString()) {
|
|
62
|
+
const db = await this.open();
|
|
63
|
+
return await new Promise((resolve, reject) => {
|
|
64
|
+
let deleted = 0;
|
|
65
|
+
const transaction = db.transaction(this.storeName, 'readwrite');
|
|
66
|
+
const store = transaction.objectStore(this.storeName);
|
|
67
|
+
const request = store.getAll();
|
|
68
|
+
request.onsuccess = () => {
|
|
69
|
+
for (const record of request.result) {
|
|
70
|
+
if (shouldPurgeTerminalRecord(record, maxAgeMs, now)) {
|
|
71
|
+
store.delete(record.id);
|
|
72
|
+
deleted += 1;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
request.onerror = () => reject(request.error);
|
|
77
|
+
transaction.oncomplete = () => resolve(deleted);
|
|
78
|
+
transaction.onerror = () => reject(transaction.error);
|
|
79
|
+
transaction.onabort = () => reject(transaction.error);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async request(mode, create) {
|
|
83
|
+
const db = await this.open();
|
|
84
|
+
return await new Promise((resolve, reject) => {
|
|
85
|
+
const transaction = db.transaction(this.storeName, mode);
|
|
86
|
+
const request = create(transaction.objectStore(this.storeName));
|
|
87
|
+
request.onsuccess = () => resolve(request.result);
|
|
88
|
+
request.onerror = () => reject(request.error);
|
|
89
|
+
transaction.onerror = () => reject(transaction.error);
|
|
90
|
+
transaction.onabort = () => reject(transaction.error);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async open() {
|
|
94
|
+
if (this.dbPromise === undefined) {
|
|
95
|
+
this.dbPromise = new Promise((resolve, reject) => {
|
|
96
|
+
const request = this.factory.open(this.dbName, 1);
|
|
97
|
+
request.onupgradeneeded = () => {
|
|
98
|
+
const db = request.result;
|
|
99
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
100
|
+
db.createObjectStore(this.storeName, { keyPath: 'id' });
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
request.onsuccess = () => resolve(request.result);
|
|
104
|
+
request.onerror = () => reject(request.error);
|
|
105
|
+
request.onblocked = () => reject(new Error('IndexedDB W3C dedupe store is blocked'));
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return await this.dbPromise;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export function defaultW3CProjectionDedupeStore() {
|
|
112
|
+
if (typeof globalThis.indexedDB !== 'undefined') {
|
|
113
|
+
return new IndexedDbW3CProjectionDedupeStore(globalThis.indexedDB);
|
|
114
|
+
}
|
|
115
|
+
return new MemoryW3CProjectionDedupeStore();
|
|
116
|
+
}
|
|
117
|
+
export class W3C {
|
|
118
|
+
constructor(client) {
|
|
119
|
+
this.client = client;
|
|
120
|
+
}
|
|
121
|
+
async verifiers() {
|
|
122
|
+
const res = await this.client.fetch('/w3c/verifiers', 'GET', null);
|
|
123
|
+
const body = await res.json();
|
|
124
|
+
return body.verifiers;
|
|
125
|
+
}
|
|
126
|
+
async project(name, credentialSaid, verifierId) {
|
|
127
|
+
const res = await this.client.fetch(`/identifiers/${name}/w3c/projections`, 'POST', { credentialSaid, verifierId });
|
|
128
|
+
return await res.json();
|
|
129
|
+
}
|
|
130
|
+
async projection(name, sessionId) {
|
|
131
|
+
const res = await this.client.fetch(`/identifiers/${name}/w3c/projections/${encodeURIComponent(sessionId)}`, 'GET', null);
|
|
132
|
+
return await res.json();
|
|
133
|
+
}
|
|
134
|
+
async requests(name, includeComplete = false) {
|
|
135
|
+
const names = name !== undefined ? [name] : await this.managedIdentifierNames();
|
|
136
|
+
const requests = [];
|
|
137
|
+
for (const aidName of names) {
|
|
138
|
+
const params = new URLSearchParams();
|
|
139
|
+
if (includeComplete) {
|
|
140
|
+
params.set('includeComplete', 'true');
|
|
141
|
+
}
|
|
142
|
+
const query = params.size > 0 ? `?${params.toString()}` : '';
|
|
143
|
+
const res = await this.client.fetch(`/identifiers/${aidName}/w3c/signing-requests${query}`, 'GET', null);
|
|
144
|
+
const body = await res.json();
|
|
145
|
+
requests.push(...body.requests);
|
|
146
|
+
}
|
|
147
|
+
return requests;
|
|
148
|
+
}
|
|
149
|
+
async submitSignature(request, signature) {
|
|
150
|
+
const res = await this.client.fetch(`/identifiers/${request.name}/w3c/signing-requests/${encodeURIComponent(request.d)}/signatures`, 'POST', { signature });
|
|
151
|
+
return await res.json();
|
|
152
|
+
}
|
|
153
|
+
async managedIdentifierNames() {
|
|
154
|
+
const result = await this.client.identifiers().list();
|
|
155
|
+
return result.aids.map((aid) => aid.name);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
export class W3CProjectionAutoApprover {
|
|
159
|
+
constructor(client, options = {}) {
|
|
160
|
+
var _a, _b, _c, _d;
|
|
161
|
+
this.activeApprovals = new Map();
|
|
162
|
+
this.client = client;
|
|
163
|
+
this.w3c = new W3C(client);
|
|
164
|
+
this.store = (_a = options.store) !== null && _a !== void 0 ? _a : defaultW3CProjectionDedupeStore();
|
|
165
|
+
this.now = (_b = options.now) !== null && _b !== void 0 ? _b : (() => new Date().toISOString());
|
|
166
|
+
this.terminalRetentionMs =
|
|
167
|
+
(_c = options.terminalRetentionMs) !== null && _c !== void 0 ? _c : W3C_DEDUPE_TERMINAL_RETENTION_MS;
|
|
168
|
+
this.inFlightRetentionMs =
|
|
169
|
+
(_d = options.inFlightRetentionMs) !== null && _d !== void 0 ? _d : W3C_DEDUPE_IN_FLIGHT_RETENTION_MS;
|
|
170
|
+
}
|
|
171
|
+
async handleEnvelope(envelope) {
|
|
172
|
+
const verified = this.client.signals().verifyReplyEnvelope(envelope, {
|
|
173
|
+
route: W3C_SIGNING_ROUTE,
|
|
174
|
+
});
|
|
175
|
+
if (!verified) {
|
|
176
|
+
return {
|
|
177
|
+
outcome: 'rejected',
|
|
178
|
+
error: 'W3C signing request envelope failed verification',
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return await this.handleRequest(envelope.rpy.a, 'sse');
|
|
182
|
+
}
|
|
183
|
+
async handleRequest(request, source = 'polling') {
|
|
184
|
+
if ((request === null || request === void 0 ? void 0 : request.d) === undefined || request.d === '') {
|
|
185
|
+
return {
|
|
186
|
+
outcome: 'rejected',
|
|
187
|
+
source,
|
|
188
|
+
error: 'W3C signing request is missing request SAID',
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const active = this.activeApprovals.get(request.d);
|
|
192
|
+
if (active !== undefined) {
|
|
193
|
+
return await active;
|
|
194
|
+
}
|
|
195
|
+
const approval = this.handleRequestOnce(request, source);
|
|
196
|
+
this.activeApprovals.set(request.d, approval);
|
|
197
|
+
try {
|
|
198
|
+
return await approval;
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
this.activeApprovals.delete(request.d);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async pollOnce(name) {
|
|
205
|
+
const requests = await this.w3c.requests(name);
|
|
206
|
+
const results = [];
|
|
207
|
+
for (const request of requests) {
|
|
208
|
+
results.push(await this.handleRequest(request, 'polling'));
|
|
209
|
+
}
|
|
210
|
+
return results;
|
|
211
|
+
}
|
|
212
|
+
async reconcile(name) {
|
|
213
|
+
var _a;
|
|
214
|
+
await this.store.purgeTerminal(this.terminalRetentionMs, this.now());
|
|
215
|
+
const requests = await this.w3c.requests(name, true);
|
|
216
|
+
const records = [];
|
|
217
|
+
for (const request of requests) {
|
|
218
|
+
if (request.state === W3C_DEDUPE_COMPLETE) {
|
|
219
|
+
records.push(await this.putRecord(request, W3C_DEDUPE_COMPLETE));
|
|
220
|
+
}
|
|
221
|
+
else if (request.state === W3C_DEDUPE_FAILED) {
|
|
222
|
+
records.push(await this.putRecord(request, W3C_DEDUPE_FAILED, (_a = request.error) !== null && _a !== void 0 ? _a : 'KERIA reported W3C request failure'));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return records;
|
|
226
|
+
}
|
|
227
|
+
async handleRequestOnce(request, source) {
|
|
228
|
+
const now = this.now();
|
|
229
|
+
await this.store.purgeTerminal(this.terminalRetentionMs, now);
|
|
230
|
+
const existing = await this.store.get(request.d);
|
|
231
|
+
if (this.shouldSkip(existing, now)) {
|
|
232
|
+
return {
|
|
233
|
+
outcome: 'skipped',
|
|
234
|
+
requestId: request.d,
|
|
235
|
+
source,
|
|
236
|
+
record: existing,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
await this.putRecord(request, W3C_DEDUPE_IN_FLIGHT);
|
|
240
|
+
const ownershipError = await this.localOwnershipError(request);
|
|
241
|
+
if (ownershipError !== undefined) {
|
|
242
|
+
const record = await this.putRecord(request, W3C_DEDUPE_REJECTED, ownershipError);
|
|
243
|
+
return {
|
|
244
|
+
outcome: 'rejected',
|
|
245
|
+
requestId: request.d,
|
|
246
|
+
source,
|
|
247
|
+
record,
|
|
248
|
+
error: ownershipError,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
const signature = await this.signRequest(request);
|
|
253
|
+
await this.w3c.submitSignature(request, signature);
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
257
|
+
const record = await this.putRecord(request, W3C_DEDUPE_FAILED, message);
|
|
258
|
+
return {
|
|
259
|
+
outcome: 'failed',
|
|
260
|
+
requestId: request.d,
|
|
261
|
+
source,
|
|
262
|
+
record,
|
|
263
|
+
error: message,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
const record = await this.putRecord(request, W3C_DEDUPE_SUBMITTED);
|
|
267
|
+
return {
|
|
268
|
+
outcome: 'submitted',
|
|
269
|
+
requestId: request.d,
|
|
270
|
+
source,
|
|
271
|
+
record,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
async signRequest(request) {
|
|
275
|
+
if (![W3C_REQUEST_DATA_INTEGRITY_PROOF, W3C_REQUEST_VC_JWT].includes(request.kind)) {
|
|
276
|
+
throw new Error(`unsupported W3C signing request kind ${request.kind}`);
|
|
277
|
+
}
|
|
278
|
+
const hab = await this.client.identifiers().get(request.name);
|
|
279
|
+
const keeper = this.client.manager.get(hab);
|
|
280
|
+
const sigs = await keeper.sign(decodeBase64Url(request.signingInputB64), false);
|
|
281
|
+
return sigs[0];
|
|
282
|
+
}
|
|
283
|
+
async localOwnershipError(request) {
|
|
284
|
+
let hab;
|
|
285
|
+
try {
|
|
286
|
+
hab = await this.client.identifiers().get(request.name);
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
290
|
+
return `W3C request ${request.d} targets ${request.aid}, but local identifier ${request.name} is unavailable: ${message}`;
|
|
291
|
+
}
|
|
292
|
+
if (hab.prefix !== request.aid) {
|
|
293
|
+
return `W3C request ${request.d} targets ${request.aid}, but local identifier ${request.name} is ${hab.prefix}`;
|
|
294
|
+
}
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
shouldSkip(record, now) {
|
|
298
|
+
if ((record === null || record === void 0 ? void 0 : record.status) === W3C_DEDUPE_IN_FLIGHT &&
|
|
299
|
+
recordAgeMs(record, now) >= this.inFlightRetentionMs) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
return (record !== undefined &&
|
|
303
|
+
[
|
|
304
|
+
W3C_DEDUPE_IN_FLIGHT,
|
|
305
|
+
W3C_DEDUPE_SUBMITTED,
|
|
306
|
+
W3C_DEDUPE_COMPLETE,
|
|
307
|
+
W3C_DEDUPE_FAILED,
|
|
308
|
+
W3C_DEDUPE_REJECTED,
|
|
309
|
+
].includes(record.status));
|
|
310
|
+
}
|
|
311
|
+
async putRecord(request, status, error = null) {
|
|
312
|
+
const record = {
|
|
313
|
+
id: request.d,
|
|
314
|
+
aid: request.aid,
|
|
315
|
+
kind: request.kind,
|
|
316
|
+
status,
|
|
317
|
+
updated: this.now(),
|
|
318
|
+
error,
|
|
319
|
+
};
|
|
320
|
+
await this.store.put(record);
|
|
321
|
+
return record;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function shouldPurgeTerminalRecord(record, maxAgeMs, now) {
|
|
325
|
+
if (!W3C_TERMINAL_DEDUPE_STATUSES.has(record.status)) {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
return recordAgeMs(record, now) >= maxAgeMs;
|
|
329
|
+
}
|
|
330
|
+
function recordAgeMs(record, now) {
|
|
331
|
+
const updated = Date.parse(record.updated);
|
|
332
|
+
const current = Date.parse(now);
|
|
333
|
+
if (Number.isNaN(updated) || Number.isNaN(current)) {
|
|
334
|
+
return 0;
|
|
335
|
+
}
|
|
336
|
+
return current - updated;
|
|
337
|
+
}
|
|
@@ -46,7 +46,18 @@ export interface IdentifierManager<T extends IdentifierManagerParams = Identifie
|
|
|
46
46
|
params(): T;
|
|
47
47
|
incept(transferable: boolean): Promise<IdentifierManagerResult>;
|
|
48
48
|
rotate(ncodes: string[], transferable: boolean, states?: KeyState[], rstates?: KeyState[]): Promise<IdentifierManagerResult>;
|
|
49
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Sign serialized bytes.
|
|
51
|
+
*
|
|
52
|
+
* `rotated` is meaningful for group identifiers only. Callers building a
|
|
53
|
+
* group rotation must pass `rotated=true` so the group signature exposes
|
|
54
|
+
* prior-next `ondex`; non-rotation calls leave it false/current-only.
|
|
55
|
+
*
|
|
56
|
+
* `ondices === undefined` uses the default dual-index behavior where each
|
|
57
|
+
* `ondex` matches its `index`. `ondices[j] === undefined` is the explicit
|
|
58
|
+
* current-only case for that signature, so no `ondex` is emitted.
|
|
59
|
+
*/
|
|
60
|
+
sign(ser: Uint8Array, indexed?: boolean, indices?: number[], ondices?: Array<number | undefined>, rotated?: boolean): Promise<SignResult>;
|
|
50
61
|
}
|
|
51
62
|
/**
|
|
52
63
|
* Creates IdentifierManager instances based on the algorithm and key indexes.
|
|
@@ -102,7 +113,7 @@ export declare class SaltyIdentifierManager implements IdentifierManager {
|
|
|
102
113
|
params(): SaltyManagerParams;
|
|
103
114
|
incept(transferable: boolean): Promise<IdentifierManagerResult>;
|
|
104
115
|
rotate(ncodes: string[], transferable: boolean): Promise<[string[], string[]]>;
|
|
105
|
-
sign(ser: Uint8Array, indexed?: boolean, indices?: number[] | undefined, ondices?: number
|
|
116
|
+
sign(ser: Uint8Array, indexed?: boolean, indices?: number[] | undefined, ondices?: Array<number | undefined> | undefined, _rotated?: boolean): Promise<SignResult>;
|
|
106
117
|
}
|
|
107
118
|
export declare class RandyIdentifierManager implements IdentifierManager {
|
|
108
119
|
private salter;
|
|
@@ -126,27 +137,104 @@ export declare class RandyIdentifierManager implements IdentifierManager {
|
|
|
126
137
|
params(): RandyManagerParams;
|
|
127
138
|
incept(transferable: boolean): Promise<IdentifierManagerResult>;
|
|
128
139
|
rotate(ncodes: string[], transferable: boolean): Promise<IdentifierManagerResult>;
|
|
129
|
-
sign(ser: Uint8Array, indexed?: boolean, indices?: number[] | undefined, ondices?: number
|
|
140
|
+
sign(ser: Uint8Array, indexed?: boolean, indices?: number[] | undefined, ondices?: Array<number | undefined> | undefined, _rotated?: boolean): Promise<SignResult>;
|
|
130
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Identifier manager for group identifiers.
|
|
144
|
+
*
|
|
145
|
+
* A group identifier does not have its own private signing keys. Instead, this
|
|
146
|
+
* manager maps group event signing indexes onto the local member AID (`mhab`)
|
|
147
|
+
* and delegates actual signing to that member's key manager.
|
|
148
|
+
*
|
|
149
|
+
* The central invariant is that indexed rotation signatures carry two
|
|
150
|
+
* positions:
|
|
151
|
+
*
|
|
152
|
+
* - `index` points to the signer's current key in the event's current key list
|
|
153
|
+
* (`k`), which SignifyTS derives from `states`.
|
|
154
|
+
* - `ondex` points to the prior establishment event's next digest list (`n`),
|
|
155
|
+
* proving that the current key was a precommitted key.
|
|
156
|
+
*
|
|
157
|
+
* `rstates` are the proposed next digest source for the new event. They are not
|
|
158
|
+
* the signer authorization set for the event being signed.
|
|
159
|
+
*/
|
|
131
160
|
export declare class GroupIdentifierManager implements IdentifierManager {
|
|
161
|
+
/** Factory used to recover the local member keeper that performs signing. */
|
|
132
162
|
private manager;
|
|
163
|
+
/** Local member AID whose private key signs on behalf of the group. */
|
|
133
164
|
private mhab;
|
|
165
|
+
/**
|
|
166
|
+
* Current group signing public keys for the event being built. During
|
|
167
|
+
* rotation these come from `states[*].k[0]` and define signature `index`.
|
|
168
|
+
*/
|
|
134
169
|
private gkeys;
|
|
170
|
+
/**
|
|
171
|
+
* Proposed next digest list for the event being built. During rotation
|
|
172
|
+
* these come from `rstates[*].n[0]`, are returned from `rotate()`, and are
|
|
173
|
+
* serialized as `group.ndigs`.
|
|
174
|
+
*/
|
|
135
175
|
private gdigs;
|
|
176
|
+
/**
|
|
177
|
+
* Prior next digest list from the current group establishment event. For
|
|
178
|
+
* rotations, this is the authorization commitment set used to derive
|
|
179
|
+
* `ondex`; it must remain separate from proposed next digests in `gdigs`.
|
|
180
|
+
*/
|
|
181
|
+
private gpndigs;
|
|
136
182
|
algo: Algos;
|
|
183
|
+
/**
|
|
184
|
+
* Retained for the `IdentifierManager` interface. Group signing delegates
|
|
185
|
+
* to the local member AID keeper instead of storing signers here.
|
|
186
|
+
*/
|
|
137
187
|
signers: Signer[];
|
|
188
|
+
/**
|
|
189
|
+
* Creates a manager for either a new group event or an existing group AID.
|
|
190
|
+
*
|
|
191
|
+
* `states` and `rstates` are app-level key state records used when creating
|
|
192
|
+
* a new group event. `keys` and `ndigs` are persisted group manager params
|
|
193
|
+
* used when loading an existing group. For an existing group, `ndigs` is
|
|
194
|
+
* expected to be the current establishment event's next digest list.
|
|
195
|
+
*
|
|
196
|
+
* During group inception there is no separate prior establishment event, so
|
|
197
|
+
* `gpndigs` starts as the same digest list as `gdigs`. During rotation,
|
|
198
|
+
* `gdigs` changes to the proposed next digests, while `gpndigs` stays
|
|
199
|
+
* fixed for `ondex` derivation.
|
|
200
|
+
*/
|
|
138
201
|
constructor(manager: IdentifierManagerFactory, mhab: HabState, states?: KeyState[] | undefined, rstates?: KeyState[] | undefined, keys?: string[], ndigs?: string[]);
|
|
202
|
+
/**
|
|
203
|
+
* Returns the inception key material for the group event.
|
|
204
|
+
*
|
|
205
|
+
* Inception has no prior group establishment event. The current keys and
|
|
206
|
+
* proposed next digests are therefore returned directly from the manager's
|
|
207
|
+
* configured `states` and `rstates`.
|
|
208
|
+
*/
|
|
139
209
|
incept(): Promise<IdentifierManagerResult>;
|
|
140
210
|
/**
|
|
141
|
-
* Performs a multisig rotation
|
|
211
|
+
* Performs a multisig rotation.
|
|
212
|
+
*
|
|
213
|
+
* `states` supplies the current group signing keys for the event being
|
|
214
|
+
* signed. `rstates` supplies the proposed next key digests to put in the
|
|
215
|
+
* new event. The prior next digests used for rotation `ondex` lookup stay
|
|
216
|
+
* unchanged until KERIA accepts the new establishment event and returns an
|
|
217
|
+
* updated group state.
|
|
218
|
+
*
|
|
142
219
|
* @param _ncodes
|
|
143
220
|
* @param _transferable
|
|
144
221
|
* @param states
|
|
145
|
-
* @param rstates
|
|
146
|
-
* You should pass in the current key
|
|
222
|
+
* @param rstates
|
|
147
223
|
*/
|
|
148
224
|
rotate(_ncodes: string[], _transferable: boolean, states: KeyState[], rstates: KeyState[]): Promise<IdentifierManagerResult>;
|
|
149
|
-
|
|
225
|
+
/**
|
|
226
|
+
* Signs for the group by delegating to the local member keeper.
|
|
227
|
+
*/
|
|
228
|
+
sign(ser: Uint8Array, indexed?: boolean, _indices?: number[] | undefined, _ondices?: Array<number | undefined> | undefined, rotated?: boolean): Promise<SignResult>;
|
|
229
|
+
/**
|
|
230
|
+
* Finds the prior next digest position that precommitted the current key.
|
|
231
|
+
*
|
|
232
|
+
* This mirrors KERIpy's exposed-prior-next behavior: for each prior next
|
|
233
|
+
* digest, hash the current verifier key using that digest's code and compare
|
|
234
|
+
* the result to the digest at that position. A match means the signer has
|
|
235
|
+
* exposed that precommitted key, and the matching position is the `ondex`.
|
|
236
|
+
*/
|
|
237
|
+
private priorNextIndexForKey;
|
|
150
238
|
params(): {
|
|
151
239
|
mhab: {
|
|
152
240
|
name: string;
|