provared 0.5.0 → 0.6.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,8 +412,8 @@ 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
 
@@ -356,6 +428,8 @@ export interface CheckOptions {
356
428
  blocks?: string[];
357
429
  /** The key set fingerprints of the organisations whose vouching the checker trusts. */
358
430
  vouchers?: Fingerprint[];
431
+ /** The key set fingerprints of the witnesses the checker trusts (format description, section 29). */
432
+ witnesses?: Fingerprint[];
359
433
  /** For covered fields: the disclosures handed over, by the fingerprint of the slip they belong to. */
360
434
  disclosures?: Record<Fingerprint, string[]>;
361
435
  /**
@@ -364,6 +438,11 @@ export interface CheckOptions {
364
438
  * out, or holds them with a later time-stamp.
365
439
  */
366
440
  cancellations?: HandedCancellation[];
441
+ /**
442
+ * Seals held elsewhere, each as the entry it is in the book. At most 64. The book must hold each at the
443
+ * place it states. Each counts only where its recorder is one named in "sealKeys".
444
+ */
445
+ seals?: HandedSeal[];
367
446
  expectedRoot?: Fingerprint;
368
447
  expectedSize?: number;
369
448
  /** Signing methods to treat as not built into this device. It can only withhold a pass. */
@@ -406,6 +485,14 @@ export function checkSlip(record: SignedRecord, options?: Pick<CheckOptions, 'is
406
485
  export function methodAvailable(alg: string): Promise<boolean>;
407
486
  export function thumbprint(key: PublicKey): Promise<string>;
408
487
  export function keySetFingerprint(keys: KeySet | SealKeySet): Promise<Fingerprint>;
488
+ /**
489
+ * Check a consistency proof (RFC 9162, section 2.1.4.2): that the tree of m leaves whose root is rootM is the first
490
+ * 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
491
+ * m of 0 the proof is empty and rootM the root of the empty tree. Never throws for a bad proof.
492
+ */
493
+ export function verifyConsistency(m: number, n: number, rootM: Uint8Array, rootN: Uint8Array, proof: Uint8Array[]): Promise<boolean>;
494
+ /** Check an audit path (RFC 9162, section 2.1.3.2). */
495
+ export function verifyInclusion(index: number, size: number, leaf: Uint8Array, path: Uint8Array[], root: Uint8Array): Promise<boolean>;
409
496
  /** The fingerprint of a block, as it is written everywhere: 64 hex characters. */
410
497
  export function blockFingerprint(header: Uint8Array): Promise<string>;
411
498
  /** A chain of Bitcoin block headers, checked on this device from the chain's first block. */
@@ -433,6 +520,8 @@ export function conditionWords(condition: Condition): string;
433
520
  export function neverWords(name: string): string;
434
521
  export const LIMITS: string[];
435
522
  export const REFUSAL_REASONS: Record<string, string>;
523
+ /** Who said no, in a denial: "check", "person" or "rule", each with its meaning. */
524
+ export const DENIAL_SOURCES: Record<'check' | 'person' | 'rule', string>;
436
525
 
437
526
  export interface Proposal extends Request {
438
527
  slip: Fingerprint;
@@ -478,8 +567,18 @@ export interface Recorder {
478
567
  * answer says why.
479
568
  */
480
569
  detailsAfter?: (result: T) => DocumentRef[] | null | undefined | Promise<DocumentRef[] | null | undefined>;
570
+ /** False writes no denial for this call, where the writer was opened with "denials: true". */
571
+ denial?: boolean;
481
572
  },
482
- ): Promise<{ done: boolean; answer: BeforeAnswer; result?: T; stub?: WrittenStub; detailsAfter?: { added: number; problem: Finding | null } }>;
573
+ ): Promise<{ done: boolean; answer: BeforeAnswer; result?: T; stub?: WrittenStub; detailsAfter?: { added: number; problem: Finding | null }; denial?: DenialAnswer }>;
574
+ /**
575
+ * Writes a denial for an action that was not taken where "act" wrote none: the person said no, a rule of the
576
+ * software around the agent said no, or ("check", with the codes of that answer) the check before acting said no
577
+ * on a call for which "act" was asked to write none. A writer opened without "denials: true" writes none.
578
+ */
579
+ deny(request: Request, more: { source: 'check' | 'person' | 'rule'; codes?: string[]; when?: number | Date }): Promise<DenialAnswer>;
580
+ /** Whether this writer writes denials, as it was opened. */
581
+ readonly denials: boolean;
483
582
  book(): string;
484
583
  /**
485
584
  * What the record shows as it stands: the answer a whole check of the book gives, with the cancellations
@@ -503,6 +602,11 @@ export interface Recorder {
503
602
  */
504
603
  add(entry: BookEntry): Promise<{ acknowledgement?: SignedRecord }>;
505
604
  }
605
+ /**
606
+ * What the stub writer did about a denial. "notWritten" counts the denials not written because of the hourly
607
+ * limit since the writer was opened.
608
+ */
609
+ export type DenialAnswer = { written: true; fingerprint: Fingerprint; line: string; notWritten: number } | { written: false; problem: Finding; notWritten: number };
506
610
  /** A stub the stub writer wrote. A countersignature that did not check is left out, and "problem" says why. */
507
611
  export type WrittenStub = Written & { seq: number; line: string; countersignature: { accepted: boolean; problem: Finding | null } };
508
612
  export function openRecorder(o: {
@@ -519,6 +623,10 @@ export function openRecorder(o: {
519
623
  countersignWithin?: number;
520
624
  /** How long a signer is given for each signature, in milliseconds. Defaults to 30,000. After that nothing is written. */
521
625
  signWithin?: number;
626
+ /** True to write a denial whenever "act" does not take an action, and to let "deny" write one. Defaults to false. */
627
+ denials?: boolean;
628
+ /** The most denials written in any hour, by the clock: 1 to 100,000. Defaults to 600. */
629
+ denialsPerHour?: number;
522
630
  }): Promise<Recorder>;
523
631
 
524
632
  /** One of an agent's tools, as it is described to recordTools. */
@@ -530,7 +638,7 @@ export interface RecordedTool<A = any, R = any> {
530
638
  /** The id of the service the tool deals with, as the slip names it. */
531
639
  with?: string | ((args: A) => string | undefined);
532
640
  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. */
641
+ /** The documents to name in the stub, or false for none. By default: "provared.arguments", the fingerprint of the arguments. */
534
642
  details?: false | ((args: A) => DocumentRef[] | Promise<DocumentRef[]>);
535
643
  /** Asks the other side to countersign the stub. */
536
644
  countersign?: (stub: SignedRecord, args: A) => Promise<SignedRecord | null>;
@@ -539,9 +647,11 @@ export interface RecordedTool<A = any, R = any> {
539
647
  }
540
648
  /** A call through a recorded tool that was not run. "answer" is what the check before acting said. */
541
649
  export class NotTaken extends Error {
542
- constructor(tool: string, answer: BeforeAnswer);
650
+ constructor(tool: string, answer: BeforeAnswer, denial?: DenialAnswer);
543
651
  tool: string;
544
652
  answer: BeforeAnswer;
653
+ /** Where the stub writer writes denials: what it did about the denial for this call. */
654
+ denial?: DenialAnswer;
545
655
  }
546
656
  /**
547
657
  * Puts an agent's tools behind the stub writer: each call asks first, runs the tool only if the slip allows
@@ -624,6 +734,15 @@ export function writeAcknowledgement(
624
734
  fields: { slip: Fingerprint; cancellation: Fingerprint; pass?: Fingerprint; when?: number | Date; id?: Base64url },
625
735
  privateKeys: SigningKey[],
626
736
  ): Promise<Written>;
737
+ /** The format's own list of finding codes, from which a denial by the check before acting names its reasons. */
738
+ export const FINDING_CODES: readonly string[];
739
+ /** The reasons of a refusal by the check before acting, as a denial names them: 1 to 16 codes, each once. */
740
+ export function denialCodes(answer: Pick<BeforeAnswer, 'problems' | 'breaches'>): string[];
741
+ /** The agent's side states that it did not take an action that was asked for, and why. */
742
+ export function writeDenial(
743
+ fields: Request & { slip: Fingerprint; pass?: Fingerprint; source: 'check' | 'person' | 'rule'; codes?: string[]; when?: number | Date; id?: Base64url },
744
+ privateKeys: SigningKey[],
745
+ ): Promise<Written>;
627
746
  /**
628
747
  * It needs Node.js: no browser has the seal's third signing method. "privateKeys": the recorder's three keys (two
629
748
  * 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 +750,139 @@ export function writeAcknowledgement(
631
750
  */
632
751
  export function writeSeal(
633
752
  book: string,
634
- fields: { by: { keys: SealKeySet; name: string }; previous?: Fingerprint; when?: number | Date; id?: Base64url },
753
+ /** "book": the book's number among the books of one permission, 2 or more; 1 or none for the first (section 21.6). */
754
+ fields: { by: { keys: SealKeySet; name: string }; previous?: Fingerprint; book?: number; when?: number | Date; id?: Base64url },
635
755
  privateKeys: unknown[],
636
756
  ): Promise<Written & { fingerprintBytes: Uint8Array }>;
637
757
 
758
+ /** The most witness statements beside one seal, or handed over with one seal held elsewhere. */
759
+ export const MAX_WITNESSES: 4;
760
+ /** A witness signs for a seal. This only writes the statement; it does not check the seal (see openWitness). */
761
+ export function writeWitness(
762
+ fields: { seal: Fingerprint; size: number; root: Fingerprint; by: { keys: KeySet; name: string }; when?: number | Date; id?: Base64url },
763
+ privateKeys: SigningKey[],
764
+ ): Promise<Written>;
765
+ /** 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. */
766
+ export function consistencyProof(leaves: Uint8Array[], m: number): Promise<Uint8Array[]>;
767
+ /** The audit path of one leaf (RFC 9162, section 2.1.3). */
768
+ export function inclusionPath(leaves: Uint8Array[], index: number): Promise<Uint8Array[]>;
769
+ /** 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. */
770
+ export function bookIdentity(keys: SealKeySet, firstLine: Uint8Array): Promise<Fingerprint>;
771
+ /**
772
+ * What a witness is sent with a seal: the consistency proof from the seal it holds for the book ("from", its number
773
+ * 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
774
+ * the new seal's tree.
775
+ */
776
+ export function witnessProof(book: string, size: number, from?: number | null): Promise<Uint8Array[]>;
777
+ /** A witness's state, as it hands it back: to be saved before a statement is given out. */
778
+ export interface WitnessState {
779
+ type: 'provared.witness-state.v0';
780
+ /** The fingerprint of the witness's key set. */
781
+ by: Fingerprint;
782
+ /** For each recorder, passkey (its thumbprint) and agent (the fingerprint of its key set): the latest seal this witness signed for, and its statement. */
783
+ books: { passkey: Fingerprint; agent: Fingerprint; seal: SignedRecord; statement: SignedRecord }[];
784
+ }
785
+ export type WitnessAnswer<T> = T | { refused: Finding };
786
+ export interface Witness {
787
+ /** 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. */
788
+ witness(entry: { seal: SignedRecord; stamps?: StampItem[]; witnesses?: SignedRecord[] }, proof: Uint8Array[], firstLine: Uint8Array): Promise<WitnessAnswer<{ statement: SignedRecord; state: WitnessState }>>;
789
+ /** 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. */
790
+ update(entry: { seal: SignedRecord; witnesses: SignedRecord[]; stamps?: StampItem[] }, proof: Uint8Array[], firstLine: Uint8Array): Promise<WitnessAnswer<{ state: WitnessState }>>;
791
+ /** Opened from a saved state, the witness signs nothing until it is told it has been brought up to date. */
792
+ ready(): void;
793
+ latest(book: Fingerprint): { seal: SignedRecord; statement: SignedRecord } | null;
794
+ state(): WitnessState;
795
+ }
796
+ /** A witness needs SLH-DSA, so it needs Node.js. */
797
+ export function openWitness(o: {
798
+ by: { keys: KeySet; name: string };
799
+ signers: SigningKey[];
800
+ state?: WitnessState;
801
+ clock?: () => number;
802
+ withoutMethods?: string[];
803
+ }): Promise<Witness>;
804
+
805
+ /** 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. */
806
+ export function leafFingerprint(line: string): Promise<Uint8Array>;
807
+ /** 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). */
808
+ export type SealerBook = Fingerprint | { first: Fingerprint; number?: number };
809
+ /** A central sealer's state, as it hands it back: to be saved before the answer is given out. At most 1,000 books. */
810
+ export interface SealerState {
811
+ type: 'provared.sealer-state.v0';
812
+ /** The fingerprint of the sealer's key set. */
813
+ by: Fingerprint;
814
+ /**
815
+ * For each book: its first line's leaf fingerprint and number, the recorder it belongs to (absent for the sealer's own calls),
816
+ * 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
817
+ * its latest seal, or with none at its first line: lines that no seal covers are asked for again after a start.
818
+ */
819
+ books: {
820
+ first: Fingerprint;
821
+ number: number;
822
+ recorder?: string;
823
+ follows?: Fingerprint;
824
+ closed: boolean;
825
+ /** The latest seal and its time-stamps (at most four); witness statements are never kept. */
826
+ latest?: { entry: { seal: SignedRecord; stamps?: StampItem[] }; parts: Base64url[] };
827
+ }[];
828
+ }
829
+ export type SealerAnswer<T> = T | { refused: Finding; size?: number };
830
+ /** The calls of a central sealer, made as one recorder (forRecorder) or as none (the sealer's own). */
831
+ export interface SealerCalls {
832
+ /**
833
+ * 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
834
+ * names the book it follows; a book whose first line the sealer holds starts only as the next book after the latest with that
835
+ * 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,
836
+ * 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.
837
+ */
838
+ extend(book: SealerBook, at: number, leaves: Uint8Array[], more?: { follows?: Fingerprint; line?: string }): Promise<SealerAnswer<{ size: number; state?: SealerState }>>;
839
+ /** 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. */
840
+ seal(
841
+ book: SealerBook,
842
+ more: { previous: Fingerprint | null; when?: number | Date; id?: Base64url },
843
+ ): Promise<SealerAnswer<{ entry: { seal: SignedRecord; stamps?: StampItem[] }; line: string; fingerprint: Fingerprint; fingerprintBytes: Uint8Array; state: SealerState; stampProblem?: Finding }>>;
844
+ /**
845
+ * 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.
846
+ * 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
847
+ * 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
848
+ * as with extend, "line" included. It signs nothing. It keeps the seal, whose three signatures it checks, and its time-stamps
849
+ * (at most four), read for their form only: no time-stamp's signature is checked. It keeps no witness statements; an entry longer
850
+ * than a line of a book may be is refused (too-large). The entry is read once, and only that copy is used.
851
+ */
852
+ update(
853
+ book: SealerBook,
854
+ entry: { seal: SignedRecord; stamps?: StampItem[]; witnesses?: SignedRecord[] },
855
+ leaves: Uint8Array[],
856
+ more?: { line?: string },
857
+ ): Promise<SealerAnswer<{ size: number; state: SealerState }>>;
858
+ latest(book: SealerBook): { entry: { seal: SignedRecord; stamps?: StampItem[] }; line: string; fingerprint: Fingerprint } | null;
859
+ held(book: SealerBook): { first: Fingerprint; number: number; follows: Fingerprint | null; size: number; closed: boolean; latest: Fingerprint | null; sealed: number } | null;
860
+ }
861
+ export interface Sealer extends SealerCalls {
862
+ state(): SealerState;
863
+ /**
864
+ * The same calls for one named recorder (a label): its books, and every book after them, are its own. "rules": the fingerprints
865
+ * 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
866
+ * most books it may hold (1 to 1,000). A recorder's rules are fixed by the first call that gives them.
867
+ */
868
+ forRecorder(recorder: string, rules?: { agents?: Fingerprint[]; books?: number }): SealerCalls;
869
+ /**
870
+ * 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
871
+ * own calls). The state handed back is to be saved.
872
+ */
873
+ releaseUnsealed(o?: { recorder?: string | null }): Promise<{ released: { first: Fingerprint; number: number; recorder: string | null }[]; state: SealerState }>;
874
+ }
875
+ /** 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. */
876
+ export function openSealer(o: {
877
+ by: { keys: SealKeySet; name: string };
878
+ signers: unknown[];
879
+ /** Ask a time-stamp service for a stamp over a seal's fingerprint: an item of "stamps", or null. */
880
+ stamp?: (fingerprintBytes: Uint8Array) => Promise<StampItem | null> | StampItem | null;
881
+ state?: SealerState;
882
+ clock?: () => number;
883
+ signWithin?: number;
884
+ }): Promise<Sealer>;
885
+
638
886
  /** A proof from a public blockchain and the 80-byte header of its block, in the form they have in "stamps". */
639
887
  export function blockStampItem(proof: Uint8Array, header: Uint8Array): { block: Base64url; proof: Base64url };
640
888
 
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
+ }