provared 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1062 -0
- package/LICENSE +202 -0
- package/LICENSE-CC-BY-4.0.txt +396 -0
- package/README.md +689 -0
- package/bin/provared-check.mjs +565 -0
- package/bin/provared-headers.mjs +110 -0
- package/docs/threat-model.md +997 -0
- package/net/headers.js +403 -0
- package/package.json +57 -0
- package/page/check-page.js +162 -0
- package/page/index.html +47 -0
- package/page/render.js +530 -0
- package/page/style.css +150 -0
- package/samples/office-supplies.expected.json +82 -0
- package/samples/office-supplies.jsonl +10 -0
- package/samples/office-supplies.show.json +59 -0
- package/spec/provared-format.md +1672 -0
- package/src/actions.js +90 -0
- package/src/approval.js +73 -0
- package/src/blockstamp.js +198 -0
- package/src/book.js +2232 -0
- package/src/check.d.ts +21 -0
- package/src/check.js +15 -0
- package/src/compare.js +276 -0
- package/src/cover.js +198 -0
- package/src/der.js +192 -0
- package/src/encoding.js +286 -0
- package/src/fields.js +94 -0
- package/src/guard.js +171 -0
- package/src/headers.js +380 -0
- package/src/index.d.ts +2 -0
- package/src/index.js +37 -0
- package/src/jws.js +235 -0
- package/src/keys.js +149 -0
- package/src/pass.js +67 -0
- package/src/passkey-browser.d.ts +20 -0
- package/src/passkey-browser.js +171 -0
- package/src/recorder.js +1115 -0
- package/src/seal.js +133 -0
- package/src/service.js +124 -0
- package/src/signatures.js +147 -0
- package/src/slip.js +401 -0
- package/src/standing.js +181 -0
- package/src/stub.js +140 -0
- package/src/timestamp.js +351 -0
- package/src/tools.js +192 -0
- package/src/tree.js +143 -0
- package/src/types.d.ts +628 -0
- package/src/webauthn.js +119 -0
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
// Type declarations for Provared, draft version 0.
|
|
2
|
+
//
|
|
3
|
+
// These describe the shapes the library takes and returns, so that an editor
|
|
4
|
+
// can check how it is called. They change nothing about how the library
|
|
5
|
+
// runs. The format itself is described in spec/provared-format.md.
|
|
6
|
+
|
|
7
|
+
/** Text in base64url with no padding. */
|
|
8
|
+
export type Base64url = string;
|
|
9
|
+
/** A SHA-256 fingerprint in base64url: 43 characters. */
|
|
10
|
+
export type Fingerprint = string;
|
|
11
|
+
/** A time written as YYYY-MM-DDTHH:MM:SSZ. */
|
|
12
|
+
export type Time = string;
|
|
13
|
+
|
|
14
|
+
// --- keys ---
|
|
15
|
+
|
|
16
|
+
export interface Ed25519Key {
|
|
17
|
+
alg: 'Ed25519';
|
|
18
|
+
crv: 'Ed25519';
|
|
19
|
+
kty: 'OKP';
|
|
20
|
+
x: Base64url;
|
|
21
|
+
}
|
|
22
|
+
export interface MlDsaKey {
|
|
23
|
+
alg: 'ML-DSA-87';
|
|
24
|
+
kty: 'AKP';
|
|
25
|
+
pub: Base64url;
|
|
26
|
+
}
|
|
27
|
+
export interface SlhDsaKey {
|
|
28
|
+
alg: 'SLH-DSA-SHA2-256s';
|
|
29
|
+
kty: 'AKP';
|
|
30
|
+
pub: Base64url;
|
|
31
|
+
}
|
|
32
|
+
export interface Es256Key {
|
|
33
|
+
alg: 'ES256';
|
|
34
|
+
crv: 'P-256';
|
|
35
|
+
kty: 'EC';
|
|
36
|
+
x: Base64url;
|
|
37
|
+
y: Base64url;
|
|
38
|
+
}
|
|
39
|
+
export interface Rs256Key {
|
|
40
|
+
alg: 'RS256';
|
|
41
|
+
e: 'AQAB';
|
|
42
|
+
kty: 'RSA';
|
|
43
|
+
n: Base64url;
|
|
44
|
+
}
|
|
45
|
+
/** The public key of a person's passkey. */
|
|
46
|
+
export type PasskeyKey = Es256Key | Rs256Key | Ed25519Key;
|
|
47
|
+
/** The two keys of an agent, a service or an organisation. */
|
|
48
|
+
export type KeySet = [Ed25519Key, MlDsaKey];
|
|
49
|
+
/** The three keys of a recorder, which signs seals. */
|
|
50
|
+
export type SealKeySet = [Ed25519Key, MlDsaKey, SlhDsaKey];
|
|
51
|
+
export type PublicKey = PasskeyKey | MlDsaKey | SlhDsaKey;
|
|
52
|
+
|
|
53
|
+
// --- what records hold ---
|
|
54
|
+
|
|
55
|
+
export interface Amount {
|
|
56
|
+
unit: string;
|
|
57
|
+
value: number;
|
|
58
|
+
}
|
|
59
|
+
export interface DocumentRef {
|
|
60
|
+
name: string;
|
|
61
|
+
sha256: Fingerprint;
|
|
62
|
+
}
|
|
63
|
+
export type Limit =
|
|
64
|
+
| { action: string; max: number; unit: string; per?: number }
|
|
65
|
+
| { action: string; each: number; unit: string }
|
|
66
|
+
| { action: string; count: number; per?: number };
|
|
67
|
+
export interface Condition {
|
|
68
|
+
need: 'approval' | 'countersignature';
|
|
69
|
+
action?: string;
|
|
70
|
+
above?: number;
|
|
71
|
+
unit?: string;
|
|
72
|
+
}
|
|
73
|
+
export interface Service {
|
|
74
|
+
id: string;
|
|
75
|
+
name: string;
|
|
76
|
+
keys?: KeySet;
|
|
77
|
+
}
|
|
78
|
+
export interface Party {
|
|
79
|
+
keys: KeySet;
|
|
80
|
+
name: string;
|
|
81
|
+
}
|
|
82
|
+
export interface SlipFields {
|
|
83
|
+
id?: Base64url;
|
|
84
|
+
issuer: { name: string; key: PasskeyKey; rpId: string; origin: string };
|
|
85
|
+
agent: { name: string; keys: KeySet; software?: DocumentRef[] };
|
|
86
|
+
actions: string[];
|
|
87
|
+
limits: Limit[];
|
|
88
|
+
/** Defaults to an empty list. */
|
|
89
|
+
requires?: Condition[];
|
|
90
|
+
/** Kinds of action and rules of conduct. Defaults to an empty list. */
|
|
91
|
+
never?: string[];
|
|
92
|
+
with: Service[];
|
|
93
|
+
/** How many times the permission may be passed on to a helper agent: 1 to 10. Absent: not at all. */
|
|
94
|
+
passes?: number;
|
|
95
|
+
validFrom: Time;
|
|
96
|
+
validUntil: Time;
|
|
97
|
+
purpose: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* A slip's content as the checker hands it back. A name or the purpose
|
|
101
|
+
* that is covered, and was not revealed, is absent.
|
|
102
|
+
*/
|
|
103
|
+
export interface SlipContent extends Required<Omit<SlipFields, 'id' | 'passes' | 'issuer' | 'agent' | 'with' | 'purpose'>> {
|
|
104
|
+
issuer: { name?: string; key: PasskeyKey; rpId: string; origin: string };
|
|
105
|
+
agent: { name?: string; keys: KeySet; software?: DocumentRef[] };
|
|
106
|
+
with: { id: string; name?: string; keys?: KeySet }[];
|
|
107
|
+
purpose?: string;
|
|
108
|
+
passes?: number;
|
|
109
|
+
type: 'provared.slip.v0';
|
|
110
|
+
id: Base64url;
|
|
111
|
+
}
|
|
112
|
+
export interface Request {
|
|
113
|
+
action: string;
|
|
114
|
+
amount?: Amount;
|
|
115
|
+
with?: string;
|
|
116
|
+
details?: DocumentRef[];
|
|
117
|
+
}
|
|
118
|
+
export interface StubContent extends Request {
|
|
119
|
+
type: 'provared.stub.v0';
|
|
120
|
+
id: Base64url;
|
|
121
|
+
slip: Fingerprint;
|
|
122
|
+
seq: number;
|
|
123
|
+
previous?: Fingerprint;
|
|
124
|
+
approval?: Fingerprint;
|
|
125
|
+
terms?: Fingerprint;
|
|
126
|
+
/** For a helper agent: the pass it acts under. */
|
|
127
|
+
pass?: Fingerprint;
|
|
128
|
+
when: Time;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** A signed record: a JSON Web Signature in the general JSON serialisation. */
|
|
132
|
+
export interface SignedRecord {
|
|
133
|
+
payload: Base64url;
|
|
134
|
+
signatures: { protected: Base64url; signature: Base64url; header?: { authenticatorData: Base64url; clientDataJSON: Base64url } }[];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** One entry of a book. */
|
|
138
|
+
export type BookEntry =
|
|
139
|
+
| { slip: SignedRecord }
|
|
140
|
+
| { stub: SignedRecord; countersignature?: SignedRecord; approval?: SignedRecord }
|
|
141
|
+
| { refusal: SignedRecord }
|
|
142
|
+
| { terms: SignedRecord }
|
|
143
|
+
| { seal: SignedRecord; stamps?: StampItem[] }
|
|
144
|
+
| { cancellation: SignedRecord; stamps?: StampItem[] }
|
|
145
|
+
| { acknowledgement: SignedRecord }
|
|
146
|
+
| { vouching: SignedRecord }
|
|
147
|
+
| { withdrawal: SignedRecord }
|
|
148
|
+
| { pass: SignedRecord };
|
|
149
|
+
|
|
150
|
+
export interface Show {
|
|
151
|
+
type: 'provared.show.v0';
|
|
152
|
+
size: number;
|
|
153
|
+
root: Fingerprint;
|
|
154
|
+
/** "disclosures" reveal covered fields of the record on that page. */
|
|
155
|
+
pages: { index: number; entry: string; path: Fingerprint[]; disclosures?: string[] }[];
|
|
156
|
+
seal?: { seal: SignedRecord; stamps?: StampItem[] };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// --- what the checker answers ---
|
|
160
|
+
|
|
161
|
+
export interface Finding {
|
|
162
|
+
/** A fixed code in plain words, listed in docs/threat-model.md, section 8. */
|
|
163
|
+
code: string;
|
|
164
|
+
message: string;
|
|
165
|
+
}
|
|
166
|
+
export type SignatureState = 'valid' | 'invalid' | 'unavailable';
|
|
167
|
+
export interface KeySignature {
|
|
168
|
+
method: string;
|
|
169
|
+
state: SignatureState;
|
|
170
|
+
}
|
|
171
|
+
export interface Stamp {
|
|
172
|
+
/** "service": a time-stamp from a time-stamp service. "block": a proof that leads to a block of a public blockchain. */
|
|
173
|
+
kind: 'service' | 'block';
|
|
174
|
+
/** "valid": from a service, or in a block, the checker named. "untrusted": sound, and not named. */
|
|
175
|
+
state: 'valid' | 'untrusted' | 'unavailable' | 'invalid';
|
|
176
|
+
/** The time the service, or the block, states. */
|
|
177
|
+
when: Time | null;
|
|
178
|
+
/** When the record existed by, in milliseconds. For a block: the stated time and two hours. */
|
|
179
|
+
time: number;
|
|
180
|
+
/** The earliest the time-stamp could have been made. For a block: the stated time less two hours. */
|
|
181
|
+
earliest?: number;
|
|
182
|
+
/** The fingerprint of the certificate of the service that signed, or of the block (64 hex characters). */
|
|
183
|
+
authority: string | null;
|
|
184
|
+
/** For a block: its number, as the proof states it. Nothing checks it. */
|
|
185
|
+
height?: number;
|
|
186
|
+
}
|
|
187
|
+
/** A time-stamp beside a seal or a cancellation: a service's answer, or a proof with the header of its block. */
|
|
188
|
+
export type StampItem = Base64url | { block: Base64url; proof: Base64url };
|
|
189
|
+
/** Who vouches for a name, if anyone does. */
|
|
190
|
+
export type Vouched = { counted: boolean; by: string; keys: Fingerprint } | null;
|
|
191
|
+
|
|
192
|
+
export interface CheckedEntry {
|
|
193
|
+
index: number;
|
|
194
|
+
kind: 'slip' | 'stub' | 'refusal' | 'terms' | 'seal' | 'cancellation' | 'acknowledgement' | 'vouching' | 'withdrawal' | 'pass' | 'unreadable';
|
|
195
|
+
fingerprint: Fingerprint | null;
|
|
196
|
+
/** How much of the entry's own signing this device confirmed. */
|
|
197
|
+
verified: 'all' | 'some' | 'none';
|
|
198
|
+
/** Reasons the entry cannot be relied on. */
|
|
199
|
+
problems: Finding[];
|
|
200
|
+
/** What a sound entry shows about the agent. */
|
|
201
|
+
breaches: Finding[];
|
|
202
|
+
notes: string[];
|
|
203
|
+
/**
|
|
204
|
+
* What the entry says. Where the entry has problems, or "verified" is
|
|
205
|
+
* "none", this is unverified: never show it as fact.
|
|
206
|
+
*/
|
|
207
|
+
content?: any;
|
|
208
|
+
/**
|
|
209
|
+
* The time by which the entry is shown to have existed: the earliest that a counted time-stamp on a seal
|
|
210
|
+
* after it states. Absent where no counted time-stamp covers the entry.
|
|
211
|
+
*/
|
|
212
|
+
existedBy?: Time;
|
|
213
|
+
/**
|
|
214
|
+
* A seal or a cancellation: the time its own counted time-stamps give it. Absent where it has none, or
|
|
215
|
+
* did not pass its check. (For a cancellation: the time it counts from.)
|
|
216
|
+
*/
|
|
217
|
+
stampedAt?: Time;
|
|
218
|
+
// A slip:
|
|
219
|
+
/** The fields that are covered and were not revealed, for example "issuer.name". */
|
|
220
|
+
covered?: string[];
|
|
221
|
+
signature?: { method: string; alg: string | null; state: SignatureState | 'unchecked' };
|
|
222
|
+
issuerKey?: string | null;
|
|
223
|
+
/**
|
|
224
|
+
* Who vouches for the names. On a slip: for the issuer, the agent, and
|
|
225
|
+
* each service by its id (null where nobody does). On a seal or a pass:
|
|
226
|
+
* for the recorder or the helper agent.
|
|
227
|
+
*/
|
|
228
|
+
vouched?: { issuer: Vouched; agent: Vouched; services: Record<string, Vouched> } | Vouched;
|
|
229
|
+
// A stub:
|
|
230
|
+
slip?: Fingerprint;
|
|
231
|
+
/** For a helper agent's stub: the pass it was written under. */
|
|
232
|
+
pass?: Fingerprint;
|
|
233
|
+
signatures?: KeySignature[];
|
|
234
|
+
countersignature?: { state: 'absent' | 'unchecked' | SignatureState; signatures: KeySignature[]; when?: Time };
|
|
235
|
+
approval?: { state: 'absent' | SignatureState; when?: Time; alg?: string };
|
|
236
|
+
/** A stub, or a pass: whether it was compared with what it rests on. */
|
|
237
|
+
compared?: boolean;
|
|
238
|
+
needs?: ('approval' | 'countersignature')[];
|
|
239
|
+
running?: { action: string; unit: string; total: string; max: number };
|
|
240
|
+
// A refusal, terms, a seal, a vouching record, a withdrawal:
|
|
241
|
+
signer?: Fingerprint;
|
|
242
|
+
service?: string | null;
|
|
243
|
+
sealer?: Fingerprint;
|
|
244
|
+
stamps?: Stamp[];
|
|
245
|
+
counted?: boolean;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export interface Summary {
|
|
249
|
+
/** No problem was found and every signature was checked. The only answer that is a pass. */
|
|
250
|
+
intact: boolean;
|
|
251
|
+
problemFound: boolean;
|
|
252
|
+
fullyChecked: boolean;
|
|
253
|
+
/** The signing methods this device lacks, if any. */
|
|
254
|
+
methodsMissing: string[];
|
|
255
|
+
/** Every stub and every pass was compared with what it rests on, and none is outside it. */
|
|
256
|
+
withinSlips: boolean;
|
|
257
|
+
firstBreach: number | null;
|
|
258
|
+
counts: {
|
|
259
|
+
slips: number;
|
|
260
|
+
stubs: number;
|
|
261
|
+
countersigned: number;
|
|
262
|
+
oneSided: number;
|
|
263
|
+
approved: number;
|
|
264
|
+
refusals: number;
|
|
265
|
+
terms: number;
|
|
266
|
+
seals: number;
|
|
267
|
+
cancellations: number;
|
|
268
|
+
acknowledgements: number;
|
|
269
|
+
vouchings: number;
|
|
270
|
+
passes: number;
|
|
271
|
+
};
|
|
272
|
+
/** How far the last time-stamp from a trusted service reaches. */
|
|
273
|
+
sealed: { entries: number; when: Time; seal: number; by: 'service' | 'block' } | null;
|
|
274
|
+
/** What no check can show, in plain sentences. */
|
|
275
|
+
limits: string[];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* The person's own copy of a cancellation, as it is handed over: the entry it would be in a book, and with
|
|
280
|
+
* it, where the person was given any, up to four acknowledgements from the agent's side.
|
|
281
|
+
*/
|
|
282
|
+
export interface HandedCancellation {
|
|
283
|
+
cancellation: SignedRecord;
|
|
284
|
+
stamps?: StampItem[];
|
|
285
|
+
acknowledgements?: SignedRecord[];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** The person's own copy of a cancellation, handed over beside the book. */
|
|
289
|
+
export interface HeldCancellation {
|
|
290
|
+
kind: 'cancellation';
|
|
291
|
+
fingerprint: Fingerprint | null;
|
|
292
|
+
slip: Fingerprint | null;
|
|
293
|
+
/** The entry of the book that holds the same cancellation, or null if the book leaves it out. */
|
|
294
|
+
inBook: number | null;
|
|
295
|
+
/** Whether it passed its check and was counted. */
|
|
296
|
+
used: boolean;
|
|
297
|
+
problems: Finding[];
|
|
298
|
+
notes: string[];
|
|
299
|
+
stamps: Stamp[];
|
|
300
|
+
content?: any;
|
|
301
|
+
signature?: { method: string; alg: string; state: SignatureState };
|
|
302
|
+
/** The earliest time a time-stamp from a trusted service gives it. */
|
|
303
|
+
stampedAt?: Time;
|
|
304
|
+
/** The acknowledgements handed over with it: what the agent's side said about being handed it. */
|
|
305
|
+
acknowledgements: HeldAcknowledgement[];
|
|
306
|
+
}
|
|
307
|
+
/** An acknowledgement handed over with the person's own copy of a cancellation. */
|
|
308
|
+
export interface HeldAcknowledgement {
|
|
309
|
+
fingerprint: Fingerprint;
|
|
310
|
+
/** Signed by the slip's own agent, or by a helper agent under a pass. */
|
|
311
|
+
by: 'agent' | 'helper';
|
|
312
|
+
pass: Fingerprint | null;
|
|
313
|
+
state: 'valid' | 'unavailable';
|
|
314
|
+
signatures: KeySignature[];
|
|
315
|
+
/** When the agent's side says it was handed the cancellation: its own word. Null where this device could not check who signed. */
|
|
316
|
+
when: Time | null;
|
|
317
|
+
/** The entry of the book that holds the same acknowledgement, or null. */
|
|
318
|
+
inBook: number | null;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export interface CheckResult {
|
|
322
|
+
format: string;
|
|
323
|
+
size: number;
|
|
324
|
+
root: Fingerprint | null;
|
|
325
|
+
problems: Finding[];
|
|
326
|
+
notes: string[];
|
|
327
|
+
entries: CheckedEntry[];
|
|
328
|
+
/** What was handed over beside the book, and what came of it. */
|
|
329
|
+
held: HeldCancellation[];
|
|
330
|
+
summary: Summary;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export interface CheckOptions {
|
|
334
|
+
/** The thumbprints of the issuer keys the checker expects. */
|
|
335
|
+
issuerKeys?: string[];
|
|
336
|
+
/** The key set fingerprints of the recorders the checker expects. */
|
|
337
|
+
sealKeys?: Fingerprint[];
|
|
338
|
+
/** The certificate fingerprints of the time-stamp services the checker trusts. */
|
|
339
|
+
stampServices?: Fingerprint[];
|
|
340
|
+
/** The fingerprints (64 hex characters) of the blocks of a public blockchain that the checker trusts to be part of the chain. */
|
|
341
|
+
blocks?: string[];
|
|
342
|
+
/** The key set fingerprints of the organisations whose vouching the checker trusts. */
|
|
343
|
+
vouchers?: Fingerprint[];
|
|
344
|
+
/** For covered fields: the disclosures handed over, by the fingerprint of the slip they belong to. */
|
|
345
|
+
disclosures?: Record<Fingerprint, string[]>;
|
|
346
|
+
/**
|
|
347
|
+
* The person's own copies of cancellations, each as the entry it would
|
|
348
|
+
* be in a book. At most 16. They count even where the book leaves them
|
|
349
|
+
* out, or holds them with a later time-stamp.
|
|
350
|
+
*/
|
|
351
|
+
cancellations?: HandedCancellation[];
|
|
352
|
+
expectedRoot?: Fingerprint;
|
|
353
|
+
expectedSize?: number;
|
|
354
|
+
/** Signing methods to treat as not built into this device. It can only withhold a pass. */
|
|
355
|
+
withoutMethods?: string[];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export interface SlipCheck {
|
|
359
|
+
kind: 'slip';
|
|
360
|
+
fingerprint: Fingerprint | null;
|
|
361
|
+
problems: Finding[];
|
|
362
|
+
notes: string[];
|
|
363
|
+
signature: { method: string; alg: string | null; state: SignatureState | 'unchecked' };
|
|
364
|
+
issuerKey: string | null;
|
|
365
|
+
content: SlipContent | null;
|
|
366
|
+
covered: string[];
|
|
367
|
+
/**
|
|
368
|
+
* Why the disclosures handed over were not used, if they were not. The
|
|
369
|
+
* slip is then checked with those fields covered.
|
|
370
|
+
*/
|
|
371
|
+
disclosureProblems: Finding[];
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// --- checking ---
|
|
375
|
+
|
|
376
|
+
export function checkBook(text: string, options?: CheckOptions): Promise<CheckResult>;
|
|
377
|
+
/**
|
|
378
|
+
* A checker that can be carried on: it reads a book once, and then takes more lines as the book grows,
|
|
379
|
+
* without reading the earlier lines again. Its answer is the answer checkBook gives for the same text.
|
|
380
|
+
*/
|
|
381
|
+
export interface Checker {
|
|
382
|
+
/** Takes more whole lines, each ending with a line feed. */
|
|
383
|
+
add(text: string): Promise<void>;
|
|
384
|
+
/** The answer for the book so far: a copy of its own each time. */
|
|
385
|
+
result(): Promise<CheckResult>;
|
|
386
|
+
}
|
|
387
|
+
/** The options are fixed when the checker is opened. The book so far may be empty. */
|
|
388
|
+
export function openChecker(text?: string, options?: CheckOptions): Promise<Checker>;
|
|
389
|
+
export function checkShow(show: Show | string, options?: CheckOptions): Promise<CheckResult>;
|
|
390
|
+
export function checkSlip(record: SignedRecord, options?: Pick<CheckOptions, 'issuerKeys' | 'withoutMethods' | 'disclosures'>, disclosures?: string[]): Promise<SlipCheck>;
|
|
391
|
+
export function methodAvailable(alg: string): Promise<boolean>;
|
|
392
|
+
export function thumbprint(key: PublicKey): Promise<string>;
|
|
393
|
+
export function keySetFingerprint(keys: KeySet | SealKeySet): Promise<Fingerprint>;
|
|
394
|
+
/** The fingerprint of a block, as it is written everywhere: 64 hex characters. */
|
|
395
|
+
export function blockFingerprint(header: Uint8Array): Promise<string>;
|
|
396
|
+
/** A chain of Bitcoin block headers, checked on this device from the chain's first block. */
|
|
397
|
+
export interface HeaderChain {
|
|
398
|
+
/** The number of the latest block. */
|
|
399
|
+
height: number;
|
|
400
|
+
/** Its fingerprint, 64 hex characters. */
|
|
401
|
+
tip: string;
|
|
402
|
+
/** The time it states. */
|
|
403
|
+
tipWhen: Time;
|
|
404
|
+
/** A block's number, and how many blocks come after it in this chain; null where the chain does not hold it. */
|
|
405
|
+
find(fingerprint: string): { height: number; after: number } | null;
|
|
406
|
+
}
|
|
407
|
+
/** Check a chain of block headers held in a file. Throws a Refusal "headers-invalid" at the first header that breaks a rule. */
|
|
408
|
+
export function checkHeaderChain(bytes: Uint8Array, options?: { now?: number }): Promise<HeaderChain>;
|
|
409
|
+
/** The blocks a check's block time-stamps lead to that a checked chain holds with at least MIN_BLOCKS_AFTER blocks after them. */
|
|
410
|
+
export function blocksInChain(
|
|
411
|
+
result: CheckResult,
|
|
412
|
+
chain: Pick<HeaderChain, 'find'>,
|
|
413
|
+
): { blocks: string[]; found: { block: string; stated: number; height: number | null; after: number | null; counted: boolean }[] };
|
|
414
|
+
/** A block counts as part of the chain only with at least this many blocks after it. */
|
|
415
|
+
export const MIN_BLOCKS_AFTER: number;
|
|
416
|
+
export function limitWords(limit: Limit): string;
|
|
417
|
+
export function conditionWords(condition: Condition): string;
|
|
418
|
+
export function neverWords(name: string): string;
|
|
419
|
+
export const LIMITS: string[];
|
|
420
|
+
export const REFUSAL_REASONS: Record<string, string>;
|
|
421
|
+
|
|
422
|
+
export interface Proposal extends Request {
|
|
423
|
+
slip: Fingerprint;
|
|
424
|
+
pass?: Fingerprint;
|
|
425
|
+
approval?: SignedRecord;
|
|
426
|
+
terms?: Fingerprint;
|
|
427
|
+
when?: number | Date;
|
|
428
|
+
}
|
|
429
|
+
export interface BeforeAnswer {
|
|
430
|
+
allowed: boolean;
|
|
431
|
+
problems: Finding[];
|
|
432
|
+
breaches: Finding[];
|
|
433
|
+
needs: ('approval' | 'countersignature')[];
|
|
434
|
+
}
|
|
435
|
+
/** "issuerKeys" must be given: a slip signed by any other key allows nothing. */
|
|
436
|
+
export function checkBefore(book: string, proposal: Proposal, options: CheckOptions & { issuerKeys: string[] }): Promise<BeforeAnswer>;
|
|
437
|
+
|
|
438
|
+
/** The stub writer beside an agent: ask first, act, write the stub. */
|
|
439
|
+
export interface Recorder {
|
|
440
|
+
before(request: Request, more?: { when?: number | Date; terms?: Fingerprint; approval?: { record: SignedRecord } }): Promise<BeforeAnswer>;
|
|
441
|
+
record(
|
|
442
|
+
request: Request,
|
|
443
|
+
more?: { when?: number | Date; terms?: Fingerprint; approval?: { record: SignedRecord }; countersign?: (stub: SignedRecord) => Promise<SignedRecord | null> },
|
|
444
|
+
): Promise<WrittenStub>;
|
|
445
|
+
/**
|
|
446
|
+
* If the action is not allowed it is not taken and nothing is written. Calls wait for one another.
|
|
447
|
+
* The action is taken now: "when" defaults to the clock and must be within 300 seconds of it.
|
|
448
|
+
*/
|
|
449
|
+
act<T>(
|
|
450
|
+
request: Request,
|
|
451
|
+
perform: () => Promise<T>,
|
|
452
|
+
more?: { when?: number | Date; terms?: Fingerprint; approval?: { record: SignedRecord }; countersign?: (stub: SignedRecord) => Promise<SignedRecord | null> },
|
|
453
|
+
): Promise<{ done: boolean; answer: BeforeAnswer; result?: T; stub?: WrittenStub }>;
|
|
454
|
+
book(): string;
|
|
455
|
+
/**
|
|
456
|
+
* What the record shows as it stands: the answer a whole check of the book gives, with the cancellations
|
|
457
|
+
* the writer holds beside it. Nothing is read again. A copy of its own each time.
|
|
458
|
+
*/
|
|
459
|
+
check(): Promise<CheckResult>;
|
|
460
|
+
/**
|
|
461
|
+
* The person's cancellations the writer holds beside its book: those it was given when opened, and those of
|
|
462
|
+
* its own slip that it could not yet write into the book, each with the acknowledgement the writer signed
|
|
463
|
+
* for it; and a cancellation whose acknowledgement still waits to follow it into the book, with that
|
|
464
|
+
* acknowledgement. Keep them with the book, and hand them in again ("options.cancellations") when a
|
|
465
|
+
* writer is opened again from it.
|
|
466
|
+
*/
|
|
467
|
+
cancellations(): HandedCancellation[];
|
|
468
|
+
/**
|
|
469
|
+
* Adds an entry someone else made, only if the book still passes its check with it. A cancellation that
|
|
470
|
+
* cannot be added as it was handed over is added by itself where that passes, or kept beside the book if
|
|
471
|
+
* the person signed it for this writer's slip; the call still fails, and the error says what was done.
|
|
472
|
+
* For the person's cancellation of this writer's own slip, the writer signs an acknowledgement, to be
|
|
473
|
+
* given to the person: it is in the answer, or in the "acknowledgement" member of the error.
|
|
474
|
+
*/
|
|
475
|
+
add(entry: BookEntry): Promise<{ acknowledgement?: SignedRecord }>;
|
|
476
|
+
}
|
|
477
|
+
/** A stub the stub writer wrote. A countersignature that did not check is left out, and "problem" says why. */
|
|
478
|
+
export type WrittenStub = Written & { seq: number; line: string; countersignature: { accepted: boolean; problem: Finding | null } };
|
|
479
|
+
export function openRecorder(o: {
|
|
480
|
+
book: string;
|
|
481
|
+
slip: Fingerprint;
|
|
482
|
+
privateKeys: CryptoKey[];
|
|
483
|
+
issuerKeys: string[];
|
|
484
|
+
pass?: Fingerprint;
|
|
485
|
+
options?: CheckOptions;
|
|
486
|
+
/** The clock, in milliseconds. Defaults to the device's. "act" takes an action at its time, and nothing is written dated more than 300 seconds ahead of it. */
|
|
487
|
+
now?: () => number;
|
|
488
|
+
/** How long the other side is given to countersign, in milliseconds. Defaults to 30,000. */
|
|
489
|
+
countersignWithin?: number;
|
|
490
|
+
}): Promise<Recorder>;
|
|
491
|
+
|
|
492
|
+
/** One of an agent's tools, as it is described to recordTools. */
|
|
493
|
+
export interface RecordedTool<A = any, R = any> {
|
|
494
|
+
/** The action name the slip uses for what this tool does. */
|
|
495
|
+
action: string;
|
|
496
|
+
/** The tool itself. It must not call the stub writer. */
|
|
497
|
+
run: (args: A) => R | Promise<R>;
|
|
498
|
+
/** The id of the service the tool deals with, as the slip names it. */
|
|
499
|
+
with?: string | ((args: A) => string | undefined);
|
|
500
|
+
amount?: Amount | ((args: A) => Amount | undefined);
|
|
501
|
+
/** The documents to name in the stub, or false for none. By default: "arguments", the fingerprint of the arguments. */
|
|
502
|
+
details?: false | ((args: A) => DocumentRef[] | Promise<DocumentRef[]>);
|
|
503
|
+
/** Asks the other side to countersign the stub. */
|
|
504
|
+
countersign?: (stub: SignedRecord, args: A) => Promise<SignedRecord | null>;
|
|
505
|
+
/** Asks the person for their approval, where the slip asks for one. */
|
|
506
|
+
approve?: (request: Request, args: A) => Promise<{ record: SignedRecord } | null | undefined>;
|
|
507
|
+
}
|
|
508
|
+
/** A call through a recorded tool that was not run. "answer" is what the check before acting said. */
|
|
509
|
+
export class NotTaken extends Error {
|
|
510
|
+
constructor(tool: string, answer: BeforeAnswer);
|
|
511
|
+
tool: string;
|
|
512
|
+
answer: BeforeAnswer;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Puts an agent's tools behind the stub writer: each call asks first, runs the tool only if the slip allows
|
|
516
|
+
* the action, and writes the stub. A call that is not allowed throws NotTaken, and the tool is not run.
|
|
517
|
+
*/
|
|
518
|
+
export function recordTools<T extends Record<string, RecordedTool>>(
|
|
519
|
+
recorder: Recorder,
|
|
520
|
+
tools: T,
|
|
521
|
+
options?: { onStub?: (stub: WrittenStub, tool: string) => void | Promise<void> },
|
|
522
|
+
): { [K in keyof T]: (args?: Parameters<T[K]['run']>[0]) => Promise<Awaited<ReturnType<T[K]['run']>>> };
|
|
523
|
+
/** The fingerprint of the arguments of a call: SHA-256 of the arguments written in the one form of RFC 8785. */
|
|
524
|
+
export function argumentsFingerprint(args: unknown): Promise<Fingerprint>;
|
|
525
|
+
|
|
526
|
+
// --- writing ---
|
|
527
|
+
|
|
528
|
+
export interface Prepared {
|
|
529
|
+
contentBytes: Uint8Array;
|
|
530
|
+
payloadB64: Base64url;
|
|
531
|
+
protectedB64: Base64url;
|
|
532
|
+
/** What the person's passkey must sign. */
|
|
533
|
+
challenge: Uint8Array;
|
|
534
|
+
}
|
|
535
|
+
export interface Assertion {
|
|
536
|
+
authenticatorData: Uint8Array;
|
|
537
|
+
clientDataJSON: Uint8Array;
|
|
538
|
+
signature: Uint8Array;
|
|
539
|
+
}
|
|
540
|
+
export interface Written {
|
|
541
|
+
record: SignedRecord;
|
|
542
|
+
fingerprint: Fingerprint;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/** The fields of a slip that may be covered. */
|
|
546
|
+
export const COVERABLE: string[];
|
|
547
|
+
/** "disclosures" reveal the covered fields: keep them beside the slip, and hand over only the ones a reader needs. */
|
|
548
|
+
export function prepareSlip(fields: SlipFields, options?: { cover?: string[] }): Promise<Prepared & { disclosures: string[] }>;
|
|
549
|
+
export function assembleSlip(prepared: Pick<Prepared, 'payloadB64' | 'protectedB64'>, assertion: Assertion): SignedRecord;
|
|
550
|
+
export function prepareApproval(fields: Request & { slip: Fingerprint; when?: number | Date; id?: Base64url }): Promise<Prepared & { fingerprint: Fingerprint }>;
|
|
551
|
+
export function assembleApproval(prepared: Pick<Prepared, 'payloadB64' | 'protectedB64'>, assertion: Assertion): SignedRecord;
|
|
552
|
+
export function prepareCancellation(fields: { slip: Fingerprint; when?: number | Date; id?: Base64url }): Promise<Prepared & { fingerprint: Fingerprint }>;
|
|
553
|
+
export function assembleCancellation(prepared: Pick<Prepared, 'payloadB64' | 'protectedB64'>, assertion: Assertion): SignedRecord;
|
|
554
|
+
|
|
555
|
+
export function writeStub(
|
|
556
|
+
fields: Request & {
|
|
557
|
+
slip: Fingerprint;
|
|
558
|
+
after: { seq: number; fingerprint: Fingerprint } | null;
|
|
559
|
+
approval?: Fingerprint;
|
|
560
|
+
terms?: Fingerprint;
|
|
561
|
+
pass?: Fingerprint;
|
|
562
|
+
when?: number | Date;
|
|
563
|
+
id?: Base64url;
|
|
564
|
+
},
|
|
565
|
+
privateKeys: CryptoKey[],
|
|
566
|
+
): Promise<Written & { seq: number }>;
|
|
567
|
+
/** An agent hands part of its permission to a helper agent. */
|
|
568
|
+
export function writePass(
|
|
569
|
+
fields: { slip: Fingerprint; from?: Fingerprint; to: Party; actions: string[]; limits?: Limit[]; validFrom: Time; validUntil: Time; when?: number | Date; id?: Base64url },
|
|
570
|
+
privateKeys: CryptoKey[],
|
|
571
|
+
): Promise<Written>;
|
|
572
|
+
export function countersign(stub: SignedRecord, privateKeys: CryptoKey[], when?: number | Date): Promise<SignedRecord>;
|
|
573
|
+
export function writeRefusal(
|
|
574
|
+
fields: { slip: Fingerprint; by: Party; action: string; amount?: Amount; reason: string; when?: number | Date; id?: Base64url },
|
|
575
|
+
privateKeys: CryptoKey[],
|
|
576
|
+
): Promise<Written>;
|
|
577
|
+
export function writeTerms(
|
|
578
|
+
fields: { by: Party; accepts: string[]; never?: string[]; validFrom: Time; validUntil: Time; id?: Base64url },
|
|
579
|
+
privateKeys: CryptoKey[],
|
|
580
|
+
): Promise<Written>;
|
|
581
|
+
export type VouchedFor =
|
|
582
|
+
| { kind: 'person'; key: PasskeyKey; name: string }
|
|
583
|
+
| { kind: 'agent' | 'service'; keys: KeySet; name: string }
|
|
584
|
+
| { kind: 'recorder'; keys: SealKeySet; name: string };
|
|
585
|
+
export function writeVouching(
|
|
586
|
+
fields: { by: Party; for: VouchedFor; validFrom: Time; validUntil: Time; when?: number | Date; id?: Base64url },
|
|
587
|
+
privateKeys: CryptoKey[],
|
|
588
|
+
): Promise<Written>;
|
|
589
|
+
export function writeWithdrawal(fields: { vouching: Fingerprint; when?: number | Date; id?: Base64url }, privateKeys: CryptoKey[]): Promise<Written>;
|
|
590
|
+
/** The agent's side states that it was handed the person's cancellation of a slip, and when. */
|
|
591
|
+
export function writeAcknowledgement(
|
|
592
|
+
fields: { slip: Fingerprint; cancellation: Fingerprint; pass?: Fingerprint; when?: number | Date; id?: Base64url },
|
|
593
|
+
privateKeys: CryptoKey[],
|
|
594
|
+
): Promise<Written>;
|
|
595
|
+
/** It needs Node.js: no browser has the seal's third signing method. */
|
|
596
|
+
export function writeSeal(
|
|
597
|
+
book: string,
|
|
598
|
+
fields: { by: { keys: SealKeySet; name: string }; previous?: Fingerprint; when?: number | Date; id?: Base64url },
|
|
599
|
+
privateKeys: unknown[],
|
|
600
|
+
): Promise<Written & { fingerprintBytes: Uint8Array }>;
|
|
601
|
+
|
|
602
|
+
/** A proof from a public blockchain and the 80-byte header of its block, in the form they have in "stamps". */
|
|
603
|
+
export function blockStampItem(proof: Uint8Array, header: Uint8Array): { block: Base64url; proof: Base64url };
|
|
604
|
+
|
|
605
|
+
export function entryLine(entry: BookEntry): string;
|
|
606
|
+
export function writeBook(entries: BookEntry[]): string;
|
|
607
|
+
export function makeShow(text: string, indexes: number[], options?: { seal?: number; disclosures?: Record<number, string[]> }): Promise<Show>;
|
|
608
|
+
|
|
609
|
+
// --- keys and small tools ---
|
|
610
|
+
|
|
611
|
+
export function generateKeySet(): Promise<{ keys: KeySet; privateKeys: CryptoKey[] }>;
|
|
612
|
+
export function generateSealKeySet(): Promise<{ keys: SealKeySet; privateKeys: unknown[] }>;
|
|
613
|
+
export function fingerprint(contentBytes: Uint8Array): Promise<Fingerprint>;
|
|
614
|
+
export function sha256(bytes: Uint8Array): Promise<Uint8Array>;
|
|
615
|
+
export function formatTime(when: number | Date): Time;
|
|
616
|
+
export function randomId(): Base64url;
|
|
617
|
+
export function toBase64url(bytes: Uint8Array): Base64url;
|
|
618
|
+
export function fromBase64url(text: Base64url): Uint8Array;
|
|
619
|
+
export function utf8(text: string): Uint8Array;
|
|
620
|
+
|
|
621
|
+
// --- the shared list ---
|
|
622
|
+
|
|
623
|
+
export const RESERVED_PREFIX: 'provared.';
|
|
624
|
+
export const ACTION_KINDS: Record<string, string>;
|
|
625
|
+
export const SHARED_ACTIONS: Record<string, [kind: string, meaning: string]>;
|
|
626
|
+
export const CONDUCT_RULES: Record<string, string>;
|
|
627
|
+
export const VOUCHED_KINDS: Record<string, string>;
|
|
628
|
+
export function actionKind(name: string): string | null;
|