selfstore 1.8.25 → 1.8.26

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/README.md CHANGED
@@ -182,6 +182,23 @@ const proof = await prove(bytes, { notary, label: 'inventory', store, file });
182
182
  const done = await completeProof(proof, { notary, store });
183
183
  ```
184
184
 
185
+ An app does not track which receipts are still waiting: the store already
186
+ knows. At launch, `completePending` asks each notary for the incomplete proofs
187
+ it issued and writes back only those that came back complete; `proofsIn(store)`
188
+ reads every proof back, oldest deposit first, to list a series or rebuild a
189
+ screen on another device.
190
+
191
+ ```ts
192
+ import { completePending, proofsIn } from 'selfstore/proof';
193
+
194
+ const { completed, pending, refused } = await completePending({ store, notaries: [notary] });
195
+ for (const proof of proofsIn(store)) show(proof.file, proof.depositedAt, proof.complete);
196
+ ```
197
+
198
+ A notary that cannot be reached is not asked again in the same round, and its
199
+ proofs stay pending for the next launch; a completion answering for another
200
+ digest is set apart in `refused` and never overwrites the receipt.
201
+
185
202
  `verify` proves the bytes are the ones the receipt names. That the receipt is
186
203
  genuine is what the notary's evidence is for, checked with the notary's own
187
204
  verifier.
package/dist/proof.d.ts CHANGED
@@ -102,6 +102,36 @@ declare function completeProof(proof: Proof, options: {
102
102
  declare function verify(blob: FileBytes, receipt: Pick<Receipt, 'hash' | 'complete'> & {
103
103
  size?: number;
104
104
  }): Promise<ProofCheck>;
105
+ /** Where proofs are read back from: the simple store fits as is. */
106
+ interface ProofLibrary extends ProofShelf {
107
+ all(collection: string): readonly SimpleRecord[];
108
+ }
109
+ /**
110
+ * Every proof kept in `store`, oldest deposit first. Records that are not
111
+ * proofs - another app's, a damaged one - are skipped rather than trusted.
112
+ */
113
+ declare function proofsIn(store: Pick<ProofLibrary, 'all'>): Proof[];
114
+ /** What `completePending()` did, proof by proof. */
115
+ interface PendingRound {
116
+ /** Finished this round, and written back to the store. */
117
+ readonly completed: readonly Proof[];
118
+ /** Still waiting: the notary has nothing to add yet, could not be reached,
119
+ * or is not among the notaries given. Ask again on a later launch. */
120
+ readonly pending: readonly Proof[];
121
+ /** The notary answered for something else; kept as it was, not overwritten. */
122
+ readonly refused: readonly Proof[];
123
+ }
124
+ /**
125
+ * Ask each notary to finish the incomplete proofs it issued - the call an app
126
+ * makes at launch, since a notary that anchors its digests finishes a receipt
127
+ * hours after the deposit. A notary that cannot be reached is not asked again
128
+ * this round; a proof whose notary is not in `notaries` waits. Only a proof
129
+ * that came back complete is written to the store.
130
+ */
131
+ declare function completePending(options: {
132
+ store: ProofLibrary;
133
+ notaries: readonly Notary[];
134
+ }): Promise<PendingRound>;
105
135
 
106
136
  interface Rfc3161Options {
107
137
  /** The authority's endpoint. In a browser it must answer CORS, or sit
@@ -161,4 +191,4 @@ interface JournalOptions {
161
191
  */
162
192
  declare function digestJournalNotary(options: JournalOptions): Notary;
163
193
 
164
- export { type JournalOptions, type Notary, PROOFS, type Proof, type ProofCheck, type ProofShelf, type ProveOptions, type Receipt, type Rfc3161Options, type TimestampToken, completeProof, digestJournalNotary, prove, readTimestampReply, rfc3161Notary, timestampReplyOf, verify };
194
+ export { type JournalOptions, type Notary, PROOFS, type PendingRound, type Proof, type ProofCheck, type ProofLibrary, type ProofShelf, type ProveOptions, type Receipt, type Rfc3161Options, type TimestampToken, completePending, completeProof, digestJournalNotary, proofsIn, prove, readTimestampReply, rfc3161Notary, timestampReplyOf, verify };
package/dist/proof.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { toBytes } from './chunk-SJ3PKQPR.js';
2
2
  import { sha256Hex, hex } from './chunk-QSLHEE2L.js';
3
3
  import { toBase64, fromBase64 } from './chunk-WXI2NAPA.js';
4
- import { SelfstoreError } from './chunk-CSYEIXNY.js';
4
+ import { SelfstoreError, isSelfstoreError } from './chunk-CSYEIXNY.js';
5
5
 
6
6
  // src/proof/proof.ts
7
7
  var PROOFS = "proofs";
@@ -66,6 +66,52 @@ async function verify(blob, receipt) {
66
66
  const sizeAgrees = receipt.size === void 0 || receipt.size === bytes.length;
67
67
  return { matches: hash === expected && sizeAgrees, hash, complete: receipt.complete === true };
68
68
  }
69
+ function isProof(record) {
70
+ return record.kind === "selfstore-proof" && typeof record.hash === "string" && HEX_DIGEST.test(record.hash) && typeof record.notary === "string" && typeof record.complete === "boolean";
71
+ }
72
+ function proofsIn(store) {
73
+ return store.all(PROOFS).filter(isProof).map((record) => {
74
+ const { id: _id, ...proof } = record;
75
+ return proof;
76
+ }).sort((a, b) => Date.parse(a.depositedAt) - Date.parse(b.depositedAt));
77
+ }
78
+ function isCode(error, code) {
79
+ return isSelfstoreError(error) && error.code === code;
80
+ }
81
+ async function completePending(options) {
82
+ const byName = new Map(options.notaries.map((notary) => [notary.name, notary]));
83
+ const unreachable = /* @__PURE__ */ new Set();
84
+ const completed = [];
85
+ const pending = [];
86
+ const refused3 = [];
87
+ for (const proof of proofsIn(options.store)) {
88
+ if (proof.complete) continue;
89
+ const notary = byName.get(proof.notary);
90
+ if (notary === void 0 || unreachable.has(notary.name)) {
91
+ pending.push(proof);
92
+ continue;
93
+ }
94
+ try {
95
+ const answer = await completeProof(proof, { notary });
96
+ if (answer.complete) {
97
+ await shelve(answer, options.store);
98
+ completed.push(answer);
99
+ } else {
100
+ pending.push(proof);
101
+ }
102
+ } catch (error) {
103
+ if (isCode(error, "NOTARY_UNAVAILABLE")) {
104
+ unreachable.add(notary.name);
105
+ pending.push(proof);
106
+ } else if (isCode(error, "NOTARY_REFUSED")) {
107
+ refused3.push(proof);
108
+ } else {
109
+ throw error;
110
+ }
111
+ }
112
+ }
113
+ return { completed, pending, refused: refused3 };
114
+ }
69
115
 
70
116
  // src/proof/der.ts
71
117
  var DerError = class extends Error {
@@ -355,6 +401,6 @@ function digestJournalNotary(options) {
355
401
  };
356
402
  }
357
403
 
358
- export { PROOFS, completeProof, digestJournalNotary, prove, readTimestampReply, rfc3161Notary, timestampReplyOf, verify };
404
+ export { PROOFS, completePending, completeProof, digestJournalNotary, proofsIn, prove, readTimestampReply, rfc3161Notary, timestampReplyOf, verify };
359
405
  //# sourceMappingURL=proof.js.map
360
406
  //# sourceMappingURL=proof.js.map
package/dist/proof.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/proof/proof.ts","../src/proof/der.ts","../src/proof/reach.ts","../src/proof/rfc3161.ts","../src/proof/journal.ts"],"names":["hex","refused"],"mappings":"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","file":"proof.js","sourcesContent":["// Proof of existence for the user's own documents: the bytes never leave the\n// device, only their SHA-256 does. A notary dates that digest; the receipt it\n// hands back is kept next to the file, in the user's own storage, and anyone\n// holding the file and the receipt can check the two still belong together.\n\nimport { sha256Hex } from '../selfstore/digest';\nimport { SelfstoreError } from '../selfstore/errors';\nimport { toBytes, type FileBytes } from '../simple/bytes';\nimport type { SimpleRecord } from '../simple/simple';\n\n/**\n * What a notary hands back for one digest. `evidence` is the notary's own\n * material (a timestamp token, an inclusion path, a download address), as\n * strings - base64 for bytes - so a receipt is plain JSON and survives every\n * destination a store can write to.\n */\nexport interface Receipt {\n\t/** The notary that issued it (`Notary.name`). */\n\treadonly notary: string;\n\t/** Lowercase hex SHA-256 of the document. */\n\treadonly hash: string;\n\t/** The label sent with the deposit, if any. */\n\treadonly label?: string;\n\t/** ISO 8601 instant the notary vouches for (its clock, not the device's). */\n\treadonly depositedAt: string;\n\t/** False while the notary still has something to add (an anchor, a seal). */\n\treadonly complete: boolean;\n\treadonly evidence: Readonly<Record<string, string>>;\n}\n\n/**\n * Anyone who dates a digest. `deposit` must never see more than the digest and\n * the label; `complete` is called later - the next day, the next launch - and\n * returns the same receipt with whatever the notary could not give at once.\n * A notary that is final on deposit returns its argument unchanged.\n */\nexport interface Notary {\n\treadonly name: string;\n\tdeposit(hash: string, label?: string): Promise<Receipt>;\n\tcomplete(receipt: Receipt): Promise<Receipt>;\n}\n\n/** A receipt as `prove()` keeps it: the notary's, plus what the device measured. */\nexport interface Proof extends Receipt {\n\treadonly kind: 'selfstore-proof';\n\treadonly version: 1;\n\treadonly algorithm: 'SHA-256';\n\t/** Size of the document in bytes. */\n\treadonly size: number;\n\t/** The store file the proof belongs to, when there is one. */\n\treadonly file?: string;\n}\n\n/** Where proofs are kept: the simple store fits as is. */\nexport interface ProofShelf {\n\tput(collection: string, record: SimpleRecord): Promise<void>;\n}\n\n/** The collection `prove()` and `completeProof()` write to. */\nexport const PROOFS = 'proofs';\n\nexport interface ProveOptions {\n\tnotary: Notary;\n\t/** Sent to the notary with the digest. A TYPE ('invoice', 'inventory'),\n\t * never a name or a piece of the content: it leaves the device. */\n\tlabel?: string;\n\t/** Keep the proof in this store, under `PROOFS`, keyed by `file` or by hash. */\n\tstore?: ProofShelf;\n\t/** Id of the store file the document is (as `putFile` returned it). */\n\tfile?: string;\n}\n\n/** The result of `verify()`. */\nexport interface ProofCheck {\n\t/** The bytes hash to the receipt's digest (and size, when it carries one). */\n\treadonly matches: boolean;\n\t/** What the bytes hash to now. */\n\treadonly hash: string;\n\t/** Whether the notary had finished with this receipt. */\n\treadonly complete: boolean;\n}\n\nconst HEX_DIGEST = /^[0-9a-f]{64}$/;\n\nfunction refused(message: string): SelfstoreError {\n\treturn new SelfstoreError('NOTARY_REFUSED', message);\n}\n\n/** A notary that answers for another digest, or for another notary, is not believed. */\nfunction checkAnswer(answer: Receipt, hash: string, notary: Notary): void {\n\tif (typeof answer?.hash !== 'string' || answer.hash.toLowerCase() !== hash) {\n\t\tthrow refused(`${notary.name} answered for another digest`);\n\t}\n\tif (answer.notary !== notary.name) {\n\t\tthrow refused(`receipt names ${String(answer.notary)}, not ${notary.name}`);\n\t}\n\tif (Number.isNaN(Date.parse(answer.depositedAt))) {\n\t\tthrow refused(`${notary.name} gave no readable deposit time`);\n\t}\n}\n\nasync function shelve(proof: Proof, store: ProofShelf | undefined): Promise<void> {\n\tif (store) await store.put(PROOFS, { ...proof, id: proof.file ?? proof.hash });\n}\n\n/**\n * Hash `blob` on the device, deposit the digest with `options.notary`, and\n * return the proof - kept in `options.store` too when one is given. Only the\n * digest and the label leave the device.\n *\n * Throws NOTARY_UNAVAILABLE when the notary cannot be reached (retry later)\n * and NOTARY_REFUSED when it refuses or answers for something else.\n */\nexport async function prove(blob: FileBytes, options: ProveOptions): Promise<Proof> {\n\tconst bytes = await toBytes(blob);\n\tconst hash = await sha256Hex(bytes);\n\tconst answer = await options.notary.deposit(hash, options.label);\n\tcheckAnswer(answer, hash, options.notary);\n\tconst proof: Proof = {\n\t\t...answer,\n\t\thash,\n\t\tkind: 'selfstore-proof',\n\t\tversion: 1,\n\t\talgorithm: 'SHA-256',\n\t\tsize: bytes.length,\n\t\t...(options.file === undefined ? {} : { file: options.file })\n\t};\n\tawait shelve(proof, options.store);\n\treturn proof;\n}\n\n/**\n * Ask the notary for what it could not give on deposit. A complete proof is\n * returned as is, without a call. The fields the device measured are kept;\n * the notary may only add to its own.\n */\nexport async function completeProof(\n\tproof: Proof,\n\toptions: { notary: Notary; store?: ProofShelf }\n): Promise<Proof> {\n\tif (proof.complete) return proof;\n\tconst answer = await options.notary.complete(proof);\n\tcheckAnswer(answer, proof.hash, options.notary);\n\tconst completed: Proof = {\n\t\t...answer,\n\t\thash: proof.hash,\n\t\tkind: proof.kind,\n\t\tversion: proof.version,\n\t\talgorithm: proof.algorithm,\n\t\tsize: proof.size,\n\t\t...(proof.file === undefined ? {} : { file: proof.file })\n\t};\n\tawait shelve(completed, options.store);\n\treturn completed;\n}\n\n/**\n * Hash `blob` again and compare it with the receipt. This proves the bytes are\n * the ones the receipt names; that the receipt itself is genuine is what the\n * notary's evidence is for, checked with the notary's own verifier.\n *\n * Throws BAD_FORMAT on a receipt that carries no SHA-256 digest at all - a\n * damaged receipt is not a document that changed.\n */\nexport async function verify(\n\tblob: FileBytes,\n\treceipt: Pick<Receipt, 'hash' | 'complete'> & { size?: number }\n): Promise<ProofCheck> {\n\tconst expected = typeof receipt?.hash === 'string' ? receipt.hash.toLowerCase() : '';\n\tif (!HEX_DIGEST.test(expected)) {\n\t\tthrow new SelfstoreError('BAD_FORMAT', 'the receipt carries no SHA-256 digest');\n\t}\n\tconst bytes = await toBytes(blob);\n\tconst hash = await sha256Hex(bytes);\n\tconst sizeAgrees = receipt.size === undefined || receipt.size === bytes.length;\n\treturn { matches: hash === expected && sizeAgrees, hash, complete: receipt.complete === true };\n}\n","// The few DER shapes an RFC 3161 exchange needs, and no more. A timestamp\n// request is five fields and the reply is read along one path, so a general\n// ASN.1 library would be a fourth runtime dependency for two functions.\n\n/** One decoded TLV: `start` and `end` bound its CONTENT. */\nexport interface DerNode {\n\treadonly tag: number;\n\treadonly start: number;\n\treadonly end: number;\n}\n\nexport class DerError extends Error {}\n\nfunction encodeLength(length: number): Uint8Array {\n\tif (length < 0x80) return Uint8Array.of(length);\n\tconst bytes: number[] = [];\n\tfor (let rest = length; rest > 0; rest = Math.floor(rest / 256)) bytes.unshift(rest % 256);\n\treturn Uint8Array.of(0x80 | bytes.length, ...bytes);\n}\n\n/** Encode one TLV around already-encoded content. */\nexport function tlv(tag: number, ...parts: Uint8Array[]): Uint8Array {\n\tconst length = parts.reduce((sum, part) => sum + part.length, 0);\n\tconst head = encodeLength(length);\n\tconst out = new Uint8Array(1 + head.length + length);\n\tout[0] = tag;\n\tout.set(head, 1);\n\tlet at = 1 + head.length;\n\tfor (const part of parts) {\n\t\tout.set(part, at);\n\t\tat += part.length;\n\t}\n\treturn out;\n}\n\n/** Read the TLV that starts at `offset`. Indefinite lengths are BER, not DER: refused. */\nexport function readNode(bytes: Uint8Array, offset: number, limit = bytes.length): DerNode {\n\tif (offset + 2 > limit) throw new DerError('truncated header');\n\tconst tag = bytes[offset];\n\tconst first = bytes[offset + 1];\n\tlet start = offset + 2;\n\tlet length = first;\n\tif (first & 0x80) {\n\t\tconst count = first & 0x7f;\n\t\tif (count === 0 || count > 4) throw new DerError('unsupported length');\n\t\tif (start + count > limit) throw new DerError('truncated length');\n\t\tlength = 0;\n\t\tfor (let i = 0; i < count; i++) length = length * 256 + bytes[start + i];\n\t\tstart += count;\n\t}\n\tconst end = start + length;\n\tif (end > limit) throw new DerError('content runs past its parent');\n\treturn { tag, start, end };\n}\n\n/** The direct children of a constructed node. */\nexport function childrenOf(bytes: Uint8Array, node: DerNode): DerNode[] {\n\tconst out: DerNode[] = [];\n\tfor (let at = node.start; at < node.end;) {\n\t\tconst child = readNode(bytes, at, node.end);\n\t\tout.push(child);\n\t\tat = child.end;\n\t}\n\treturn out;\n}\n\n/** The node's content, or a refusal when its tag is not the one expected. */\nexport function expectTag(node: DerNode | undefined, tag: number): DerNode {\n\tif (node?.tag !== tag) throw new DerError(`expected tag 0x${tag.toString(16)}`);\n\treturn node;\n}\n\nexport function contentOf(bytes: Uint8Array, node: DerNode): Uint8Array {\n\treturn bytes.subarray(node.start, node.end);\n}\n","// One way to call a notary over HTTP, so every adapter maps the same failures\n// to the same codes: unreachable, overloaded or rate-limited is worth a retry,\n// any other refusal is not.\n\nimport { SelfstoreError } from '../selfstore/errors';\n\n/** Fetch `url`, throwing NOTARY_UNAVAILABLE or NOTARY_REFUSED instead of returning a failure. */\nexport async function reach(\n\tsend: typeof fetch,\n\turl: string,\n\tinit: RequestInit | undefined,\n\twho: string\n): Promise<Response> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await send(url, init);\n\t} catch (e) {\n\t\tthrow new SelfstoreError(\n\t\t\t'NOTARY_UNAVAILABLE',\n\t\t\t`${who} could not be reached: ${(e as Error).message}`\n\t\t);\n\t}\n\tif (!response.ok) {\n\t\tconst code =\n\t\t\tresponse.status >= 500 || response.status === 429 ? 'NOTARY_UNAVAILABLE' : 'NOTARY_REFUSED';\n\t\tthrow new SelfstoreError(code, `${who} answered ${response.status}`);\n\t}\n\treturn response;\n}\n","// A notary over any RFC 3161 time-stamping authority. The authority signs the\n// digest and the time in one token, so the receipt is complete on deposit: the\n// token (the `.tsr` file) is the evidence, and `openssl ts -verify` checks it\n// against the authority's certificate without this library.\n\nimport { fromBase64, toBase64 } from '../selfstore/base64';\nimport { hex } from '../selfstore/digest';\nimport { SelfstoreError } from '../selfstore/errors';\nimport { childrenOf, contentOf, DerError, expectTag, readNode, tlv, type DerNode } from './der';\nimport type { Notary, Receipt } from './proof';\nimport { reach } from './reach';\n\nconst SEQUENCE = 0x30;\nconst INTEGER = 0x02;\nconst OCTET_STRING = 0x04;\nconst OID = 0x06;\nconst GENERALIZED_TIME = 0x18;\nconst EXPLICIT_0 = 0xa0;\n\n/** id-sha256, 2.16.840.1.101.3.4.2.1, with its NULL parameters. */\nconst SHA256_ALGORITHM = tlv(\n\tSEQUENCE,\n\ttlv(OID, Uint8Array.of(0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01)),\n\tUint8Array.of(0x05, 0x00)\n);\n\nexport interface Rfc3161Options {\n\t/** The authority's endpoint. In a browser it must answer CORS, or sit\n\t * behind a relay of yours that does: most public authorities do not. */\n\turl: string;\n\t/** Receipt `notary` field. Defaults to 'rfc3161'. */\n\tname?: string;\n\t/** Injected for tests and relays; defaults to the global fetch. */\n\tfetch?: typeof fetch;\n}\n\n/** What a timestamp token says, read without checking its signature. */\nexport interface TimestampToken {\n\t/** Lowercase hex of the digest the authority stamped. */\n\treadonly hash: string;\n\t/** ISO 8601 instant the authority signed. */\n\treadonly time: string;\n\t/** The nonce echoed back, as lowercase hex, when there is one. */\n\treadonly nonce?: string;\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n\treturn Uint8Array.from(hex.match(/../g) ?? [], (pair) => Number.parseInt(pair, 16));\n}\n\n/** Leading zero bytes are DER's sign padding, not part of the value. */\nfunction integerHex(bytes: Uint8Array): string {\n\tlet at = 0;\n\twhile (at < bytes.length - 1 && bytes[at] === 0) at++;\n\treturn hex(bytes.subarray(at));\n}\n\n/** Eight random bytes, positive and minimal as a DER INTEGER. */\nfunction newNonce(): Uint8Array {\n\tconst nonce = crypto.getRandomValues(new Uint8Array(8));\n\tnonce[0] = (nonce[0] & 0x3f) | 0x40;\n\treturn nonce;\n}\n\n/** A TimeStampReq (RFC 3161 section 2.4.1) for a SHA-256 digest, asking for the certificate. */\nexport function timestampRequest(hash: string, nonce: Uint8Array): Uint8Array {\n\treturn tlv(\n\t\tSEQUENCE,\n\t\ttlv(INTEGER, Uint8Array.of(1)),\n\t\ttlv(SEQUENCE, SHA256_ALGORITHM, tlv(OCTET_STRING, hexToBytes(hash))),\n\t\ttlv(INTEGER, nonce),\n\t\tUint8Array.of(0x01, 0x01, 0xff)\n\t);\n}\n\n/** YYYYMMDDHHMMSS[.fff]Z to ISO 8601. */\nfunction generalizedTime(text: string): string {\n\tconst parts = /^(\\d{4})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\.\\d+)?Z$/.exec(text);\n\tif (parts === null) throw new DerError(`unreadable time ${text}`);\n\tconst [, y, mo, d, h, mi, s, fraction] = parts;\n\treturn new Date(`${y}-${mo}-${d}T${h}:${mi}:${s}${fraction ?? ''}Z`).toISOString();\n}\n\n/** TimeStampResp: the status, or the token when it was granted. */\nfunction tokenOf(bytes: Uint8Array): DerNode {\n\tconst [statusInfo, token] = childrenOf(bytes, expectTag(readNode(bytes, 0), SEQUENCE));\n\tconst status = expectTag(childrenOf(bytes, expectTag(statusInfo, SEQUENCE))[0], INTEGER);\n\tconst code = Number.parseInt(integerHex(contentOf(bytes, status)), 16);\n\t// 0 granted, 1 granted with modifications; anything else is a refusal.\n\tif (code > 1) {\n\t\tthrow new SelfstoreError(\n\t\t\t'NOTARY_REFUSED',\n\t\t\t`the authority refused the request (status ${code})`\n\t\t);\n\t}\n\treturn expectTag(token, SEQUENCE);\n}\n\n/** ContentInfo > SignedData > EncapsulatedContentInfo > TSTInfo. */\nfunction tstInfoOf(bytes: Uint8Array, token: DerNode): DerNode {\n\tconst signedData = expectTag(\n\t\tchildrenOf(bytes, expectTag(childrenOf(bytes, token)[1], EXPLICIT_0))[0],\n\t\tSEQUENCE\n\t);\n\tconst encapsulated = expectTag(childrenOf(bytes, signedData)[2], SEQUENCE);\n\tconst wrapper = expectTag(childrenOf(bytes, encapsulated)[1], EXPLICIT_0);\n\tconst octets = expectTag(childrenOf(bytes, wrapper)[0], OCTET_STRING);\n\treturn expectTag(readNode(bytes, octets.start, octets.end), SEQUENCE);\n}\n\n/**\n * Read the digest, the time and the nonce out of a TimeStampResp (the `.tsr`\n * file). It does NOT check the authority's signature - that is what\n * `openssl ts -verify` is for; it is what tells a token for another document\n * apart before anything is kept. Throws NOTARY_REFUSED on a refusal or on\n * anything that is not a timestamp reply.\n */\nexport function readTimestampReply(bytes: Uint8Array): TimestampToken {\n\ttry {\n\t\tconst tstInfo = tstInfoOf(bytes, tokenOf(bytes));\n\t\t// version, policy, messageImprint, serialNumber, genTime, then optional\n\t\t// accuracy, ordering and nonce - the nonce is the only INTEGER among them.\n\t\tconst [, , imprint, , time, ...rest] = childrenOf(bytes, tstInfo);\n\t\tconst digest = expectTag(childrenOf(bytes, expectTag(imprint, SEQUENCE))[1], OCTET_STRING);\n\t\tconst nonce = rest.find((node) => node.tag === INTEGER);\n\t\tconst genTime = new TextDecoder().decode(contentOf(bytes, expectTag(time, GENERALIZED_TIME)));\n\t\treturn {\n\t\t\thash: hex(contentOf(bytes, digest)),\n\t\t\ttime: generalizedTime(genTime),\n\t\t\t...(nonce ? { nonce: integerHex(contentOf(bytes, nonce)) } : {})\n\t\t};\n\t} catch (e) {\n\t\tif (e instanceof SelfstoreError) throw e;\n\t\tthrow new SelfstoreError('NOTARY_REFUSED', `not a timestamp reply: ${(e as Error).message}`);\n\t}\n}\n\nasync function post(options: Rfc3161Options, body: Uint8Array): Promise<Uint8Array> {\n\tconst response = await reach(\n\t\toptions.fetch ?? globalThis.fetch,\n\t\toptions.url,\n\t\t{\n\t\t\tmethod: 'POST',\n\t\t\theaders: { 'content-type': 'application/timestamp-query' },\n\t\t\tbody: body as BodyInit\n\t\t},\n\t\t'the authority'\n\t);\n\treturn new Uint8Array(await response.arrayBuffer());\n}\n\n/**\n * A notary over an RFC 3161 time-stamping authority. The receipt is complete\n * on deposit; its evidence is the reply, base64 under `tsr`, and the\n * authority's address under `tsa`. The label is never sent: RFC 3161 has no\n * field for it, so it stays on the receipt.\n */\nexport function rfc3161Notary(options: Rfc3161Options): Notary {\n\tconst name = options.name ?? 'rfc3161';\n\treturn {\n\t\tname,\n\t\tasync deposit(hash: string, label?: string): Promise<Receipt> {\n\t\t\tconst nonce = newNonce();\n\t\t\tconst reply = await post(options, timestampRequest(hash, nonce));\n\t\t\tconst token = readTimestampReply(reply);\n\t\t\tif (token.hash !== hash) {\n\t\t\t\tthrow new SelfstoreError('NOTARY_REFUSED', 'the authority stamped another digest');\n\t\t\t}\n\t\t\tif (token.nonce !== integerHex(nonce)) {\n\t\t\t\tthrow new SelfstoreError('NOTARY_REFUSED', 'the reply does not answer this request');\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tnotary: name,\n\t\t\t\thash,\n\t\t\t\t...(label === undefined ? {} : { label }),\n\t\t\t\tdepositedAt: token.time,\n\t\t\t\tcomplete: true,\n\t\t\t\tevidence: { tsr: toBase64(reply), tsa: options.url }\n\t\t\t};\n\t\t},\n\t\tcomplete(receipt: Receipt): Promise<Receipt> {\n\t\t\treturn Promise.resolve(receipt);\n\t\t}\n\t};\n}\n\n/** The `.tsr` bytes of an RFC 3161 receipt, to offer as a download. */\nexport function timestampReplyOf(receipt: Pick<Receipt, 'evidence'>): Uint8Array | undefined {\n\tconst tsr = receipt.evidence.tsr;\n\treturn tsr === undefined ? undefined : fromBase64(tsr);\n}\n","// A notary over a public digest journal: a service that takes a SHA-256 with\n// no account, writes it as one line of a public deposits file, and folds that\n// file into a Merkle tree it anchors in Bitcoin (OpenTimestamps) and often\n// stamps under RFC 3161 too. The deposit answers at once with the journal's\n// own receipt; the anchor comes hours later, so `complete` is what fetches it.\n//\n// The protocol, relative to the journal's base URL:\n// POST /api/depots {\"sha256\", \"libelle\"?} -> 201\n// {\"id\", \"jour\", \"place\", \"ligne\", \"recu\": {\"sha256\", \"depose\", ...}}\n// GET /empreintes/depots.jsonl one JSON line per deposit, in order, with \"recu\" = id\n// GET /tours.json {\"tours\": [{\"id\", \"racine\", \"depots\", \"ots\", \"bloc\",\n// \"horodatage\", \"fichiers\"}]}; a tour covers the\n// first `depots` lines of the deposits file\n// GET /empreintes/<tour>.racine.ots, /empreintes/<tour>.racine.tsr\n\nimport { toBase64 } from '../selfstore/base64';\nimport { SelfstoreError } from '../selfstore/errors';\nimport type { Notary, Receipt } from './proof';\nimport { reach } from './reach';\n\nexport interface JournalOptions {\n\t/** The journal's base URL, e.g. 'https://journal.example'. */\n\turl: string;\n\t/** Receipt `notary` field. Defaults to 'digest-journal'. */\n\tname?: string;\n\t/** Injected for tests and relays; defaults to the global fetch. */\n\tfetch?: typeof fetch;\n}\n\ninterface Deposit {\n\tid: string;\n\tligne: string;\n\trecu: { sha256: string; depose: string };\n}\n\ninterface Tour {\n\tid: string;\n\tracine: string;\n\tdepots: number;\n\tots: string;\n\tbloc: number | null;\n\thorodatage: string | null;\n\tfichiers?: string[];\n}\n\nconst HEX_ID = /^[0-9a-f]{64}$/;\n\nfunction refused(message: string): SelfstoreError {\n\treturn new SelfstoreError('NOTARY_REFUSED', message);\n}\n\nfunction readDeposit(body: unknown): Deposit {\n\tconst d = body as Partial<Deposit> | null;\n\tif (\n\t\ttypeof d?.id !== 'string' ||\n\t\t!HEX_ID.test(d.id) ||\n\t\ttypeof d.ligne !== 'string' ||\n\t\ttypeof d.recu?.sha256 !== 'string' ||\n\t\ttypeof d.recu.depose !== 'string'\n\t) {\n\t\tthrow refused('the journal answered without a receipt');\n\t}\n\treturn d as Deposit;\n}\n\nfunction parseLine(text: string): { recu?: string; sha256?: string } {\n\ttry {\n\t\treturn JSON.parse(text) as { recu?: string; sha256?: string };\n\t} catch {\n\t\tthrow refused('the journal published an unreadable deposits file');\n\t}\n}\n\n/** 1-based rank of the deposit line whose receipt id is `id`, or 0. */\nfunction rankOf(deposits: string, id: string, hash: string): number {\n\tconst lines = deposits.split('\\n').filter(Boolean);\n\tfor (const [index, text] of lines.entries()) {\n\t\tconst line = parseLine(text);\n\t\tif (line.recu !== id) continue;\n\t\tif (line.sha256 !== hash) throw refused('the journal lists this receipt for another digest');\n\t\treturn index + 1;\n\t}\n\treturn 0;\n}\n\n/**\n * A notary over a public digest journal (the protocol is at the top of this\n * file). The label is sent as the journal's `libelle`: keep it a TYPE.\n *\n * On deposit the receipt is incomplete; its evidence holds the journal's\n * address, the receipt `id`, the public `line` and the journal's own `receipt`\n * as JSON - the file its verifier reads. `complete` finds the first tour that\n * covers the line and, once that tour is anchored in Bitcoin, completes the\n * receipt with `tour`, `root`, `block`, the `.ots` proof and, when the journal\n * stamped that root, the `.tsr` token (both base64). Checking the Merkle path\n * from the line to the root is the journal's verifier's job.\n */\nexport function digestJournalNotary(options: JournalOptions): Notary {\n\tconst name = options.name ?? 'digest-journal';\n\tconst base = options.url.replace(/\\/+$/, '');\n\tconst send = options.fetch ?? globalThis.fetch;\n\tconst get = (path: string): Promise<Response> =>\n\t\treach(send, `${base}${path}`, undefined, 'the journal');\n\tconst bytesOf = async (path: string): Promise<string> =>\n\t\ttoBase64(new Uint8Array(await (await get(path)).arrayBuffer()));\n\n\treturn {\n\t\tname,\n\t\tasync deposit(hash: string, label?: string): Promise<Receipt> {\n\t\t\tconst response = await reach(\n\t\t\t\tsend,\n\t\t\t\t`${base}/api/depots`,\n\t\t\t\t{\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { 'content-type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify({ sha256: hash, ...(label === undefined ? {} : { libelle: label }) })\n\t\t\t\t},\n\t\t\t\t'the journal'\n\t\t\t);\n\t\t\tconst deposit = readDeposit(await response.json().catch(() => null));\n\t\t\treturn {\n\t\t\t\tnotary: name,\n\t\t\t\thash: deposit.recu.sha256,\n\t\t\t\t...(label === undefined ? {} : { label }),\n\t\t\t\tdepositedAt: deposit.recu.depose,\n\t\t\t\tcomplete: false,\n\t\t\t\tevidence: {\n\t\t\t\t\tjournal: base,\n\t\t\t\t\tid: deposit.id,\n\t\t\t\t\tline: deposit.ligne,\n\t\t\t\t\treceipt: JSON.stringify(deposit.recu)\n\t\t\t\t}\n\t\t\t};\n\t\t},\n\t\tasync complete(receipt: Receipt): Promise<Receipt> {\n\t\t\tconst id = receipt.evidence.id;\n\t\t\tif (id === undefined) throw refused('the receipt carries no journal id');\n\t\t\tconst rank = rankOf(await (await get('/empreintes/depots.jsonl')).text(), id, receipt.hash);\n\t\t\tif (rank === 0) return receipt;\n\t\t\tconst { tours } = (await (await get('/tours.json')).json()) as { tours: Tour[] };\n\t\t\tconst tour = tours.find((t) => t.depots >= rank);\n\t\t\tif (tour?.ots !== 'bitcoin') return receipt;\n\t\t\tconst stamped = tour.horodatage !== null && tour.fichiers?.includes(`${tour.id}.racine.tsr`);\n\t\t\treturn {\n\t\t\t\t...receipt,\n\t\t\t\tcomplete: true,\n\t\t\t\tevidence: {\n\t\t\t\t\t...receipt.evidence,\n\t\t\t\t\ttour: tour.id,\n\t\t\t\t\troot: tour.racine,\n\t\t\t\t\t...(tour.bloc === null ? {} : { block: String(tour.bloc) }),\n\t\t\t\t\tots: await bytesOf(`/empreintes/${tour.id}.racine.ots`),\n\t\t\t\t\t...(stamped ? { tsr: await bytesOf(`/empreintes/${tour.id}.racine.tsr`) } : {})\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t};\n}\n"]}
1
+ {"version":3,"sources":["../src/proof/proof.ts","../src/proof/der.ts","../src/proof/reach.ts","../src/proof/rfc3161.ts","../src/proof/journal.ts"],"names":["refused","hex"],"mappings":"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","file":"proof.js","sourcesContent":["// Proof of existence for the user's own documents: the bytes never leave the\n// device, only their SHA-256 does. A notary dates that digest; the receipt it\n// hands back is kept next to the file, in the user's own storage, and anyone\n// holding the file and the receipt can check the two still belong together.\n\nimport { sha256Hex } from '../selfstore/digest';\nimport { isSelfstoreError, SelfstoreError } from '../selfstore/errors';\nimport { toBytes, type FileBytes } from '../simple/bytes';\nimport type { SimpleRecord } from '../simple/simple';\n\n/**\n * What a notary hands back for one digest. `evidence` is the notary's own\n * material (a timestamp token, an inclusion path, a download address), as\n * strings - base64 for bytes - so a receipt is plain JSON and survives every\n * destination a store can write to.\n */\nexport interface Receipt {\n\t/** The notary that issued it (`Notary.name`). */\n\treadonly notary: string;\n\t/** Lowercase hex SHA-256 of the document. */\n\treadonly hash: string;\n\t/** The label sent with the deposit, if any. */\n\treadonly label?: string;\n\t/** ISO 8601 instant the notary vouches for (its clock, not the device's). */\n\treadonly depositedAt: string;\n\t/** False while the notary still has something to add (an anchor, a seal). */\n\treadonly complete: boolean;\n\treadonly evidence: Readonly<Record<string, string>>;\n}\n\n/**\n * Anyone who dates a digest. `deposit` must never see more than the digest and\n * the label; `complete` is called later - the next day, the next launch - and\n * returns the same receipt with whatever the notary could not give at once.\n * A notary that is final on deposit returns its argument unchanged.\n */\nexport interface Notary {\n\treadonly name: string;\n\tdeposit(hash: string, label?: string): Promise<Receipt>;\n\tcomplete(receipt: Receipt): Promise<Receipt>;\n}\n\n/** A receipt as `prove()` keeps it: the notary's, plus what the device measured. */\nexport interface Proof extends Receipt {\n\treadonly kind: 'selfstore-proof';\n\treadonly version: 1;\n\treadonly algorithm: 'SHA-256';\n\t/** Size of the document in bytes. */\n\treadonly size: number;\n\t/** The store file the proof belongs to, when there is one. */\n\treadonly file?: string;\n}\n\n/** Where proofs are kept: the simple store fits as is. */\nexport interface ProofShelf {\n\tput(collection: string, record: SimpleRecord): Promise<void>;\n}\n\n/** The collection `prove()` and `completeProof()` write to. */\nexport const PROOFS = 'proofs';\n\nexport interface ProveOptions {\n\tnotary: Notary;\n\t/** Sent to the notary with the digest. A TYPE ('invoice', 'inventory'),\n\t * never a name or a piece of the content: it leaves the device. */\n\tlabel?: string;\n\t/** Keep the proof in this store, under `PROOFS`, keyed by `file` or by hash. */\n\tstore?: ProofShelf;\n\t/** Id of the store file the document is (as `putFile` returned it). */\n\tfile?: string;\n}\n\n/** The result of `verify()`. */\nexport interface ProofCheck {\n\t/** The bytes hash to the receipt's digest (and size, when it carries one). */\n\treadonly matches: boolean;\n\t/** What the bytes hash to now. */\n\treadonly hash: string;\n\t/** Whether the notary had finished with this receipt. */\n\treadonly complete: boolean;\n}\n\nconst HEX_DIGEST = /^[0-9a-f]{64}$/;\n\nfunction refused(message: string): SelfstoreError {\n\treturn new SelfstoreError('NOTARY_REFUSED', message);\n}\n\n/** A notary that answers for another digest, or for another notary, is not believed. */\nfunction checkAnswer(answer: Receipt, hash: string, notary: Notary): void {\n\tif (typeof answer?.hash !== 'string' || answer.hash.toLowerCase() !== hash) {\n\t\tthrow refused(`${notary.name} answered for another digest`);\n\t}\n\tif (answer.notary !== notary.name) {\n\t\tthrow refused(`receipt names ${String(answer.notary)}, not ${notary.name}`);\n\t}\n\tif (Number.isNaN(Date.parse(answer.depositedAt))) {\n\t\tthrow refused(`${notary.name} gave no readable deposit time`);\n\t}\n}\n\nasync function shelve(proof: Proof, store: ProofShelf | undefined): Promise<void> {\n\tif (store) await store.put(PROOFS, { ...proof, id: proof.file ?? proof.hash });\n}\n\n/**\n * Hash `blob` on the device, deposit the digest with `options.notary`, and\n * return the proof - kept in `options.store` too when one is given. Only the\n * digest and the label leave the device.\n *\n * Throws NOTARY_UNAVAILABLE when the notary cannot be reached (retry later)\n * and NOTARY_REFUSED when it refuses or answers for something else.\n */\nexport async function prove(blob: FileBytes, options: ProveOptions): Promise<Proof> {\n\tconst bytes = await toBytes(blob);\n\tconst hash = await sha256Hex(bytes);\n\tconst answer = await options.notary.deposit(hash, options.label);\n\tcheckAnswer(answer, hash, options.notary);\n\tconst proof: Proof = {\n\t\t...answer,\n\t\thash,\n\t\tkind: 'selfstore-proof',\n\t\tversion: 1,\n\t\talgorithm: 'SHA-256',\n\t\tsize: bytes.length,\n\t\t...(options.file === undefined ? {} : { file: options.file })\n\t};\n\tawait shelve(proof, options.store);\n\treturn proof;\n}\n\n/**\n * Ask the notary for what it could not give on deposit. A complete proof is\n * returned as is, without a call. The fields the device measured are kept;\n * the notary may only add to its own.\n */\nexport async function completeProof(\n\tproof: Proof,\n\toptions: { notary: Notary; store?: ProofShelf }\n): Promise<Proof> {\n\tif (proof.complete) return proof;\n\tconst answer = await options.notary.complete(proof);\n\tcheckAnswer(answer, proof.hash, options.notary);\n\tconst completed: Proof = {\n\t\t...answer,\n\t\thash: proof.hash,\n\t\tkind: proof.kind,\n\t\tversion: proof.version,\n\t\talgorithm: proof.algorithm,\n\t\tsize: proof.size,\n\t\t...(proof.file === undefined ? {} : { file: proof.file })\n\t};\n\tawait shelve(completed, options.store);\n\treturn completed;\n}\n\n/**\n * Hash `blob` again and compare it with the receipt. This proves the bytes are\n * the ones the receipt names; that the receipt itself is genuine is what the\n * notary's evidence is for, checked with the notary's own verifier.\n *\n * Throws BAD_FORMAT on a receipt that carries no SHA-256 digest at all - a\n * damaged receipt is not a document that changed.\n */\nexport async function verify(\n\tblob: FileBytes,\n\treceipt: Pick<Receipt, 'hash' | 'complete'> & { size?: number }\n): Promise<ProofCheck> {\n\tconst expected = typeof receipt?.hash === 'string' ? receipt.hash.toLowerCase() : '';\n\tif (!HEX_DIGEST.test(expected)) {\n\t\tthrow new SelfstoreError('BAD_FORMAT', 'the receipt carries no SHA-256 digest');\n\t}\n\tconst bytes = await toBytes(blob);\n\tconst hash = await sha256Hex(bytes);\n\tconst sizeAgrees = receipt.size === undefined || receipt.size === bytes.length;\n\treturn { matches: hash === expected && sizeAgrees, hash, complete: receipt.complete === true };\n}\n\n/** Where proofs are read back from: the simple store fits as is. */\nexport interface ProofLibrary extends ProofShelf {\n\tall(collection: string): readonly SimpleRecord[];\n}\n\nfunction isProof(record: SimpleRecord): boolean {\n\treturn (\n\t\trecord.kind === 'selfstore-proof' &&\n\t\ttypeof record.hash === 'string' &&\n\t\tHEX_DIGEST.test(record.hash) &&\n\t\ttypeof record.notary === 'string' &&\n\t\ttypeof record.complete === 'boolean'\n\t);\n}\n\n/**\n * Every proof kept in `store`, oldest deposit first. Records that are not\n * proofs - another app's, a damaged one - are skipped rather than trusted.\n */\nexport function proofsIn(store: Pick<ProofLibrary, 'all'>): Proof[] {\n\treturn store\n\t\t.all(PROOFS)\n\t\t.filter(isProof)\n\t\t.map((record) => {\n\t\t\tconst { id: _id, ...proof } = record;\n\t\t\treturn proof as unknown as Proof;\n\t\t})\n\t\t.sort((a, b) => Date.parse(a.depositedAt) - Date.parse(b.depositedAt));\n}\n\n/** What `completePending()` did, proof by proof. */\nexport interface PendingRound {\n\t/** Finished this round, and written back to the store. */\n\treadonly completed: readonly Proof[];\n\t/** Still waiting: the notary has nothing to add yet, could not be reached,\n\t * or is not among the notaries given. Ask again on a later launch. */\n\treadonly pending: readonly Proof[];\n\t/** The notary answered for something else; kept as it was, not overwritten. */\n\treadonly refused: readonly Proof[];\n}\n\nfunction isCode(error: unknown, code: string): boolean {\n\treturn isSelfstoreError(error) && error.code === code;\n}\n\n/**\n * Ask each notary to finish the incomplete proofs it issued - the call an app\n * makes at launch, since a notary that anchors its digests finishes a receipt\n * hours after the deposit. A notary that cannot be reached is not asked again\n * this round; a proof whose notary is not in `notaries` waits. Only a proof\n * that came back complete is written to the store.\n */\nexport async function completePending(options: {\n\tstore: ProofLibrary;\n\tnotaries: readonly Notary[];\n}): Promise<PendingRound> {\n\tconst byName = new Map(options.notaries.map((notary) => [notary.name, notary]));\n\tconst unreachable = new Set<string>();\n\tconst completed: Proof[] = [];\n\tconst pending: Proof[] = [];\n\tconst refused: Proof[] = [];\n\tfor (const proof of proofsIn(options.store)) {\n\t\tif (proof.complete) continue;\n\t\tconst notary = byName.get(proof.notary);\n\t\tif (notary === undefined || unreachable.has(notary.name)) {\n\t\t\tpending.push(proof);\n\t\t\tcontinue;\n\t\t}\n\t\ttry {\n\t\t\tconst answer = await completeProof(proof, { notary });\n\t\t\tif (answer.complete) {\n\t\t\t\tawait shelve(answer, options.store);\n\t\t\t\tcompleted.push(answer);\n\t\t\t} else {\n\t\t\t\tpending.push(proof);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tif (isCode(error, 'NOTARY_UNAVAILABLE')) {\n\t\t\t\tunreachable.add(notary.name);\n\t\t\t\tpending.push(proof);\n\t\t\t} else if (isCode(error, 'NOTARY_REFUSED')) {\n\t\t\t\trefused.push(proof);\n\t\t\t} else {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t}\n\treturn { completed, pending, refused };\n}\n","// The few DER shapes an RFC 3161 exchange needs, and no more. A timestamp\n// request is five fields and the reply is read along one path, so a general\n// ASN.1 library would be a fourth runtime dependency for two functions.\n\n/** One decoded TLV: `start` and `end` bound its CONTENT. */\nexport interface DerNode {\n\treadonly tag: number;\n\treadonly start: number;\n\treadonly end: number;\n}\n\nexport class DerError extends Error {}\n\nfunction encodeLength(length: number): Uint8Array {\n\tif (length < 0x80) return Uint8Array.of(length);\n\tconst bytes: number[] = [];\n\tfor (let rest = length; rest > 0; rest = Math.floor(rest / 256)) bytes.unshift(rest % 256);\n\treturn Uint8Array.of(0x80 | bytes.length, ...bytes);\n}\n\n/** Encode one TLV around already-encoded content. */\nexport function tlv(tag: number, ...parts: Uint8Array[]): Uint8Array {\n\tconst length = parts.reduce((sum, part) => sum + part.length, 0);\n\tconst head = encodeLength(length);\n\tconst out = new Uint8Array(1 + head.length + length);\n\tout[0] = tag;\n\tout.set(head, 1);\n\tlet at = 1 + head.length;\n\tfor (const part of parts) {\n\t\tout.set(part, at);\n\t\tat += part.length;\n\t}\n\treturn out;\n}\n\n/** Read the TLV that starts at `offset`. Indefinite lengths are BER, not DER: refused. */\nexport function readNode(bytes: Uint8Array, offset: number, limit = bytes.length): DerNode {\n\tif (offset + 2 > limit) throw new DerError('truncated header');\n\tconst tag = bytes[offset];\n\tconst first = bytes[offset + 1];\n\tlet start = offset + 2;\n\tlet length = first;\n\tif (first & 0x80) {\n\t\tconst count = first & 0x7f;\n\t\tif (count === 0 || count > 4) throw new DerError('unsupported length');\n\t\tif (start + count > limit) throw new DerError('truncated length');\n\t\tlength = 0;\n\t\tfor (let i = 0; i < count; i++) length = length * 256 + bytes[start + i];\n\t\tstart += count;\n\t}\n\tconst end = start + length;\n\tif (end > limit) throw new DerError('content runs past its parent');\n\treturn { tag, start, end };\n}\n\n/** The direct children of a constructed node. */\nexport function childrenOf(bytes: Uint8Array, node: DerNode): DerNode[] {\n\tconst out: DerNode[] = [];\n\tfor (let at = node.start; at < node.end;) {\n\t\tconst child = readNode(bytes, at, node.end);\n\t\tout.push(child);\n\t\tat = child.end;\n\t}\n\treturn out;\n}\n\n/** The node's content, or a refusal when its tag is not the one expected. */\nexport function expectTag(node: DerNode | undefined, tag: number): DerNode {\n\tif (node?.tag !== tag) throw new DerError(`expected tag 0x${tag.toString(16)}`);\n\treturn node;\n}\n\nexport function contentOf(bytes: Uint8Array, node: DerNode): Uint8Array {\n\treturn bytes.subarray(node.start, node.end);\n}\n","// One way to call a notary over HTTP, so every adapter maps the same failures\n// to the same codes: unreachable, overloaded or rate-limited is worth a retry,\n// any other refusal is not.\n\nimport { SelfstoreError } from '../selfstore/errors';\n\n/** Fetch `url`, throwing NOTARY_UNAVAILABLE or NOTARY_REFUSED instead of returning a failure. */\nexport async function reach(\n\tsend: typeof fetch,\n\turl: string,\n\tinit: RequestInit | undefined,\n\twho: string\n): Promise<Response> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await send(url, init);\n\t} catch (e) {\n\t\tthrow new SelfstoreError(\n\t\t\t'NOTARY_UNAVAILABLE',\n\t\t\t`${who} could not be reached: ${(e as Error).message}`\n\t\t);\n\t}\n\tif (!response.ok) {\n\t\tconst code =\n\t\t\tresponse.status >= 500 || response.status === 429 ? 'NOTARY_UNAVAILABLE' : 'NOTARY_REFUSED';\n\t\tthrow new SelfstoreError(code, `${who} answered ${response.status}`);\n\t}\n\treturn response;\n}\n","// A notary over any RFC 3161 time-stamping authority. The authority signs the\n// digest and the time in one token, so the receipt is complete on deposit: the\n// token (the `.tsr` file) is the evidence, and `openssl ts -verify` checks it\n// against the authority's certificate without this library.\n\nimport { fromBase64, toBase64 } from '../selfstore/base64';\nimport { hex } from '../selfstore/digest';\nimport { SelfstoreError } from '../selfstore/errors';\nimport { childrenOf, contentOf, DerError, expectTag, readNode, tlv, type DerNode } from './der';\nimport type { Notary, Receipt } from './proof';\nimport { reach } from './reach';\n\nconst SEQUENCE = 0x30;\nconst INTEGER = 0x02;\nconst OCTET_STRING = 0x04;\nconst OID = 0x06;\nconst GENERALIZED_TIME = 0x18;\nconst EXPLICIT_0 = 0xa0;\n\n/** id-sha256, 2.16.840.1.101.3.4.2.1, with its NULL parameters. */\nconst SHA256_ALGORITHM = tlv(\n\tSEQUENCE,\n\ttlv(OID, Uint8Array.of(0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01)),\n\tUint8Array.of(0x05, 0x00)\n);\n\nexport interface Rfc3161Options {\n\t/** The authority's endpoint. In a browser it must answer CORS, or sit\n\t * behind a relay of yours that does: most public authorities do not. */\n\turl: string;\n\t/** Receipt `notary` field. Defaults to 'rfc3161'. */\n\tname?: string;\n\t/** Injected for tests and relays; defaults to the global fetch. */\n\tfetch?: typeof fetch;\n}\n\n/** What a timestamp token says, read without checking its signature. */\nexport interface TimestampToken {\n\t/** Lowercase hex of the digest the authority stamped. */\n\treadonly hash: string;\n\t/** ISO 8601 instant the authority signed. */\n\treadonly time: string;\n\t/** The nonce echoed back, as lowercase hex, when there is one. */\n\treadonly nonce?: string;\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n\treturn Uint8Array.from(hex.match(/../g) ?? [], (pair) => Number.parseInt(pair, 16));\n}\n\n/** Leading zero bytes are DER's sign padding, not part of the value. */\nfunction integerHex(bytes: Uint8Array): string {\n\tlet at = 0;\n\twhile (at < bytes.length - 1 && bytes[at] === 0) at++;\n\treturn hex(bytes.subarray(at));\n}\n\n/** Eight random bytes, positive and minimal as a DER INTEGER. */\nfunction newNonce(): Uint8Array {\n\tconst nonce = crypto.getRandomValues(new Uint8Array(8));\n\tnonce[0] = (nonce[0] & 0x3f) | 0x40;\n\treturn nonce;\n}\n\n/** A TimeStampReq (RFC 3161 section 2.4.1) for a SHA-256 digest, asking for the certificate. */\nexport function timestampRequest(hash: string, nonce: Uint8Array): Uint8Array {\n\treturn tlv(\n\t\tSEQUENCE,\n\t\ttlv(INTEGER, Uint8Array.of(1)),\n\t\ttlv(SEQUENCE, SHA256_ALGORITHM, tlv(OCTET_STRING, hexToBytes(hash))),\n\t\ttlv(INTEGER, nonce),\n\t\tUint8Array.of(0x01, 0x01, 0xff)\n\t);\n}\n\n/** YYYYMMDDHHMMSS[.fff]Z to ISO 8601. */\nfunction generalizedTime(text: string): string {\n\tconst parts = /^(\\d{4})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\.\\d+)?Z$/.exec(text);\n\tif (parts === null) throw new DerError(`unreadable time ${text}`);\n\tconst [, y, mo, d, h, mi, s, fraction] = parts;\n\treturn new Date(`${y}-${mo}-${d}T${h}:${mi}:${s}${fraction ?? ''}Z`).toISOString();\n}\n\n/** TimeStampResp: the status, or the token when it was granted. */\nfunction tokenOf(bytes: Uint8Array): DerNode {\n\tconst [statusInfo, token] = childrenOf(bytes, expectTag(readNode(bytes, 0), SEQUENCE));\n\tconst status = expectTag(childrenOf(bytes, expectTag(statusInfo, SEQUENCE))[0], INTEGER);\n\tconst code = Number.parseInt(integerHex(contentOf(bytes, status)), 16);\n\t// 0 granted, 1 granted with modifications; anything else is a refusal.\n\tif (code > 1) {\n\t\tthrow new SelfstoreError(\n\t\t\t'NOTARY_REFUSED',\n\t\t\t`the authority refused the request (status ${code})`\n\t\t);\n\t}\n\treturn expectTag(token, SEQUENCE);\n}\n\n/** ContentInfo > SignedData > EncapsulatedContentInfo > TSTInfo. */\nfunction tstInfoOf(bytes: Uint8Array, token: DerNode): DerNode {\n\tconst signedData = expectTag(\n\t\tchildrenOf(bytes, expectTag(childrenOf(bytes, token)[1], EXPLICIT_0))[0],\n\t\tSEQUENCE\n\t);\n\tconst encapsulated = expectTag(childrenOf(bytes, signedData)[2], SEQUENCE);\n\tconst wrapper = expectTag(childrenOf(bytes, encapsulated)[1], EXPLICIT_0);\n\tconst octets = expectTag(childrenOf(bytes, wrapper)[0], OCTET_STRING);\n\treturn expectTag(readNode(bytes, octets.start, octets.end), SEQUENCE);\n}\n\n/**\n * Read the digest, the time and the nonce out of a TimeStampResp (the `.tsr`\n * file). It does NOT check the authority's signature - that is what\n * `openssl ts -verify` is for; it is what tells a token for another document\n * apart before anything is kept. Throws NOTARY_REFUSED on a refusal or on\n * anything that is not a timestamp reply.\n */\nexport function readTimestampReply(bytes: Uint8Array): TimestampToken {\n\ttry {\n\t\tconst tstInfo = tstInfoOf(bytes, tokenOf(bytes));\n\t\t// version, policy, messageImprint, serialNumber, genTime, then optional\n\t\t// accuracy, ordering and nonce - the nonce is the only INTEGER among them.\n\t\tconst [, , imprint, , time, ...rest] = childrenOf(bytes, tstInfo);\n\t\tconst digest = expectTag(childrenOf(bytes, expectTag(imprint, SEQUENCE))[1], OCTET_STRING);\n\t\tconst nonce = rest.find((node) => node.tag === INTEGER);\n\t\tconst genTime = new TextDecoder().decode(contentOf(bytes, expectTag(time, GENERALIZED_TIME)));\n\t\treturn {\n\t\t\thash: hex(contentOf(bytes, digest)),\n\t\t\ttime: generalizedTime(genTime),\n\t\t\t...(nonce ? { nonce: integerHex(contentOf(bytes, nonce)) } : {})\n\t\t};\n\t} catch (e) {\n\t\tif (e instanceof SelfstoreError) throw e;\n\t\tthrow new SelfstoreError('NOTARY_REFUSED', `not a timestamp reply: ${(e as Error).message}`);\n\t}\n}\n\nasync function post(options: Rfc3161Options, body: Uint8Array): Promise<Uint8Array> {\n\tconst response = await reach(\n\t\toptions.fetch ?? globalThis.fetch,\n\t\toptions.url,\n\t\t{\n\t\t\tmethod: 'POST',\n\t\t\theaders: { 'content-type': 'application/timestamp-query' },\n\t\t\tbody: body as BodyInit\n\t\t},\n\t\t'the authority'\n\t);\n\treturn new Uint8Array(await response.arrayBuffer());\n}\n\n/**\n * A notary over an RFC 3161 time-stamping authority. The receipt is complete\n * on deposit; its evidence is the reply, base64 under `tsr`, and the\n * authority's address under `tsa`. The label is never sent: RFC 3161 has no\n * field for it, so it stays on the receipt.\n */\nexport function rfc3161Notary(options: Rfc3161Options): Notary {\n\tconst name = options.name ?? 'rfc3161';\n\treturn {\n\t\tname,\n\t\tasync deposit(hash: string, label?: string): Promise<Receipt> {\n\t\t\tconst nonce = newNonce();\n\t\t\tconst reply = await post(options, timestampRequest(hash, nonce));\n\t\t\tconst token = readTimestampReply(reply);\n\t\t\tif (token.hash !== hash) {\n\t\t\t\tthrow new SelfstoreError('NOTARY_REFUSED', 'the authority stamped another digest');\n\t\t\t}\n\t\t\tif (token.nonce !== integerHex(nonce)) {\n\t\t\t\tthrow new SelfstoreError('NOTARY_REFUSED', 'the reply does not answer this request');\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tnotary: name,\n\t\t\t\thash,\n\t\t\t\t...(label === undefined ? {} : { label }),\n\t\t\t\tdepositedAt: token.time,\n\t\t\t\tcomplete: true,\n\t\t\t\tevidence: { tsr: toBase64(reply), tsa: options.url }\n\t\t\t};\n\t\t},\n\t\tcomplete(receipt: Receipt): Promise<Receipt> {\n\t\t\treturn Promise.resolve(receipt);\n\t\t}\n\t};\n}\n\n/** The `.tsr` bytes of an RFC 3161 receipt, to offer as a download. */\nexport function timestampReplyOf(receipt: Pick<Receipt, 'evidence'>): Uint8Array | undefined {\n\tconst tsr = receipt.evidence.tsr;\n\treturn tsr === undefined ? undefined : fromBase64(tsr);\n}\n","// A notary over a public digest journal: a service that takes a SHA-256 with\n// no account, writes it as one line of a public deposits file, and folds that\n// file into a Merkle tree it anchors in Bitcoin (OpenTimestamps) and often\n// stamps under RFC 3161 too. The deposit answers at once with the journal's\n// own receipt; the anchor comes hours later, so `complete` is what fetches it.\n//\n// The protocol, relative to the journal's base URL:\n// POST /api/depots {\"sha256\", \"libelle\"?} -> 201\n// {\"id\", \"jour\", \"place\", \"ligne\", \"recu\": {\"sha256\", \"depose\", ...}}\n// GET /empreintes/depots.jsonl one JSON line per deposit, in order, with \"recu\" = id\n// GET /tours.json {\"tours\": [{\"id\", \"racine\", \"depots\", \"ots\", \"bloc\",\n// \"horodatage\", \"fichiers\"}]}; a tour covers the\n// first `depots` lines of the deposits file\n// GET /empreintes/<tour>.racine.ots, /empreintes/<tour>.racine.tsr\n\nimport { toBase64 } from '../selfstore/base64';\nimport { SelfstoreError } from '../selfstore/errors';\nimport type { Notary, Receipt } from './proof';\nimport { reach } from './reach';\n\nexport interface JournalOptions {\n\t/** The journal's base URL, e.g. 'https://journal.example'. */\n\turl: string;\n\t/** Receipt `notary` field. Defaults to 'digest-journal'. */\n\tname?: string;\n\t/** Injected for tests and relays; defaults to the global fetch. */\n\tfetch?: typeof fetch;\n}\n\ninterface Deposit {\n\tid: string;\n\tligne: string;\n\trecu: { sha256: string; depose: string };\n}\n\ninterface Tour {\n\tid: string;\n\tracine: string;\n\tdepots: number;\n\tots: string;\n\tbloc: number | null;\n\thorodatage: string | null;\n\tfichiers?: string[];\n}\n\nconst HEX_ID = /^[0-9a-f]{64}$/;\n\nfunction refused(message: string): SelfstoreError {\n\treturn new SelfstoreError('NOTARY_REFUSED', message);\n}\n\nfunction readDeposit(body: unknown): Deposit {\n\tconst d = body as Partial<Deposit> | null;\n\tif (\n\t\ttypeof d?.id !== 'string' ||\n\t\t!HEX_ID.test(d.id) ||\n\t\ttypeof d.ligne !== 'string' ||\n\t\ttypeof d.recu?.sha256 !== 'string' ||\n\t\ttypeof d.recu.depose !== 'string'\n\t) {\n\t\tthrow refused('the journal answered without a receipt');\n\t}\n\treturn d as Deposit;\n}\n\nfunction parseLine(text: string): { recu?: string; sha256?: string } {\n\ttry {\n\t\treturn JSON.parse(text) as { recu?: string; sha256?: string };\n\t} catch {\n\t\tthrow refused('the journal published an unreadable deposits file');\n\t}\n}\n\n/** 1-based rank of the deposit line whose receipt id is `id`, or 0. */\nfunction rankOf(deposits: string, id: string, hash: string): number {\n\tconst lines = deposits.split('\\n').filter(Boolean);\n\tfor (const [index, text] of lines.entries()) {\n\t\tconst line = parseLine(text);\n\t\tif (line.recu !== id) continue;\n\t\tif (line.sha256 !== hash) throw refused('the journal lists this receipt for another digest');\n\t\treturn index + 1;\n\t}\n\treturn 0;\n}\n\n/**\n * A notary over a public digest journal (the protocol is at the top of this\n * file). The label is sent as the journal's `libelle`: keep it a TYPE.\n *\n * On deposit the receipt is incomplete; its evidence holds the journal's\n * address, the receipt `id`, the public `line` and the journal's own `receipt`\n * as JSON - the file its verifier reads. `complete` finds the first tour that\n * covers the line and, once that tour is anchored in Bitcoin, completes the\n * receipt with `tour`, `root`, `block`, the `.ots` proof and, when the journal\n * stamped that root, the `.tsr` token (both base64). Checking the Merkle path\n * from the line to the root is the journal's verifier's job.\n */\nexport function digestJournalNotary(options: JournalOptions): Notary {\n\tconst name = options.name ?? 'digest-journal';\n\tconst base = options.url.replace(/\\/+$/, '');\n\tconst send = options.fetch ?? globalThis.fetch;\n\tconst get = (path: string): Promise<Response> =>\n\t\treach(send, `${base}${path}`, undefined, 'the journal');\n\tconst bytesOf = async (path: string): Promise<string> =>\n\t\ttoBase64(new Uint8Array(await (await get(path)).arrayBuffer()));\n\n\treturn {\n\t\tname,\n\t\tasync deposit(hash: string, label?: string): Promise<Receipt> {\n\t\t\tconst response = await reach(\n\t\t\t\tsend,\n\t\t\t\t`${base}/api/depots`,\n\t\t\t\t{\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { 'content-type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify({ sha256: hash, ...(label === undefined ? {} : { libelle: label }) })\n\t\t\t\t},\n\t\t\t\t'the journal'\n\t\t\t);\n\t\t\tconst deposit = readDeposit(await response.json().catch(() => null));\n\t\t\treturn {\n\t\t\t\tnotary: name,\n\t\t\t\thash: deposit.recu.sha256,\n\t\t\t\t...(label === undefined ? {} : { label }),\n\t\t\t\tdepositedAt: deposit.recu.depose,\n\t\t\t\tcomplete: false,\n\t\t\t\tevidence: {\n\t\t\t\t\tjournal: base,\n\t\t\t\t\tid: deposit.id,\n\t\t\t\t\tline: deposit.ligne,\n\t\t\t\t\treceipt: JSON.stringify(deposit.recu)\n\t\t\t\t}\n\t\t\t};\n\t\t},\n\t\tasync complete(receipt: Receipt): Promise<Receipt> {\n\t\t\tconst id = receipt.evidence.id;\n\t\t\tif (id === undefined) throw refused('the receipt carries no journal id');\n\t\t\tconst rank = rankOf(await (await get('/empreintes/depots.jsonl')).text(), id, receipt.hash);\n\t\t\tif (rank === 0) return receipt;\n\t\t\tconst { tours } = (await (await get('/tours.json')).json()) as { tours: Tour[] };\n\t\t\tconst tour = tours.find((t) => t.depots >= rank);\n\t\t\tif (tour?.ots !== 'bitcoin') return receipt;\n\t\t\tconst stamped = tour.horodatage !== null && tour.fichiers?.includes(`${tour.id}.racine.tsr`);\n\t\t\treturn {\n\t\t\t\t...receipt,\n\t\t\t\tcomplete: true,\n\t\t\t\tevidence: {\n\t\t\t\t\t...receipt.evidence,\n\t\t\t\t\ttour: tour.id,\n\t\t\t\t\troot: tour.racine,\n\t\t\t\t\t...(tour.bloc === null ? {} : { block: String(tour.bloc) }),\n\t\t\t\t\tots: await bytesOf(`/empreintes/${tour.id}.racine.ots`),\n\t\t\t\t\t...(stamped ? { tsr: await bytesOf(`/empreintes/${tour.id}.racine.tsr`) } : {})\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t};\n}\n"]}
package/llms.txt CHANGED
@@ -138,6 +138,13 @@ collaborative editing (use a CRDT library), or Node-side storage.
138
138
  /empreintes/depots.jsonl and /tours.json and, once the first tour covering
139
139
  the line has ots 'bitcoin', adds tour, root, block, ots (base64) and tsr
140
140
  (base64) when that root was stamped.
141
+ completePending({ store, notaries }) -> { completed, pending, refused }:
142
+ the launch-time round, asks each notary (matched by name) for its
143
+ incomplete proofs, writes back only the ones that came back complete;
144
+ NOTARY_UNAVAILABLE skips that notary for the round (pending),
145
+ NOTARY_REFUSED goes to refused (receipt untouched), anything else throws.
146
+ proofsIn(store) -> Proof[] reads PROOFS back, oldest deposit first,
147
+ skipping records that are not proofs. ProofLibrary = ProofShelf + all().
141
148
  - 'selfstore/sync' - the bare merge engine: createNode, stamp, merge,
142
149
  createMeta, changes, detectConflicts.
143
150
  - 'selfstore/kdf-worker' - the Argon2id worker asset (bundlers pick it up
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "selfstore",
3
- "version": "1.8.25",
3
+ "version": "1.8.26",
4
4
  "description": "Local-first storage for browser apps: an IndexedDB working copy, portable encrypted ZIP backups (AES-256-GCM over Argon2id), durable homes (disk, Google Drive, WebDAV) and a serverless multi-device merge.",
5
5
  "type": "module",
6
6
  "license": "MIT",