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/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
- fields: { by: { keys: SealKeySet; name: string }; previous?: Fingerprint; when?: number | Date; id?: Base64url },
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.error": SHA-256 of the text's UTF-8 bytes, exactly as given. */
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
+ }