provared 0.5.0 → 0.7.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 +1496 -0
- package/README.md +18 -3
- package/bin/provared-check.mjs +128 -13
- package/docs/guide.md +346 -24
- package/docs/threat-model.md +299 -21
- package/net/headers.js +1 -0
- package/package.json +2 -2
- package/page/check-page.js +18 -32
- package/page/index.html +6 -0
- package/page/render.js +183 -10
- package/spec/provared-format.md +771 -37
- package/src/book.js +544 -19
- package/src/check.d.ts +4 -0
- package/src/check.js +3 -1
- package/src/denial.js +123 -0
- package/src/der.js +56 -6
- package/src/dev.d.ts +1 -1
- package/src/dev.js +1 -1
- package/src/documents.js +8 -3
- package/src/guard.js +12 -1
- package/src/index.js +10 -0
- package/src/jws.js +8 -0
- package/src/keys.js +48 -1
- package/src/recorder.js +131 -5
- package/src/seal.js +32 -4
- package/src/sealer.js +812 -0
- package/src/signatures.js +12 -6
- package/src/slip.js +72 -5
- package/src/timestamp.js +55 -16
- package/src/tools.js +30 -13
- package/src/tree.js +139 -7
- package/src/types.d.ts +272 -11
- package/src/witness.js +63 -0
- package/src/witnessing.js +362 -0
package/src/types.d.ts
CHANGED
|
@@ -155,9 +155,10 @@ export type BookEntry =
|
|
|
155
155
|
| { stub: SignedRecord; countersignature?: SignedRecord; approval?: SignedRecord }
|
|
156
156
|
| { refusal: SignedRecord }
|
|
157
157
|
| { terms: SignedRecord }
|
|
158
|
-
| { seal: SignedRecord; stamps?: StampItem[] }
|
|
158
|
+
| { seal: SignedRecord; stamps?: StampItem[]; witnesses?: SignedRecord[] }
|
|
159
159
|
| { cancellation: SignedRecord; stamps?: StampItem[] }
|
|
160
160
|
| { acknowledgement: SignedRecord }
|
|
161
|
+
| { denial: SignedRecord }
|
|
161
162
|
| { vouching: SignedRecord }
|
|
162
163
|
| { withdrawal: SignedRecord }
|
|
163
164
|
| { pass: SignedRecord };
|
|
@@ -168,7 +169,7 @@ export interface Show {
|
|
|
168
169
|
root: Fingerprint;
|
|
169
170
|
/** "disclosures" reveal covered fields of the record on that page. */
|
|
170
171
|
pages: { index: number; entry: string; path: Fingerprint[]; disclosures?: string[] }[];
|
|
171
|
-
seal?: { seal: SignedRecord; stamps?: StampItem[] };
|
|
172
|
+
seal?: { seal: SignedRecord; stamps?: StampItem[]; witnesses?: SignedRecord[] };
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
// --- what the checker answers ---
|
|
@@ -199,6 +200,28 @@ export interface Stamp {
|
|
|
199
200
|
/** For a block: its number, as the proof states it. Nothing checks it. */
|
|
200
201
|
height?: number;
|
|
201
202
|
}
|
|
203
|
+
/** A witness statement beside a seal, as the checker found it (format description, section 29). */
|
|
204
|
+
export interface WitnessCheck {
|
|
205
|
+
/** The statement's own fingerprint. */
|
|
206
|
+
fingerprint: Fingerprint | null;
|
|
207
|
+
/** The fingerprint of the witness's key set: how the person names a witness they trust. */
|
|
208
|
+
witness: Fingerprint | null;
|
|
209
|
+
/** The witness's name, a label. */
|
|
210
|
+
name: string | null;
|
|
211
|
+
/** When the witness says it signed: its own word. */
|
|
212
|
+
when: Time | null;
|
|
213
|
+
/** "valid": it passed its check and both its signatures were checked here. "unavailable": this device could not check them both. */
|
|
214
|
+
state: 'valid' | 'unavailable' | 'invalid';
|
|
215
|
+
/** Whether its witness is one the person named. */
|
|
216
|
+
trusted: boolean;
|
|
217
|
+
/**
|
|
218
|
+
* Whether it counts: valid, trusted, its seal passed its own check with its first two signatures checked here, the book
|
|
219
|
+
* starts with a permission signed by a passkey you named, you named the recorders you expect, and every permission the
|
|
220
|
+
* seal covers names the first line's passkey and agent (a Show must hold every page the seal covers). Section 29.2.
|
|
221
|
+
*/
|
|
222
|
+
counted: boolean;
|
|
223
|
+
signatures: KeySignature[];
|
|
224
|
+
}
|
|
202
225
|
/** A time-stamp beside a seal or a cancellation: a service's answer, or a proof with the header of its block. */
|
|
203
226
|
export type StampItem = Base64url | { block: Base64url; proof: Base64url };
|
|
204
227
|
/** Who vouches for a name, if anyone does. */
|
|
@@ -206,7 +229,7 @@ export type Vouched = { counted: boolean; by: string; keys: Fingerprint } | null
|
|
|
206
229
|
|
|
207
230
|
export interface CheckedEntry {
|
|
208
231
|
index: number;
|
|
209
|
-
kind: 'slip' | 'stub' | 'refusal' | 'terms' | 'seal' | 'cancellation' | 'acknowledgement' | 'vouching' | 'withdrawal' | 'pass' | 'unreadable';
|
|
232
|
+
kind: 'slip' | 'stub' | 'refusal' | 'terms' | 'seal' | 'cancellation' | 'acknowledgement' | 'vouching' | 'withdrawal' | 'pass' | 'denial' | 'unreadable';
|
|
210
233
|
fingerprint: Fingerprint | null;
|
|
211
234
|
/** How much of the entry's own signing this device confirmed. */
|
|
212
235
|
verified: 'all' | 'some' | 'none';
|
|
@@ -257,6 +280,8 @@ export interface CheckedEntry {
|
|
|
257
280
|
service?: string | null;
|
|
258
281
|
sealer?: Fingerprint;
|
|
259
282
|
stamps?: Stamp[];
|
|
283
|
+
/** A seal: the witness statements on its line. */
|
|
284
|
+
witnesses?: WitnessCheck[];
|
|
260
285
|
counted?: boolean;
|
|
261
286
|
}
|
|
262
287
|
|
|
@@ -281,11 +306,18 @@ export interface Summary {
|
|
|
281
306
|
seals: number;
|
|
282
307
|
cancellations: number;
|
|
283
308
|
acknowledgements: number;
|
|
309
|
+
denials: number;
|
|
284
310
|
vouchings: number;
|
|
285
311
|
passes: number;
|
|
286
312
|
};
|
|
287
313
|
/** How far the last time-stamp from a trusted service reaches. */
|
|
288
314
|
sealed: { entries: number; when: Time; seal: number; by: 'service' | 'block' } | null;
|
|
315
|
+
/**
|
|
316
|
+
* How far a witness the person named reaches: the latest seal for which a statement counts, the entries it covers,
|
|
317
|
+
* the witnesses whose statements count for it, and every seal for which one counts. Null where none does, or where a
|
|
318
|
+
* problem was found. It excuses nothing.
|
|
319
|
+
*/
|
|
320
|
+
witnessed: { seal: number; entries: number; by: Fingerprint[]; seals: number[] } | null;
|
|
289
321
|
/** What no check can show, in plain sentences. */
|
|
290
322
|
limits: string[];
|
|
291
323
|
}
|
|
@@ -333,6 +365,46 @@ export interface HeldAcknowledgement {
|
|
|
333
365
|
inBook: number | null;
|
|
334
366
|
}
|
|
335
367
|
|
|
368
|
+
/** A seal held elsewhere, as it is handed over: the entry it is in the book. */
|
|
369
|
+
export interface HandedSeal {
|
|
370
|
+
seal: SignedRecord;
|
|
371
|
+
stamps?: StampItem[];
|
|
372
|
+
/** Witness statements for the seal, 1 to 4, none from one witness twice: those beside it in the book, or that came later. */
|
|
373
|
+
witnesses?: SignedRecord[];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* A seal held elsewhere (by the person, an auditor, a copy kept apart), handed over beside the book. The book
|
|
378
|
+
* must hold the same seal at the place it states: a book that ends before that place was cut short
|
|
379
|
+
* ("book-cut-short"), and one that holds another line there was changed ("seal-not-in-book").
|
|
380
|
+
*/
|
|
381
|
+
export interface HeldSeal {
|
|
382
|
+
kind: 'seal';
|
|
383
|
+
fingerprint: Fingerprint | null;
|
|
384
|
+
/** The fingerprint of the key set that signed it. */
|
|
385
|
+
sealer: Fingerprint | null;
|
|
386
|
+
/** The entry of the book at which it stands: the number of entries it covers. Null where it could not be read. */
|
|
387
|
+
place: number | null;
|
|
388
|
+
/** That entry, where the book holds the same seal there; null otherwise, or where it was not used. */
|
|
389
|
+
inBook: number | null;
|
|
390
|
+
/**
|
|
391
|
+
* Whether it was compared with the book: it passed its check, its recorder is one you named, this device checked its
|
|
392
|
+
* first two signatures, and it gives the book's own number (section 21.6). One that was not compared gives no pass.
|
|
393
|
+
*/
|
|
394
|
+
used: boolean;
|
|
395
|
+
verified: 'all' | 'some' | 'none';
|
|
396
|
+
problems: Finding[];
|
|
397
|
+
notes: string[];
|
|
398
|
+
signatures: KeySignature[];
|
|
399
|
+
/** Its own time-stamps: checked and shown, not used for the entries of the book. */
|
|
400
|
+
stamps: Stamp[];
|
|
401
|
+
/** The witness statements handed over with it. One that counts, for a seal the book holds at its place, counts for that seal. */
|
|
402
|
+
witnesses: WitnessCheck[];
|
|
403
|
+
content?: any;
|
|
404
|
+
/** The earliest time a time-stamp from a trusted service gives it, where all three of its signatures were checked. */
|
|
405
|
+
stampedAt?: Time;
|
|
406
|
+
}
|
|
407
|
+
|
|
336
408
|
export interface CheckResult {
|
|
337
409
|
format: string;
|
|
338
410
|
size: number;
|
|
@@ -340,14 +412,21 @@ export interface CheckResult {
|
|
|
340
412
|
problems: Finding[];
|
|
341
413
|
notes: string[];
|
|
342
414
|
entries: CheckedEntry[];
|
|
343
|
-
/** What was handed over beside the book, and what came of it. */
|
|
344
|
-
held: HeldCancellation[];
|
|
415
|
+
/** What was handed over beside the book, and what came of it: the cancellations first, then the seals. */
|
|
416
|
+
held: (HeldCancellation | HeldSeal)[];
|
|
345
417
|
summary: Summary;
|
|
346
418
|
}
|
|
347
419
|
|
|
348
420
|
export interface CheckOptions {
|
|
349
421
|
/** The thumbprints of the issuer keys the checker expects. */
|
|
350
422
|
issuerKeys?: string[];
|
|
423
|
+
/**
|
|
424
|
+
* The addresses of the pages at which the checker expects every slip to
|
|
425
|
+
* have been signed, each in the form of the format description's section
|
|
426
|
+
* 4.1 (https://, a website name in lower case, a port only if needed). At
|
|
427
|
+
* most 64. A slip signed at any other address is refused.
|
|
428
|
+
*/
|
|
429
|
+
issuerOrigins?: string[];
|
|
351
430
|
/** The key set fingerprints of the recorders the checker expects. */
|
|
352
431
|
sealKeys?: Fingerprint[];
|
|
353
432
|
/** The certificate fingerprints of the time-stamp services the checker trusts. */
|
|
@@ -356,6 +435,8 @@ export interface CheckOptions {
|
|
|
356
435
|
blocks?: string[];
|
|
357
436
|
/** The key set fingerprints of the organisations whose vouching the checker trusts. */
|
|
358
437
|
vouchers?: Fingerprint[];
|
|
438
|
+
/** The key set fingerprints of the witnesses the checker trusts (format description, section 29). */
|
|
439
|
+
witnesses?: Fingerprint[];
|
|
359
440
|
/** For covered fields: the disclosures handed over, by the fingerprint of the slip they belong to. */
|
|
360
441
|
disclosures?: Record<Fingerprint, string[]>;
|
|
361
442
|
/**
|
|
@@ -364,6 +445,11 @@ export interface CheckOptions {
|
|
|
364
445
|
* out, or holds them with a later time-stamp.
|
|
365
446
|
*/
|
|
366
447
|
cancellations?: HandedCancellation[];
|
|
448
|
+
/**
|
|
449
|
+
* Seals held elsewhere, each as the entry it is in the book. At most 64. The book must hold each at the
|
|
450
|
+
* place it states. Each counts only where its recorder is one named in "sealKeys".
|
|
451
|
+
*/
|
|
452
|
+
seals?: HandedSeal[];
|
|
367
453
|
expectedRoot?: Fingerprint;
|
|
368
454
|
expectedSize?: number;
|
|
369
455
|
/** Signing methods to treat as not built into this device. It can only withhold a pass. */
|
|
@@ -402,10 +488,24 @@ export interface Checker {
|
|
|
402
488
|
/** The options are fixed when the checker is opened. The book so far may be empty. */
|
|
403
489
|
export function openChecker(text?: string, options?: CheckOptions): Promise<Checker>;
|
|
404
490
|
export function checkShow(show: Show | string, options?: CheckOptions): Promise<CheckResult>;
|
|
405
|
-
export function checkSlip(record: SignedRecord, options?: Pick<CheckOptions, 'issuerKeys' | 'withoutMethods' | 'disclosures'>, disclosures?: string[]): Promise<SlipCheck>;
|
|
491
|
+
export function checkSlip(record: SignedRecord, options?: Pick<CheckOptions, 'issuerKeys' | 'issuerOrigins' | 'withoutMethods' | 'disclosures'>, disclosures?: string[]): Promise<SlipCheck>;
|
|
492
|
+
/**
|
|
493
|
+
* Whether a text is the address of a page in the one form the format allows for a slip's "issuer.origin" (format
|
|
494
|
+
* description, section 4.1), which is also the form each address in "issuerOrigins" must have: https:// (or
|
|
495
|
+
* http://localhost), a website name in lower case, a port only where it is needed, and nothing more.
|
|
496
|
+
*/
|
|
497
|
+
export function isPageAddress(text: unknown): boolean;
|
|
406
498
|
export function methodAvailable(alg: string): Promise<boolean>;
|
|
407
499
|
export function thumbprint(key: PublicKey): Promise<string>;
|
|
408
500
|
export function keySetFingerprint(keys: KeySet | SealKeySet): Promise<Fingerprint>;
|
|
501
|
+
/**
|
|
502
|
+
* Check a consistency proof (RFC 9162, section 2.1.4.2): that the tree of m leaves whose root is rootM is the first
|
|
503
|
+
* part of the tree of n leaves whose root is rootN. For m equal to n the proof is empty and the roots the same; for
|
|
504
|
+
* m of 0 the proof is empty and rootM the root of the empty tree. Never throws for a bad proof.
|
|
505
|
+
*/
|
|
506
|
+
export function verifyConsistency(m: number, n: number, rootM: Uint8Array, rootN: Uint8Array, proof: Uint8Array[]): Promise<boolean>;
|
|
507
|
+
/** Check an audit path (RFC 9162, section 2.1.3.2). */
|
|
508
|
+
export function verifyInclusion(index: number, size: number, leaf: Uint8Array, path: Uint8Array[], root: Uint8Array): Promise<boolean>;
|
|
409
509
|
/** The fingerprint of a block, as it is written everywhere: 64 hex characters. */
|
|
410
510
|
export function blockFingerprint(header: Uint8Array): Promise<string>;
|
|
411
511
|
/** A chain of Bitcoin block headers, checked on this device from the chain's first block. */
|
|
@@ -433,6 +533,8 @@ export function conditionWords(condition: Condition): string;
|
|
|
433
533
|
export function neverWords(name: string): string;
|
|
434
534
|
export const LIMITS: string[];
|
|
435
535
|
export const REFUSAL_REASONS: Record<string, string>;
|
|
536
|
+
/** Who said no, in a denial: "check", "person" or "rule", each with its meaning. */
|
|
537
|
+
export const DENIAL_SOURCES: Record<'check' | 'person' | 'rule', string>;
|
|
436
538
|
|
|
437
539
|
export interface Proposal extends Request {
|
|
438
540
|
slip: Fingerprint;
|
|
@@ -478,8 +580,18 @@ export interface Recorder {
|
|
|
478
580
|
* answer says why.
|
|
479
581
|
*/
|
|
480
582
|
detailsAfter?: (result: T) => DocumentRef[] | null | undefined | Promise<DocumentRef[] | null | undefined>;
|
|
583
|
+
/** False writes no denial for this call, where the writer was opened with "denials: true". */
|
|
584
|
+
denial?: boolean;
|
|
481
585
|
},
|
|
482
|
-
): Promise<{ done: boolean; answer: BeforeAnswer; result?: T; stub?: WrittenStub; detailsAfter?: { added: number; problem: Finding | null } }>;
|
|
586
|
+
): Promise<{ done: boolean; answer: BeforeAnswer; result?: T; stub?: WrittenStub; detailsAfter?: { added: number; problem: Finding | null }; denial?: DenialAnswer }>;
|
|
587
|
+
/**
|
|
588
|
+
* Writes a denial for an action that was not taken where "act" wrote none: the person said no, a rule of the
|
|
589
|
+
* software around the agent said no, or ("check", with the codes of that answer) the check before acting said no
|
|
590
|
+
* on a call for which "act" was asked to write none. A writer opened without "denials: true" writes none.
|
|
591
|
+
*/
|
|
592
|
+
deny(request: Request, more: { source: 'check' | 'person' | 'rule'; codes?: string[]; when?: number | Date }): Promise<DenialAnswer>;
|
|
593
|
+
/** Whether this writer writes denials, as it was opened. */
|
|
594
|
+
readonly denials: boolean;
|
|
483
595
|
book(): string;
|
|
484
596
|
/**
|
|
485
597
|
* What the record shows as it stands: the answer a whole check of the book gives, with the cancellations
|
|
@@ -503,6 +615,11 @@ export interface Recorder {
|
|
|
503
615
|
*/
|
|
504
616
|
add(entry: BookEntry): Promise<{ acknowledgement?: SignedRecord }>;
|
|
505
617
|
}
|
|
618
|
+
/**
|
|
619
|
+
* What the stub writer did about a denial. "notWritten" counts the denials not written because of the hourly
|
|
620
|
+
* limit since the writer was opened.
|
|
621
|
+
*/
|
|
622
|
+
export type DenialAnswer = { written: true; fingerprint: Fingerprint; line: string; notWritten: number } | { written: false; problem: Finding; notWritten: number };
|
|
506
623
|
/** A stub the stub writer wrote. A countersignature that did not check is left out, and "problem" says why. */
|
|
507
624
|
export type WrittenStub = Written & { seq: number; line: string; countersignature: { accepted: boolean; problem: Finding | null } };
|
|
508
625
|
export function openRecorder(o: {
|
|
@@ -519,6 +636,10 @@ export function openRecorder(o: {
|
|
|
519
636
|
countersignWithin?: number;
|
|
520
637
|
/** How long a signer is given for each signature, in milliseconds. Defaults to 30,000. After that nothing is written. */
|
|
521
638
|
signWithin?: number;
|
|
639
|
+
/** True to write a denial whenever "act" does not take an action, and to let "deny" write one. Defaults to false. */
|
|
640
|
+
denials?: boolean;
|
|
641
|
+
/** The most denials written in any hour, by the clock: 1 to 100,000. Defaults to 600. */
|
|
642
|
+
denialsPerHour?: number;
|
|
522
643
|
}): Promise<Recorder>;
|
|
523
644
|
|
|
524
645
|
/** One of an agent's tools, as it is described to recordTools. */
|
|
@@ -530,7 +651,7 @@ export interface RecordedTool<A = any, R = any> {
|
|
|
530
651
|
/** The id of the service the tool deals with, as the slip names it. */
|
|
531
652
|
with?: string | ((args: A) => string | undefined);
|
|
532
653
|
amount?: Amount | ((args: A) => Amount | undefined);
|
|
533
|
-
/** The documents to name in the stub, or false for none. By default: "arguments", the fingerprint of the arguments. */
|
|
654
|
+
/** The documents to name in the stub, or false for none. By default: "provared.arguments", the fingerprint of the arguments. */
|
|
534
655
|
details?: false | ((args: A) => DocumentRef[] | Promise<DocumentRef[]>);
|
|
535
656
|
/** Asks the other side to countersign the stub. */
|
|
536
657
|
countersign?: (stub: SignedRecord, args: A) => Promise<SignedRecord | null>;
|
|
@@ -539,9 +660,11 @@ export interface RecordedTool<A = any, R = any> {
|
|
|
539
660
|
}
|
|
540
661
|
/** A call through a recorded tool that was not run. "answer" is what the check before acting said. */
|
|
541
662
|
export class NotTaken extends Error {
|
|
542
|
-
constructor(tool: string, answer: BeforeAnswer);
|
|
663
|
+
constructor(tool: string, answer: BeforeAnswer, denial?: DenialAnswer);
|
|
543
664
|
tool: string;
|
|
544
665
|
answer: BeforeAnswer;
|
|
666
|
+
/** Where the stub writer writes denials: what it did about the denial for this call. */
|
|
667
|
+
denial?: DenialAnswer;
|
|
545
668
|
}
|
|
546
669
|
/**
|
|
547
670
|
* Puts an agent's tools behind the stub writer: each call asks first, runs the tool only if the slip allows
|
|
@@ -624,6 +747,15 @@ export function writeAcknowledgement(
|
|
|
624
747
|
fields: { slip: Fingerprint; cancellation: Fingerprint; pass?: Fingerprint; when?: number | Date; id?: Base64url },
|
|
625
748
|
privateKeys: SigningKey[],
|
|
626
749
|
): Promise<Written>;
|
|
750
|
+
/** The format's own list of finding codes, from which a denial by the check before acting names its reasons. */
|
|
751
|
+
export const FINDING_CODES: readonly string[];
|
|
752
|
+
/** The reasons of a refusal by the check before acting, as a denial names them: 1 to 16 codes, each once. */
|
|
753
|
+
export function denialCodes(answer: Pick<BeforeAnswer, 'problems' | 'breaches'>): string[];
|
|
754
|
+
/** The agent's side states that it did not take an action that was asked for, and why. */
|
|
755
|
+
export function writeDenial(
|
|
756
|
+
fields: Request & { slip: Fingerprint; pass?: Fingerprint; source: 'check' | 'person' | 'rule'; codes?: string[]; when?: number | Date; id?: Base64url },
|
|
757
|
+
privateKeys: SigningKey[],
|
|
758
|
+
): Promise<Written>;
|
|
627
759
|
/**
|
|
628
760
|
* It needs Node.js: no browser has the seal's third signing method. "privateKeys": the recorder's three keys (two
|
|
629
761
|
* Web Crypto keys, and a Node.js KeyObject or what Node.js reads as one: PEM text, DER bytes with their format, or a
|
|
@@ -631,10 +763,139 @@ export function writeAcknowledgement(
|
|
|
631
763
|
*/
|
|
632
764
|
export function writeSeal(
|
|
633
765
|
book: string,
|
|
634
|
-
|
|
766
|
+
/** "book": the book's number among the books of one permission, 2 or more; 1 or none for the first (section 21.6). */
|
|
767
|
+
fields: { by: { keys: SealKeySet; name: string }; previous?: Fingerprint; book?: number; when?: number | Date; id?: Base64url },
|
|
635
768
|
privateKeys: unknown[],
|
|
636
769
|
): Promise<Written & { fingerprintBytes: Uint8Array }>;
|
|
637
770
|
|
|
771
|
+
/** The most witness statements beside one seal, or handed over with one seal held elsewhere. */
|
|
772
|
+
export const MAX_WITNESSES: 4;
|
|
773
|
+
/** A witness signs for a seal. This only writes the statement; it does not check the seal (see openWitness). */
|
|
774
|
+
export function writeWitness(
|
|
775
|
+
fields: { seal: Fingerprint; size: number; root: Fingerprint; by: { keys: KeySet; name: string }; when?: number | Date; id?: Base64url },
|
|
776
|
+
privateKeys: SigningKey[],
|
|
777
|
+
): Promise<Written>;
|
|
778
|
+
/** The consistency proof from the tree of the first m leaves to the tree of all of them (RFC 9162, section 2.1.4.1). Empty for m of 0 or m equal to the number of leaves. */
|
|
779
|
+
export function consistencyProof(leaves: Uint8Array[], m: number): Promise<Uint8Array[]>;
|
|
780
|
+
/** The audit path of one leaf (RFC 9162, section 2.1.3). */
|
|
781
|
+
export function inclusionPath(leaves: Uint8Array[], index: number): Promise<Uint8Array[]>;
|
|
782
|
+
/** How a witness keeps sequences of books apart: the recorder's key set, with the passkey and the agent's key set that the permission on the book's first line names. */
|
|
783
|
+
export function bookIdentity(keys: SealKeySet, firstLine: Uint8Array): Promise<Fingerprint>;
|
|
784
|
+
/**
|
|
785
|
+
* What a witness is sent with a seal: the consistency proof from the seal it holds for the book ("from", its number
|
|
786
|
+
* of entries), or, where it holds none or the seal is the first of the next book, the audit path of the book's first line in
|
|
787
|
+
* the new seal's tree.
|
|
788
|
+
*/
|
|
789
|
+
export function witnessProof(book: string, size: number, from?: number | null): Promise<Uint8Array[]>;
|
|
790
|
+
/** A witness's state, as it hands it back: to be saved before a statement is given out. */
|
|
791
|
+
export interface WitnessState {
|
|
792
|
+
type: 'provared.witness-state.v0';
|
|
793
|
+
/** The fingerprint of the witness's key set. */
|
|
794
|
+
by: Fingerprint;
|
|
795
|
+
/** For each recorder, passkey (its thumbprint) and agent (the fingerprint of its key set): the latest seal this witness signed for, and its statement. */
|
|
796
|
+
books: { passkey: Fingerprint; agent: Fingerprint; seal: SignedRecord; statement: SignedRecord }[];
|
|
797
|
+
}
|
|
798
|
+
export type WitnessAnswer<T> = T | { refused: Finding };
|
|
799
|
+
export interface Witness {
|
|
800
|
+
/** Sign for a seal if it extends the latest seal of its book this witness holds. The state handed back holds the new seal: save it first. */
|
|
801
|
+
witness(entry: { seal: SignedRecord; stamps?: StampItem[]; witnesses?: SignedRecord[] }, proof: Uint8Array[], firstLine: Uint8Array): Promise<WitnessAnswer<{ statement: SignedRecord; state: WitnessState }>>;
|
|
802
|
+
/** Take as the latest seal of its book a seal this witness signed for before, from a copy of what it gave out. It signs nothing. */
|
|
803
|
+
update(entry: { seal: SignedRecord; witnesses: SignedRecord[]; stamps?: StampItem[] }, proof: Uint8Array[], firstLine: Uint8Array): Promise<WitnessAnswer<{ state: WitnessState }>>;
|
|
804
|
+
/** Opened from a saved state, the witness signs nothing until it is told it has been brought up to date. */
|
|
805
|
+
ready(): void;
|
|
806
|
+
latest(book: Fingerprint): { seal: SignedRecord; statement: SignedRecord } | null;
|
|
807
|
+
state(): WitnessState;
|
|
808
|
+
}
|
|
809
|
+
/** A witness needs SLH-DSA, so it needs Node.js. */
|
|
810
|
+
export function openWitness(o: {
|
|
811
|
+
by: { keys: KeySet; name: string };
|
|
812
|
+
signers: SigningKey[];
|
|
813
|
+
state?: WitnessState;
|
|
814
|
+
clock?: () => number;
|
|
815
|
+
withoutMethods?: string[];
|
|
816
|
+
}): Promise<Witness>;
|
|
817
|
+
|
|
818
|
+
/** The leaf fingerprint of one line of a book, without its line feed: SHA-256 of 0x00 and the line (RFC 6962, section 2.1). What a recorder sends a central sealer. */
|
|
819
|
+
export function leafFingerprint(line: string): Promise<Uint8Array>;
|
|
820
|
+
/** Which book a sealer call is about: the leaf fingerprint of its first line (a first book), or that with the book's number (section 21.6). */
|
|
821
|
+
export type SealerBook = Fingerprint | { first: Fingerprint; number?: number };
|
|
822
|
+
/** A central sealer's state, as it hands it back: to be saved before the answer is given out. At most 1,000 books. */
|
|
823
|
+
export interface SealerState {
|
|
824
|
+
type: 'provared.sealer-state.v0';
|
|
825
|
+
/** The fingerprint of the sealer's key set. */
|
|
826
|
+
by: Fingerprint;
|
|
827
|
+
/**
|
|
828
|
+
* For each book: its first line's leaf fingerprint and number, the recorder it belongs to (absent for the sealer's own calls),
|
|
829
|
+
* the book it follows, whether a later book follows it, and its latest seal with the tree's parts at that seal. A book is kept at
|
|
830
|
+
* its latest seal, or with none at its first line: lines that no seal covers are asked for again after a start.
|
|
831
|
+
*/
|
|
832
|
+
books: {
|
|
833
|
+
first: Fingerprint;
|
|
834
|
+
number: number;
|
|
835
|
+
recorder?: string;
|
|
836
|
+
follows?: Fingerprint;
|
|
837
|
+
closed: boolean;
|
|
838
|
+
/** The latest seal and its time-stamps (at most four); witness statements are never kept. */
|
|
839
|
+
latest?: { entry: { seal: SignedRecord; stamps?: StampItem[] }; parts: Base64url[] };
|
|
840
|
+
}[];
|
|
841
|
+
}
|
|
842
|
+
export type SealerAnswer<T> = T | { refused: Finding; size?: number };
|
|
843
|
+
/** The calls of a central sealer, made as one recorder (forRecorder) or as none (the sealer's own). */
|
|
844
|
+
export interface SealerCalls {
|
|
845
|
+
/**
|
|
846
|
+
* Take in the leaf fingerprints of new lines of one book, in order, from place "at" (the number of lines it holds). A later book
|
|
847
|
+
* names the book it follows; a book whose first line the sealer holds starts only as the next book after the latest with that
|
|
848
|
+
* first line. "line" is the book's first line itself: where any recorder has a list of agents, a book is started only with it,
|
|
849
|
+
* and it must be a permission for an agent of the caller's own. Where the call started a book, the answer holds the state: save it first.
|
|
850
|
+
*/
|
|
851
|
+
extend(book: SealerBook, at: number, leaves: Uint8Array[], more?: { follows?: Fingerprint; line?: string }): Promise<SealerAnswer<{ size: number; state?: SealerState }>>;
|
|
852
|
+
/** Sign a seal over the tree this sealer holds; "previous" is the seal the new seal must name, as the book shows it. The state handed back holds the new seal: save it first. */
|
|
853
|
+
seal(
|
|
854
|
+
book: SealerBook,
|
|
855
|
+
more: { previous: Fingerprint | null; when?: number | Date; id?: Base64url },
|
|
856
|
+
): Promise<SealerAnswer<{ entry: { seal: SignedRecord; stamps?: StampItem[] }; line: string; fingerprint: Fingerprint; fingerprintBytes: Uint8Array; state: SealerState; stampProblem?: Finding }>>;
|
|
857
|
+
/**
|
|
858
|
+
* Bring the sealer up to date for a book from a later seal it signed, with the leaf fingerprints from its own latest seal to that one.
|
|
859
|
+
* Where it is the first seal of a later book, the book before is closed; where the sealer holds that book at an earlier seal than the
|
|
860
|
+
* one named, it is refused until that book is brought up to date. A book it does not hold is started here under the same rules
|
|
861
|
+
* as with extend, "line" included. It signs nothing. It keeps the seal, whose three signatures it checks, and its time-stamps
|
|
862
|
+
* (at most four), read for their form only: no time-stamp's signature is checked. It keeps no witness statements; an entry longer
|
|
863
|
+
* than a line of a book may be is refused (too-large). The entry is read once, and only that copy is used.
|
|
864
|
+
*/
|
|
865
|
+
update(
|
|
866
|
+
book: SealerBook,
|
|
867
|
+
entry: { seal: SignedRecord; stamps?: StampItem[]; witnesses?: SignedRecord[] },
|
|
868
|
+
leaves: Uint8Array[],
|
|
869
|
+
more?: { line?: string },
|
|
870
|
+
): Promise<SealerAnswer<{ size: number; state: SealerState }>>;
|
|
871
|
+
latest(book: SealerBook): { entry: { seal: SignedRecord; stamps?: StampItem[] }; line: string; fingerprint: Fingerprint } | null;
|
|
872
|
+
held(book: SealerBook): { first: Fingerprint; number: number; follows: Fingerprint | null; size: number; closed: boolean; latest: Fingerprint | null; sealed: number } | null;
|
|
873
|
+
}
|
|
874
|
+
export interface Sealer extends SealerCalls {
|
|
875
|
+
state(): SealerState;
|
|
876
|
+
/**
|
|
877
|
+
* The same calls for one named recorder (a label): its books, and every book after them, are its own. "rules": the fingerprints
|
|
878
|
+
* of the key sets of the agents whose permissions it may start books with (1 to 64; an agent belongs to one recorder), and the
|
|
879
|
+
* most books it may hold (1 to 1,000). A recorder's rules are fixed by the first call that gives them.
|
|
880
|
+
*/
|
|
881
|
+
forRecorder(recorder: string, rules?: { agents?: Fingerprint[]; books?: number }): SealerCalls;
|
|
882
|
+
/**
|
|
883
|
+
* Take off the books that hold no seal (nothing was signed for them): every recorder's, or one recorder's (null for the sealer's
|
|
884
|
+
* own calls). The state handed back is to be saved.
|
|
885
|
+
*/
|
|
886
|
+
releaseUnsealed(o?: { recorder?: string | null }): Promise<{ released: { first: Fingerprint; number: number; recorder: string | null }[]; state: SealerState }>;
|
|
887
|
+
}
|
|
888
|
+
/** A central sealer: it seals books it is sent only the leaf fingerprints of. It checks every seal it signs, so it needs Node.js. */
|
|
889
|
+
export function openSealer(o: {
|
|
890
|
+
by: { keys: SealKeySet; name: string };
|
|
891
|
+
signers: unknown[];
|
|
892
|
+
/** Ask a time-stamp service for a stamp over a seal's fingerprint: an item of "stamps", or null. */
|
|
893
|
+
stamp?: (fingerprintBytes: Uint8Array) => Promise<StampItem | null> | StampItem | null;
|
|
894
|
+
state?: SealerState;
|
|
895
|
+
clock?: () => number;
|
|
896
|
+
signWithin?: number;
|
|
897
|
+
}): Promise<Sealer>;
|
|
898
|
+
|
|
638
899
|
/** A proof from a public blockchain and the 80-byte header of its block, in the form they have in "stamps". */
|
|
639
900
|
export function blockStampItem(proof: Uint8Array, header: Uint8Array): { block: Base64url; proof: Base64url };
|
|
640
901
|
|
|
@@ -667,7 +928,7 @@ export const DOCUMENT_NAMES: Readonly<Record<string, string>>;
|
|
|
667
928
|
export function httpRequestFingerprint(r: { method: string; url: string; body?: Uint8Array }): Promise<Fingerprint>;
|
|
668
929
|
/** For "provared.http-response": the status code, and the body's bytes where there is a body. */
|
|
669
930
|
export function httpResponseFingerprint(r: { status: number; body?: Uint8Array }): Promise<Fingerprint>;
|
|
670
|
-
/** For "provared.trace-id", "provared.request-id", "provared.event-id" and "provared.
|
|
931
|
+
/** For "provared.trace-id", "provared.request-id", "provared.event-id", "provared.error", "provared.tool-name" and "provared.tool-call-id": SHA-256 of the text's UTF-8 bytes, exactly as given. A tool's result ("provared.tool-result") is fingerprinted with argumentsFingerprint. */
|
|
671
932
|
export function identifierFingerprint(id: string): Promise<Fingerprint>;
|
|
672
933
|
export const ACTION_KINDS: Record<string, string>;
|
|
673
934
|
export const SHARED_ACTIONS: Record<string, [kind: string, meaning: string]>;
|
package/src/witness.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// The witness statement: a party with a key set of its own (a witness)
|
|
2
|
+
// states that it saw a seal, that the seal extends every earlier seal of
|
|
3
|
+
// the same book it signed for, and that it will sign for no seal of that
|
|
4
|
+
// book that does not extend this one. It is signed with the witness's two
|
|
5
|
+
// keys. A statement sits beside its seal in the seal's book entry, as
|
|
6
|
+
// time-stamps do, or is handed to a checker beside a seal held elsewhere.
|
|
7
|
+
// Format description, section 29.
|
|
8
|
+
|
|
9
|
+
import { formatTime, randomId } from './encoding.js';
|
|
10
|
+
import * as f from './fields.js';
|
|
11
|
+
import { KINDS } from './jws.js';
|
|
12
|
+
import { checkKeySet } from './keys.js';
|
|
13
|
+
import { signWithKeySet } from './stub.js';
|
|
14
|
+
|
|
15
|
+
/** The most witness statements one seal entry may hold, or that may be handed over with one seal. */
|
|
16
|
+
export const MAX_WITNESSES = 4;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Confirm every member of a witness statement's content.
|
|
20
|
+
* @param {any} c
|
|
21
|
+
*/
|
|
22
|
+
export function validateWitnessContent(c) {
|
|
23
|
+
f.members(c, ['by', 'id', 'root', 'seal', 'size', 'type', 'when'], [], 'witness');
|
|
24
|
+
f.id(c.id, 'id');
|
|
25
|
+
f.fingerprintText(c.seal, 'seal');
|
|
26
|
+
f.wholeNumber(c.size, 'size');
|
|
27
|
+
if (c.size < 1) throw f.fail('size', 'must be 1 or more.');
|
|
28
|
+
f.fingerprintText(c.root, 'root');
|
|
29
|
+
f.members(c.by, ['keys', 'name'], [], 'by');
|
|
30
|
+
f.label(c.by.name, 'by.name');
|
|
31
|
+
checkKeySet(c.by.keys, 'by.keys');
|
|
32
|
+
f.time(c.when, 'when');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Write and sign a witness statement for a seal. This function only writes
|
|
37
|
+
* the statement: it does not check the seal. A witness should sign only as
|
|
38
|
+
* the format description, section 29.3, sets out; openWitness
|
|
39
|
+
* (witnessing.js) does.
|
|
40
|
+
*
|
|
41
|
+
* @param {object} fields
|
|
42
|
+
* @param {string} fields.seal the seal's fingerprint
|
|
43
|
+
* @param {number} fields.size the seal's "size", repeated
|
|
44
|
+
* @param {string} fields.root the seal's "root", repeated
|
|
45
|
+
* @param {{keys: object[], name: string}} fields.by the witness's public key set and its name (a label)
|
|
46
|
+
* @param {number|Date} [fields.when] defaults to now
|
|
47
|
+
* @param {any[]} privateKeys the witness's private keys, or signers (signatures.js): Ed25519, then ML-DSA-87. A
|
|
48
|
+
* signer must hold the key "by" names in its place
|
|
49
|
+
* @returns {Promise<{record: object, fingerprint: string}>}
|
|
50
|
+
*/
|
|
51
|
+
export async function writeWitness(fields, privateKeys) {
|
|
52
|
+
const content = {
|
|
53
|
+
type: KINDS.witness.type,
|
|
54
|
+
id: fields.id ?? randomId(),
|
|
55
|
+
seal: fields.seal,
|
|
56
|
+
size: fields.size,
|
|
57
|
+
root: fields.root,
|
|
58
|
+
by: fields.by,
|
|
59
|
+
when: formatTime(fields.when ?? Date.now()),
|
|
60
|
+
};
|
|
61
|
+
validateWitnessContent(content);
|
|
62
|
+
return signWithKeySet('witness', content, privateKeys, content.by.keys);
|
|
63
|
+
}
|