provared 0.4.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
@@ -50,6 +50,21 @@ export type KeySet = [Ed25519Key, MlDsaKey];
50
50
  export type SealKeySet = [Ed25519Key, MlDsaKey, SlhDsaKey];
51
51
  export type PublicKey = PasskeyKey | MlDsaKey | SlhDsaKey;
52
52
 
53
+ /**
54
+ * A signer: a key held elsewhere (a key service, a hardware module) that signs on request. Wherever
55
+ * "privateKeys" is taken, each item may be a key or a signer. A signer's key must be of the method of
56
+ * its place, and, where the writer knows the key the record must be signed with, that key. Every
57
+ * signature it gives is checked against its key before it is used.
58
+ */
59
+ export interface Signer {
60
+ /** The public key, in one of the shapes of the format description, section 3.8. */
61
+ key: Ed25519Key | MlDsaKey | SlhDsaKey;
62
+ /** The signature over exactly these bytes: 64 bytes for Ed25519, 4,627 for ML-DSA-87, 29,792 for SLH-DSA-SHA2-256s. */
63
+ sign(data: Uint8Array): Promise<Uint8Array | ArrayBuffer>;
64
+ }
65
+ /** A private key of the platform (a Web Crypto CryptoKey), or a signer. */
66
+ export type SigningKey = CryptoKey | Signer;
67
+
53
68
  // --- what records hold ---
54
69
 
55
70
  export interface Amount {
@@ -140,9 +155,10 @@ export type BookEntry =
140
155
  | { stub: SignedRecord; countersignature?: SignedRecord; approval?: SignedRecord }
141
156
  | { refusal: SignedRecord }
142
157
  | { terms: SignedRecord }
143
- | { seal: SignedRecord; stamps?: StampItem[] }
158
+ | { seal: SignedRecord; stamps?: StampItem[]; witnesses?: SignedRecord[] }
144
159
  | { cancellation: SignedRecord; stamps?: StampItem[] }
145
160
  | { acknowledgement: SignedRecord }
161
+ | { denial: SignedRecord }
146
162
  | { vouching: SignedRecord }
147
163
  | { withdrawal: SignedRecord }
148
164
  | { pass: SignedRecord };
@@ -153,7 +169,7 @@ export interface Show {
153
169
  root: Fingerprint;
154
170
  /** "disclosures" reveal covered fields of the record on that page. */
155
171
  pages: { index: number; entry: string; path: Fingerprint[]; disclosures?: string[] }[];
156
- seal?: { seal: SignedRecord; stamps?: StampItem[] };
172
+ seal?: { seal: SignedRecord; stamps?: StampItem[]; witnesses?: SignedRecord[] };
157
173
  }
158
174
 
159
175
  // --- what the checker answers ---
@@ -184,6 +200,28 @@ export interface Stamp {
184
200
  /** For a block: its number, as the proof states it. Nothing checks it. */
185
201
  height?: number;
186
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
+ }
187
225
  /** A time-stamp beside a seal or a cancellation: a service's answer, or a proof with the header of its block. */
188
226
  export type StampItem = Base64url | { block: Base64url; proof: Base64url };
189
227
  /** Who vouches for a name, if anyone does. */
@@ -191,7 +229,7 @@ export type Vouched = { counted: boolean; by: string; keys: Fingerprint } | null
191
229
 
192
230
  export interface CheckedEntry {
193
231
  index: number;
194
- 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';
195
233
  fingerprint: Fingerprint | null;
196
234
  /** How much of the entry's own signing this device confirmed. */
197
235
  verified: 'all' | 'some' | 'none';
@@ -242,6 +280,8 @@ export interface CheckedEntry {
242
280
  service?: string | null;
243
281
  sealer?: Fingerprint;
244
282
  stamps?: Stamp[];
283
+ /** A seal: the witness statements on its line. */
284
+ witnesses?: WitnessCheck[];
245
285
  counted?: boolean;
246
286
  }
247
287
 
@@ -266,11 +306,18 @@ export interface Summary {
266
306
  seals: number;
267
307
  cancellations: number;
268
308
  acknowledgements: number;
309
+ denials: number;
269
310
  vouchings: number;
270
311
  passes: number;
271
312
  };
272
313
  /** How far the last time-stamp from a trusted service reaches. */
273
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;
274
321
  /** What no check can show, in plain sentences. */
275
322
  limits: string[];
276
323
  }
@@ -318,6 +365,46 @@ export interface HeldAcknowledgement {
318
365
  inBook: number | null;
319
366
  }
320
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
+
321
408
  export interface CheckResult {
322
409
  format: string;
323
410
  size: number;
@@ -325,8 +412,8 @@ export interface CheckResult {
325
412
  problems: Finding[];
326
413
  notes: string[];
327
414
  entries: CheckedEntry[];
328
- /** What was handed over beside the book, and what came of it. */
329
- 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)[];
330
417
  summary: Summary;
331
418
  }
332
419
 
@@ -341,6 +428,8 @@ export interface CheckOptions {
341
428
  blocks?: string[];
342
429
  /** The key set fingerprints of the organisations whose vouching the checker trusts. */
343
430
  vouchers?: Fingerprint[];
431
+ /** The key set fingerprints of the witnesses the checker trusts (format description, section 29). */
432
+ witnesses?: Fingerprint[];
344
433
  /** For covered fields: the disclosures handed over, by the fingerprint of the slip they belong to. */
345
434
  disclosures?: Record<Fingerprint, string[]>;
346
435
  /**
@@ -349,6 +438,11 @@ export interface CheckOptions {
349
438
  * out, or holds them with a later time-stamp.
350
439
  */
351
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[];
352
446
  expectedRoot?: Fingerprint;
353
447
  expectedSize?: number;
354
448
  /** Signing methods to treat as not built into this device. It can only withhold a pass. */
@@ -391,6 +485,14 @@ export function checkSlip(record: SignedRecord, options?: Pick<CheckOptions, 'is
391
485
  export function methodAvailable(alg: string): Promise<boolean>;
392
486
  export function thumbprint(key: PublicKey): Promise<string>;
393
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>;
394
496
  /** The fingerprint of a block, as it is written everywhere: 64 hex characters. */
395
497
  export function blockFingerprint(header: Uint8Array): Promise<string>;
396
498
  /** A chain of Bitcoin block headers, checked on this device from the chain's first block. */
@@ -418,6 +520,8 @@ export function conditionWords(condition: Condition): string;
418
520
  export function neverWords(name: string): string;
419
521
  export const LIMITS: string[];
420
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>;
421
525
 
422
526
  export interface Proposal extends Request {
423
527
  slip: Fingerprint;
@@ -451,8 +555,30 @@ export interface Recorder {
451
555
  act<T>(
452
556
  request: Request,
453
557
  perform: () => Promise<T>,
454
- more?: { when?: number | Date; terms?: Fingerprint; approval?: { record: SignedRecord }; countersign?: (stub: SignedRecord) => Promise<SignedRecord | null> },
455
- ): Promise<{ done: boolean; answer: BeforeAnswer; result?: T; stub?: WrittenStub }>;
558
+ more?: {
559
+ when?: number | Date;
560
+ terms?: Fingerprint;
561
+ approval?: { record: SignedRecord };
562
+ countersign?: (stub: SignedRecord) => Promise<SignedRecord | null>;
563
+ /**
564
+ * Up to four further documents, from what the action gave back. The stub is then signed after the action,
565
+ * in the same turn, and keeps the time the action began. The request may then name at most 28 documents,
566
+ * and no approval may be given. If it fails, the stub is written without them, and "detailsAfter" in the
567
+ * answer says why.
568
+ */
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;
572
+ },
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;
456
582
  book(): string;
457
583
  /**
458
584
  * What the record shows as it stands: the answer a whole check of the book gives, with the cancellations
@@ -476,12 +602,18 @@ export interface Recorder {
476
602
  */
477
603
  add(entry: BookEntry): Promise<{ acknowledgement?: SignedRecord }>;
478
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 };
479
610
  /** A stub the stub writer wrote. A countersignature that did not check is left out, and "problem" says why. */
480
611
  export type WrittenStub = Written & { seq: number; line: string; countersignature: { accepted: boolean; problem: Finding | null } };
481
612
  export function openRecorder(o: {
482
613
  book: string;
483
614
  slip: Fingerprint;
484
- privateKeys: CryptoKey[];
615
+ /** The agent's private keys, or signers: Ed25519, then ML-DSA-87. A signer must hold the keys the slip, or the pass, names. */
616
+ privateKeys: SigningKey[];
485
617
  issuerKeys: string[];
486
618
  pass?: Fingerprint;
487
619
  options?: CheckOptions;
@@ -489,6 +621,12 @@ export function openRecorder(o: {
489
621
  now?: () => number;
490
622
  /** How long the other side is given to countersign, in milliseconds. Defaults to 30,000. */
491
623
  countersignWithin?: number;
624
+ /** How long a signer is given for each signature, in milliseconds. Defaults to 30,000. After that nothing is written. */
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;
492
630
  }): Promise<Recorder>;
493
631
 
494
632
  /** One of an agent's tools, as it is described to recordTools. */
@@ -500,7 +638,7 @@ export interface RecordedTool<A = any, R = any> {
500
638
  /** The id of the service the tool deals with, as the slip names it. */
501
639
  with?: string | ((args: A) => string | undefined);
502
640
  amount?: Amount | ((args: A) => Amount | undefined);
503
- /** 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. */
504
642
  details?: false | ((args: A) => DocumentRef[] | Promise<DocumentRef[]>);
505
643
  /** Asks the other side to countersign the stub. */
506
644
  countersign?: (stub: SignedRecord, args: A) => Promise<SignedRecord | null>;
@@ -509,9 +647,11 @@ export interface RecordedTool<A = any, R = any> {
509
647
  }
510
648
  /** A call through a recorded tool that was not run. "answer" is what the check before acting said. */
511
649
  export class NotTaken extends Error {
512
- constructor(tool: string, answer: BeforeAnswer);
650
+ constructor(tool: string, answer: BeforeAnswer, denial?: DenialAnswer);
513
651
  tool: string;
514
652
  answer: BeforeAnswer;
653
+ /** Where the stub writer writes denials: what it did about the denial for this call. */
654
+ denial?: DenialAnswer;
515
655
  }
516
656
  /**
517
657
  * Puts an agent's tools behind the stub writer: each call asks first, runs the tool only if the slip allows
@@ -564,21 +704,21 @@ export function writeStub(
564
704
  when?: number | Date;
565
705
  id?: Base64url;
566
706
  },
567
- privateKeys: CryptoKey[],
707
+ privateKeys: SigningKey[],
568
708
  ): Promise<Written & { seq: number }>;
569
709
  /** An agent hands part of its permission to a helper agent. */
570
710
  export function writePass(
571
711
  fields: { slip: Fingerprint; from?: Fingerprint; to: Party; actions: string[]; limits?: Limit[]; validFrom: Time; validUntil: Time; when?: number | Date; id?: Base64url },
572
- privateKeys: CryptoKey[],
712
+ privateKeys: SigningKey[],
573
713
  ): Promise<Written>;
574
- export function countersign(stub: SignedRecord, privateKeys: CryptoKey[], when?: number | Date): Promise<SignedRecord>;
714
+ export function countersign(stub: SignedRecord, privateKeys: SigningKey[], when?: number | Date): Promise<SignedRecord>;
575
715
  export function writeRefusal(
576
716
  fields: { slip: Fingerprint; by: Party; action: string; amount?: Amount; reason: string; when?: number | Date; id?: Base64url },
577
- privateKeys: CryptoKey[],
717
+ privateKeys: SigningKey[],
578
718
  ): Promise<Written>;
579
719
  export function writeTerms(
580
720
  fields: { by: Party; accepts: string[]; never?: string[]; validFrom: Time; validUntil: Time; id?: Base64url },
581
- privateKeys: CryptoKey[],
721
+ privateKeys: SigningKey[],
582
722
  ): Promise<Written>;
583
723
  export type VouchedFor =
584
724
  | { kind: 'person'; key: PasskeyKey; name: string }
@@ -586,21 +726,163 @@ export type VouchedFor =
586
726
  | { kind: 'recorder'; keys: SealKeySet; name: string };
587
727
  export function writeVouching(
588
728
  fields: { by: Party; for: VouchedFor; validFrom: Time; validUntil: Time; when?: number | Date; id?: Base64url },
589
- privateKeys: CryptoKey[],
729
+ privateKeys: SigningKey[],
590
730
  ): Promise<Written>;
591
- export function writeWithdrawal(fields: { vouching: Fingerprint; when?: number | Date; id?: Base64url }, privateKeys: CryptoKey[]): Promise<Written>;
731
+ export function writeWithdrawal(fields: { vouching: Fingerprint; when?: number | Date; id?: Base64url }, privateKeys: SigningKey[]): Promise<Written>;
592
732
  /** The agent's side states that it was handed the person's cancellation of a slip, and when. */
593
733
  export function writeAcknowledgement(
594
734
  fields: { slip: Fingerprint; cancellation: Fingerprint; pass?: Fingerprint; when?: number | Date; id?: Base64url },
595
- privateKeys: CryptoKey[],
735
+ privateKeys: SigningKey[],
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[],
596
745
  ): Promise<Written>;
597
- /** It needs Node.js: no browser has the seal's third signing method. */
746
+ /**
747
+ * It needs Node.js: no browser has the seal's third signing method. "privateKeys": the recorder's three keys (two
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
749
+ * JSON Web Key with its format), or signers, each holding the key "by" names in its place.
750
+ */
598
751
  export function writeSeal(
599
752
  book: string,
600
- 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 },
601
755
  privateKeys: unknown[],
602
756
  ): Promise<Written & { fingerprintBytes: Uint8Array }>;
603
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
+
604
886
  /** A proof from a public blockchain and the 80-byte header of its block, in the form they have in "stamps". */
605
887
  export function blockStampItem(proof: Uint8Array, header: Uint8Array): { block: Base64url; proof: Base64url };
606
888
 
@@ -612,6 +894,8 @@ export function makeShow(text: string, indexes: number[], options?: { seal?: num
612
894
 
613
895
  export function generateKeySet(): Promise<{ keys: KeySet; privateKeys: CryptoKey[] }>;
614
896
  export function generateSealKeySet(): Promise<{ keys: SealKeySet; privateKeys: unknown[] }>;
897
+ /** A private key held in memory, wrapped as a signer: a CryptoKey, or for SLH-DSA-SHA2-256s a Node.js KeyObject. */
898
+ export function signerFromKey(alg: 'Ed25519' | 'ML-DSA-87' | 'SLH-DSA-SHA2-256s', privateKey: unknown, key: Ed25519Key | MlDsaKey | SlhDsaKey): Signer;
615
899
  export function fingerprint(contentBytes: Uint8Array): Promise<Fingerprint>;
616
900
  export function sha256(bytes: Uint8Array): Promise<Uint8Array>;
617
901
  export function formatTime(when: number | Date): Time;
@@ -623,6 +907,16 @@ export function utf8(text: string): Uint8Array;
623
907
  // --- the shared list ---
624
908
 
625
909
  export const RESERVED_PREFIX: 'provared.';
910
+ /** Document names that begin with this are reserved: only those in DOCUMENT_NAMES may be used. */
911
+ export const RESERVED_DOCUMENT_PREFIX: 'provared.';
912
+ /** The reserved document names, and the exact text whose SHA-256 each one gives (format description, section 27). */
913
+ export const DOCUMENT_NAMES: Readonly<Record<string, string>>;
914
+ /** For "provared.http-request": the method and the address exactly as sent, and the body's bytes where there is a body. */
915
+ export function httpRequestFingerprint(r: { method: string; url: string; body?: Uint8Array }): Promise<Fingerprint>;
916
+ /** For "provared.http-response": the status code, and the body's bytes where there is a body. */
917
+ export function httpResponseFingerprint(r: { status: number; body?: Uint8Array }): Promise<Fingerprint>;
918
+ /** 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. */
919
+ export function identifierFingerprint(id: string): Promise<Fingerprint>;
626
920
  export const ACTION_KINDS: Record<string, string>;
627
921
  export const SHARED_ACTIONS: Record<string, [kind: string, meaning: string]>;
628
922
  export const CONDUCT_RULES: Record<string, 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
+ }