@real-life/trust-protocol 0.3.2 → 0.4.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.
Files changed (264) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +187 -78
  3. package/dist/ack.d.ts +84 -0
  4. package/dist/ack.d.ts.map +1 -0
  5. package/dist/ack.js +114 -0
  6. package/dist/ack.js.map +1 -0
  7. package/dist/adapters/vti-mediator.d.ts +154 -0
  8. package/dist/adapters/vti-mediator.d.ts.map +1 -0
  9. package/dist/adapters/vti-mediator.js +621 -0
  10. package/dist/adapters/vti-mediator.js.map +1 -0
  11. package/dist/anchor.d.ts +226 -0
  12. package/dist/anchor.d.ts.map +1 -0
  13. package/dist/anchor.js +347 -0
  14. package/dist/anchor.js.map +1 -0
  15. package/dist/{holder.d.ts → carrier-holder.d.ts} +1 -1
  16. package/dist/carrier-holder.d.ts.map +1 -0
  17. package/dist/{holder.js → carrier-holder.js} +5 -4
  18. package/dist/carrier-holder.js.map +1 -0
  19. package/dist/carrier-identity.js +4 -4
  20. package/dist/carrier-identity.js.map +1 -1
  21. package/dist/carrier.d.ts.map +1 -1
  22. package/dist/carrier.js +4 -3
  23. package/dist/carrier.js.map +1 -1
  24. package/dist/ceremony.d.ts +3 -2
  25. package/dist/ceremony.d.ts.map +1 -1
  26. package/dist/ceremony.js +16 -26
  27. package/dist/ceremony.js.map +1 -1
  28. package/dist/codec.d.ts +46 -0
  29. package/dist/codec.d.ts.map +1 -0
  30. package/dist/codec.js +410 -0
  31. package/dist/codec.js.map +1 -0
  32. package/dist/continuity.d.ts +67 -0
  33. package/dist/continuity.d.ts.map +1 -0
  34. package/dist/continuity.js +110 -0
  35. package/dist/continuity.js.map +1 -0
  36. package/dist/core.d.ts +4 -0
  37. package/dist/core.d.ts.map +1 -1
  38. package/dist/core.js.map +1 -1
  39. package/dist/crypto.d.ts +23 -7
  40. package/dist/crypto.d.ts.map +1 -1
  41. package/dist/crypto.js +54 -20
  42. package/dist/crypto.js.map +1 -1
  43. package/dist/delivery.d.ts +31 -3
  44. package/dist/delivery.d.ts.map +1 -1
  45. package/dist/delivery.js +75 -28
  46. package/dist/delivery.js.map +1 -1
  47. package/dist/encounter/ceremony.d.ts +37 -0
  48. package/dist/encounter/ceremony.d.ts.map +1 -0
  49. package/dist/encounter/ceremony.js +662 -0
  50. package/dist/encounter/ceremony.js.map +1 -0
  51. package/dist/encounter.d.ts +91 -1
  52. package/dist/encounter.d.ts.map +1 -1
  53. package/dist/encounter.js +191 -12
  54. package/dist/encounter.js.map +1 -1
  55. package/dist/engine.d.ts +291 -0
  56. package/dist/engine.d.ts.map +1 -0
  57. package/dist/engine.js +1395 -0
  58. package/dist/engine.js.map +1 -0
  59. package/dist/entropy.d.ts +28 -0
  60. package/dist/entropy.d.ts.map +1 -0
  61. package/dist/entropy.js +96 -0
  62. package/dist/entropy.js.map +1 -0
  63. package/dist/events.d.ts +177 -0
  64. package/dist/events.d.ts.map +1 -0
  65. package/dist/events.js +2 -0
  66. package/dist/events.js.map +1 -0
  67. package/dist/fixtures.d.ts +30 -0
  68. package/dist/fixtures.d.ts.map +1 -0
  69. package/dist/fixtures.js +155 -0
  70. package/dist/fixtures.js.map +1 -0
  71. package/dist/forge.d.ts +47 -0
  72. package/dist/forge.d.ts.map +1 -0
  73. package/dist/forge.js +20 -0
  74. package/dist/forge.js.map +1 -0
  75. package/dist/group-star.d.ts +251 -0
  76. package/dist/group-star.d.ts.map +1 -0
  77. package/dist/group-star.js +348 -0
  78. package/dist/group-star.js.map +1 -0
  79. package/dist/holder-api.d.ts +137 -0
  80. package/dist/holder-api.d.ts.map +1 -0
  81. package/dist/holder-api.js +132 -0
  82. package/dist/holder-api.js.map +1 -0
  83. package/dist/holder-state-v1.d.ts +801 -0
  84. package/dist/holder-state-v1.d.ts.map +1 -0
  85. package/dist/holder-state-v1.js +2 -0
  86. package/dist/holder-state-v1.js.map +1 -0
  87. package/dist/identity-state.d.ts +43 -0
  88. package/dist/identity-state.d.ts.map +1 -0
  89. package/dist/identity-state.js +345 -0
  90. package/dist/identity-state.js.map +1 -0
  91. package/dist/identity.d.ts +24 -2
  92. package/dist/identity.d.ts.map +1 -1
  93. package/dist/identity.js +70 -11
  94. package/dist/identity.js.map +1 -1
  95. package/dist/index.d.ts +26 -1
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +77 -29
  98. package/dist/index.js.map +1 -1
  99. package/dist/internal.d.ts +36 -0
  100. package/dist/internal.d.ts.map +1 -0
  101. package/dist/internal.js +22 -0
  102. package/dist/internal.js.map +1 -0
  103. package/dist/introduction/holder.d.ts +18 -0
  104. package/dist/introduction/holder.d.ts.map +1 -0
  105. package/dist/introduction/holder.js +757 -0
  106. package/dist/introduction/holder.js.map +1 -0
  107. package/dist/introduction.d.ts +101 -0
  108. package/dist/introduction.d.ts.map +1 -0
  109. package/dist/introduction.js +132 -0
  110. package/dist/introduction.js.map +1 -0
  111. package/dist/invariants.d.ts +43 -0
  112. package/dist/invariants.d.ts.map +1 -0
  113. package/dist/invariants.js +883 -0
  114. package/dist/invariants.js.map +1 -0
  115. package/dist/keys-seed.d.ts +7 -0
  116. package/dist/keys-seed.d.ts.map +1 -0
  117. package/dist/keys-seed.js +133 -0
  118. package/dist/keys-seed.js.map +1 -0
  119. package/dist/log-memory.d.ts +12 -0
  120. package/dist/log-memory.d.ts.map +1 -0
  121. package/dist/log-memory.js +101 -0
  122. package/dist/log-memory.js.map +1 -0
  123. package/dist/membership/holder.d.ts +35 -0
  124. package/dist/membership/holder.d.ts.map +1 -0
  125. package/dist/membership/holder.js +852 -0
  126. package/dist/membership/holder.js.map +1 -0
  127. package/dist/membership.d.ts +92 -0
  128. package/dist/membership.d.ts.map +1 -0
  129. package/dist/membership.js +157 -0
  130. package/dist/membership.js.map +1 -0
  131. package/dist/ports.d.ts +237 -0
  132. package/dist/ports.d.ts.map +1 -0
  133. package/dist/ports.js +2 -0
  134. package/dist/ports.js.map +1 -0
  135. package/dist/profile.d.ts +56 -0
  136. package/dist/profile.d.ts.map +1 -0
  137. package/dist/profile.js +193 -0
  138. package/dist/profile.js.map +1 -0
  139. package/dist/prompts.d.ts +14 -0
  140. package/dist/prompts.d.ts.map +1 -0
  141. package/dist/prompts.js +69 -0
  142. package/dist/prompts.js.map +1 -0
  143. package/dist/receive.d.ts +133 -0
  144. package/dist/receive.d.ts.map +1 -0
  145. package/dist/receive.js +298 -0
  146. package/dist/receive.js.map +1 -0
  147. package/dist/results.d.ts +158 -0
  148. package/dist/results.d.ts.map +1 -0
  149. package/dist/results.js +21 -0
  150. package/dist/results.js.map +1 -0
  151. package/dist/schemas.d.ts.map +1 -1
  152. package/dist/schemas.js +42 -38
  153. package/dist/schemas.js.map +1 -1
  154. package/dist/sha256.d.ts +3 -0
  155. package/dist/sha256.d.ts.map +1 -0
  156. package/dist/sha256.js +69 -0
  157. package/dist/sha256.js.map +1 -0
  158. package/dist/star.d.ts +31 -0
  159. package/dist/star.d.ts.map +1 -0
  160. package/dist/star.js +56 -0
  161. package/dist/star.js.map +1 -0
  162. package/dist/state-builder.d.ts +42 -0
  163. package/dist/state-builder.d.ts.map +1 -0
  164. package/dist/state-builder.js +100 -0
  165. package/dist/state-builder.js.map +1 -0
  166. package/dist/state.d.ts +853 -0
  167. package/dist/state.d.ts.map +1 -0
  168. package/dist/state.js +36 -0
  169. package/dist/state.js.map +1 -0
  170. package/dist/store-contract.d.ts +11 -0
  171. package/dist/store-contract.d.ts.map +1 -0
  172. package/dist/store-contract.js +189 -0
  173. package/dist/store-contract.js.map +1 -0
  174. package/dist/store-memory.d.ts +8 -0
  175. package/dist/store-memory.d.ts.map +1 -0
  176. package/dist/store-memory.js +17 -0
  177. package/dist/store-memory.js.map +1 -0
  178. package/dist/testing.d.ts +22 -0
  179. package/dist/testing.d.ts.map +1 -0
  180. package/dist/testing.js +21 -0
  181. package/dist/testing.js.map +1 -0
  182. package/dist/trust/continuity.d.ts +71 -0
  183. package/dist/trust/continuity.d.ts.map +1 -0
  184. package/dist/trust/continuity.js +1216 -0
  185. package/dist/trust/continuity.js.map +1 -0
  186. package/dist/trust/duties.d.ts +67 -0
  187. package/dist/trust/duties.d.ts.map +1 -0
  188. package/dist/trust/duties.js +179 -0
  189. package/dist/trust/duties.js.map +1 -0
  190. package/dist/trust/entries.d.ts +139 -0
  191. package/dist/trust/entries.d.ts.map +1 -0
  192. package/dist/trust/entries.js +694 -0
  193. package/dist/trust/entries.js.map +1 -0
  194. package/dist/trust/group-star.d.ts +43 -0
  195. package/dist/trust/group-star.d.ts.map +1 -0
  196. package/dist/trust/group-star.js +188 -0
  197. package/dist/trust/group-star.js.map +1 -0
  198. package/dist/trust/mapping.d.ts +56 -0
  199. package/dist/trust/mapping.d.ts.map +1 -0
  200. package/dist/trust/mapping.js +584 -0
  201. package/dist/trust/mapping.js.map +1 -0
  202. package/dist/trust/profile.d.ts +25 -0
  203. package/dist/trust/profile.d.ts.map +1 -0
  204. package/dist/trust/profile.js +249 -0
  205. package/dist/trust/profile.js.map +1 -0
  206. package/dist/trust/star.d.ts +208 -0
  207. package/dist/trust/star.d.ts.map +1 -0
  208. package/dist/trust/star.js +588 -0
  209. package/dist/trust/star.js.map +1 -0
  210. package/dist/trust/state.d.ts +83 -0
  211. package/dist/trust/state.d.ts.map +1 -0
  212. package/dist/trust/state.js +154 -0
  213. package/dist/trust/state.js.map +1 -0
  214. package/dist/util-freeze.d.ts +3 -0
  215. package/dist/util-freeze.d.ts.map +1 -0
  216. package/dist/util-freeze.js +11 -0
  217. package/dist/util-freeze.js.map +1 -0
  218. package/dist/views.d.ts +147 -0
  219. package/dist/views.d.ts.map +1 -0
  220. package/dist/views.js +268 -0
  221. package/dist/views.js.map +1 -0
  222. package/dist/visibility.d.ts +5 -5
  223. package/dist/visibility.d.ts.map +1 -1
  224. package/dist/visibility.js +22 -28
  225. package/dist/visibility.js.map +1 -1
  226. package/dist/wire.d.ts +178 -84
  227. package/dist/wire.d.ts.map +1 -1
  228. package/dist/wire.js.map +1 -1
  229. package/package.json +56 -10
  230. package/schemas/holder-state-v1.schema.json +3880 -0
  231. package/dist/holder.d.ts.map +0 -1
  232. package/dist/holder.js.map +0 -1
  233. package/dist/probe/acks.d.ts +0 -27
  234. package/dist/probe/acks.d.ts.map +0 -1
  235. package/dist/probe/acks.js +0 -152
  236. package/dist/probe/acks.js.map +0 -1
  237. package/dist/probe/continuity.d.ts +0 -20
  238. package/dist/probe/continuity.d.ts.map +0 -1
  239. package/dist/probe/continuity.js +0 -805
  240. package/dist/probe/continuity.js.map +0 -1
  241. package/dist/probe/deps.d.ts +0 -61
  242. package/dist/probe/deps.d.ts.map +0 -1
  243. package/dist/probe/deps.js +0 -54
  244. package/dist/probe/deps.js.map +0 -1
  245. package/dist/probe/encounter.d.ts +0 -26
  246. package/dist/probe/encounter.d.ts.map +0 -1
  247. package/dist/probe/encounter.js +0 -938
  248. package/dist/probe/encounter.js.map +0 -1
  249. package/dist/probe/introduce.d.ts +0 -62
  250. package/dist/probe/introduce.d.ts.map +0 -1
  251. package/dist/probe/introduce.js +0 -306
  252. package/dist/probe/introduce.js.map +0 -1
  253. package/dist/probe/membership.d.ts +0 -96
  254. package/dist/probe/membership.d.ts.map +0 -1
  255. package/dist/probe/membership.js +0 -467
  256. package/dist/probe/membership.js.map +0 -1
  257. package/dist/probe/trust.d.ts +0 -156
  258. package/dist/probe/trust.d.ts.map +0 -1
  259. package/dist/probe/trust.js +0 -1233
  260. package/dist/probe/trust.js.map +0 -1
  261. package/dist/probe.d.ts +0 -7
  262. package/dist/probe.d.ts.map +0 -1
  263. package/dist/probe.js +0 -42
  264. package/dist/probe.js.map +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,32 @@
1
+ # Changelog
2
+
3
+ ## 0.4.0
4
+
5
+ - `createHolder`: one person's device state behind typed human acts
6
+ (`{when, actId}`, journaled), `receive`, `report`, `resume` and `tick`,
7
+ with the views `contacts`, `contact`, `groups`, `profile`, `prompts`,
8
+ `deliveryStatus` and `subscribe`. Results are a closed union with rule
9
+ references; events are a closed union, issued after the commit.
10
+ - Ports: `Store` (compare-and-swap), `KeyProvider` (`seedKeys` by default),
11
+ `Entropy` by closed purpose, `Log`, `Access`, `RetryPolicy`,
12
+ `DeliveryReport`, pure-JSON `Outbound`. `storeContract` is the suite a
13
+ Store adapter passes.
14
+ - Modules on the holder: identity (the register, device names, the
15
+ community anchor's rotation over the Log port), trust (the trust act and
16
+ the classes, the anchor mapping, the star and the group star, merge,
17
+ split and departure, continuity, `profile@1`, the duties), encounter
18
+ (`encounter-scan@0.25` with both paths), membership (founding, prelude,
19
+ invite, admission, welcome, `vouch@2`) and introduction (Trust §8, three
20
+ roles).
21
+ - Delivery: stages 1–9 once per envelope through a task register, the
22
+ completed-effect cache with stored acks, the outbox with the status
23
+ trias, retry by backoff, give-up at the horizon.
24
+ - State codec 1 with golden fixtures (`test/fixtures/`) and a
25
+ non-normative JSON Schema (`schemas/holder-state-v1.schema.json`) with
26
+ the generated type `HolderStateV1`; a newer codec is `codec-newer`, a
27
+ codec without a validator `codec-unknown`.
28
+ - Entry points: the root, `./testing`, `./internal`,
29
+ `./adapters/vti-mediator`. `visibility` and `ceremony` carry pure wire
30
+ builders and verifiers only. There is no `./probe` entry point.
31
+ - Invariant predicates over every commit (`invariants: true`) and a
32
+ generator over random sequences of acts on two and three holders.
package/README.md CHANGED
@@ -5,14 +5,18 @@ encounters between people: two humans meet, recognize each other, and record
5
5
  that recognition as verifiable credentials. Cryptography proves freshness and
6
6
  authorship; only a human can witness a human.
7
7
 
8
- This package is the **executable form of the specification** — it lives beside
9
- the schemas and the test vectors it must reproduce byte-for-byte. Published by
10
- the [Real Life Organisation](https://real-life.org) under CC BY 4.0.
11
-
12
- - Specification, schemas and vectors: <https://rltp.real-life.org>
13
- - Zero dependencies. WebCrypto only (Ed25519, X25519, HKDF, AES-256-GCM).
14
- - Works in browsers, Node ≥ 20, Deno, Bun. No DOM, no storage, no network:
15
- the library computes and verifies, the host decides where bytes live.
8
+ This package is the reference library of the specification. It lives beside
9
+ the schemas and the test vectors it reproduces byte for byte. Published by the
10
+ [Real Life Organisation](https://real-life.org) under CC BY 4.0.
11
+
12
+ - Specification, schemas and vectors: <https://trust-protocol.real-life.org>
13
+ - Zero runtime dependencies. WebCrypto only (Ed25519, X25519, HKDF,
14
+ AES-256-GCM, SHA-256), with one scoped exception: a synchronous SHA-256
15
+ (`sha256.ts`) digests local reconciliation views inside synchronous state
16
+ transitions (the star and the group star). It never touches wire bytes,
17
+ keys or MACs.
18
+ - No DOM, no storage, no network: the holder computes and verifies, the app
19
+ stores its state and carries its envelopes.
16
20
 
17
21
  ## Install
18
22
 
@@ -20,92 +24,197 @@ the [Real Life Organisation](https://real-life.org) under CC BY 4.0.
20
24
  npm i @real-life/trust-protocol
21
25
  ```
22
26
 
23
- ## The modules mirror the specification
24
-
25
- Each module answers to one document, so a reader of the spec knows where to
26
- look in the code:
27
+ ## The holder
27
28
 
28
- | Module | Specification | What it carries |
29
- |---|---|---|
30
- | `core` | across all layers | The canonical form: JCS serialization, multibase encodings, digest equality over *decoded* bytes, whole-second timestamps, calendar validity — and the JSON-Schema subset validator the conformance runner uses. |
31
- | `crypto` | — (primitives) | Hashes, HKDF, key pairs, did:key and multikey renderings, ECDH, and the eddsa-jcs-2022 Data Integrity proof every layer signs with. Answers *how*, never *what*. |
32
- | `identity` | Identity Layer | One root seed, every context derived — community anchor, pair anchor per relationship, member anchor per group. Contexts never link without their holder's deliberate act. |
33
- | `encounter` | Encounter Layer | The ceremony under fresh-always pair anchors: challenges, contact cards, the enactment binding both sides compute independently, encounter credentials. |
34
- | `delivery` | Delivery Contract | The sealed envelope, and the receive chain in its declared order — size bound *before* decryption, envelope form, decryption, parse and digest over the canonical form, completed-effect cache. |
35
- | `carrier-identity` | Identity §7a | The carrier-relationship principal: one Ed25519 control key per (relationship × carrier), derived so that no computable relation to the relationship's other principals or its `rkid` exists — which is why registration must *prove* the binding instead of computing it. |
36
- | `carrier` | Delivery §4.4 + §5a | The carrier side of the port as one transport-agnostic state machine: the five guarantees, the proof rule, the total binding lifecycle (unbound → live → closing → released), the verdict sets in their normative evaluation orders, wind-up and binding tombstones. The clock is injected. |
37
- | `holder` | Delivery §5a + Identity §9.3 | The holder's half: proof construction for all four purposes, the closed verdict set, and the two-exchange recovery flow for a lost carrier entry. |
38
- | `visibility` | Network Visibility 0.29 on Delivery 0.79 | The trust act (`anchor-mapping@2` + `grade-declaration@1`), the blinded star (`star@1`, 5.2a chunks), the admission layer and the 5.4 reconciliation automaton, §6a continuity (probe + mapping — chains instead of duplicates), and the deniable `delivery-ack/0.1`. Namespaced: `visibility.trust`, `.continuity`, `.acks`. Graduated from `/probe` on 04.09.2026 after a 39-round conformance loop. |
39
- | `ceremony` | Encounter 0.29 (`encounter-scan@0.25`) on Delivery 0.79 | The ceremony's transmission: `encounter-bundle/0.1`, `encounter-credential-delivery/0.1` (counter-step in the bundle's thread), the signed `delivery-ack/0.1`; the own-challenge state model with its aging latch, the enactment record as the serialization point, the eight-step acceptance, connected and offline path with free switching; one commit discipline (prepare → verify → commit) under the §6.2 lock set; resumable issuance. Graduated from `/probe` on 05.09.2026 after a 25-round conformance loop. |
40
-
41
- Everything is re-exported from the package root; the table is a map, not an
42
- import instruction.
29
+ `createHolder` is one person's device: its state behind the human acts, the
30
+ delivery channel's three calls and the views an app reads.
43
31
 
44
32
  ```ts
45
- import { labeledContext, signCard, seal, sameDigest } from '@real-life/trust-protocol'
33
+ import { createHolder, seedKeys } from '@real-life/trust-protocol'
34
+ import { InMemoryStore } from '@real-life/trust-protocol/testing'
35
+
36
+ const holder = await createHolder({ store: new InMemoryStore(), keys: seedKeys(rootSeed) })
37
+
38
+ // a human act: its time and an actId the app mints when the human acts
39
+ const shown = await holder.show({ when: Date.now(), actId: crypto.randomUUID() })
40
+ // shown.events carries the card to display; shown.outbound the envelopes to send
41
+
42
+ // the channel: one receive per arriving envelope, the adapter's reports,
43
+ // and resume on a timer (retries and due duties)
44
+ const got = await holder.receive(envelope, Date.now())
45
+ await holder.report({ digest, status: 'accepted' }, Date.now())
46
+ const { outbound, nextDueAt } = await holder.resume(Date.now())
47
+
48
+ // the views, read after each commit (subscribe announces them)
49
+ holder.subscribe(async () => render(await holder.contacts(), await holder.prompts()))
46
50
  ```
47
51
 
48
- ## Two entry points
52
+ **The timer.** Every act, receive and resume may name `nextDueAt`: the
53
+ earliest time something is due, delivery work (retries, duties, acks)
54
+ and expiry work (a prompt's expiry, an act's timeout or lifetime, an aging
55
+ challenge, the retention of the effect cache, the journal, the outbox and
56
+ the address keys) alike. The app keeps one timer at the earliest
57
+ `nextDueAt` it was given; when it fires, it calls `tick(now)` and then
58
+ `resume(now)`, sends the outbound and sets the timer anew.
59
+
60
+ Every act returns `{ok: true, outbound, events} | {ok: false, code, ref?}`;
61
+ `code` is a closed union, `ref` names the rule where one applies. Every
62
+ `outbound` item is pure JSON (`digest`, `envelope`, `type`, `threadId`,
63
+ `recipient`, `attempt`); the app delivers it to the device holding
64
+ `envelope.rkid` and reports what its transport saw by `digest`.
65
+
66
+ The human acts: `show`, `scan`, `captureSentCard`, `counter` (the encounter),
67
+ `decide` (a prompt: confirm an encounter, accept an invitation, take up or
68
+ consent to an introduction), `trust`, `untrust`, `pauseTrust`, `setClass`,
69
+ `setGroupClass`, `merge`, `depart`, `setProfile`, `foundGroup`, `invite`,
70
+ `vouchFor`, `introduce`, `rotateAnchor`, `addDevice`, `revokeDevice`. Not
71
+ human: `receive`, `report`, `resume`, `tick`, `recover`. Reads: `contacts`,
72
+ `contact`, `groups`, `profile`, `prompts`, `deliveryStatus`, `deviceKey`,
73
+ `snapshot`, `subscribe`.
74
+
75
+ ## What the root exports
76
+
77
+ - `createHolder` and its types (`Holder`, `HolderOptions`, `HumanActOptions`,
78
+ `HolderSnapshot`).
79
+ - The ports: `Store`, `KeyProvider`, `Entropy`, `Log`, `Access`,
80
+ `RetryPolicy`, `DeliveryReport`, `Outbound`; `seedKeys(seed)` is the
81
+ default KeyProvider, `randomEntropy` the default Entropy.
82
+ - The views: `ContactView`, `GroupView`, `PromptView`, `ProfileView`,
83
+ `DeliveryStatusView` and their parts.
84
+ - The results and events: `ActResult`, `ReceiveResult`, `ResumeResult`,
85
+ `InfraError`, `CryptoUnavailableError`, `HolderEvent`.
86
+ - The state as an opaque type (`HolderState`), `decodeState`, `CODEC`,
87
+ `StateCodecError`, and `storeContract`, the suite a Store adapter passes.
88
+ - The pure wire builders and verifiers of every layer (`core`, `crypto`,
89
+ `identity`, `profile`, `encounter`, `membership`, `introduction`,
90
+ `delivery`, `carrier-identity`, `carrier`, `carrier-holder`), and two
91
+ namespaces of pure forms: `visibility` (anchor, star, group star,
92
+ continuity, acks) and `ceremony` (the encounter's, the group's and the
93
+ introduction's forms).
94
+
95
+ Other entry points:
96
+
97
+ | Entry point | What it holds |
98
+ |---|---|
99
+ | `./testing` | `InMemoryStore`, `memoryLog`, `scriptedEntropy`, `storeContract`, `emptyState`, `stateBuilder`, `freezeDeep`, `sampleState`, `openPromptModule`, the invariant checks, `forgeMapping` (deniability, Trust 6.1), `SCHEMAS` and the generated `wire` types. For tests, vectors and adapter authors; not for production holders. |
100
+ | `./internal` | The structural `HolderState`, `encodeState` and the codec chain, the engine's module types, the pure view functions, the modules' factories and pure reads. For the library's own modules and tests; not an app contract. |
101
+ | `./adapters/vti-mediator` | The VTI mediator as a carrier adapter: its submission states as `DeliveryReport`, collection and ack handling. |
102
+
103
+ ## The modules and the rules they implement
104
+
105
+ Bare four-digit numbers are RLTP-TRU rules; the other layers name theirs.
106
+
107
+ | Module | Specification | Rules |
108
+ |---|---|---|
109
+ | `core` | all layers | the canonical form: JCS (RFC 8785), multibase, digest equality over decoded bytes, whole-second timestamps, calendar validity, the JSON-Schema subset validator |
110
+ | `crypto` | primitives | HKDF, key pairs, did:key and multikey, X25519, the `eddsa-jcs-2022` Data Integrity proof |
111
+ | `identity` | Identity 0.54 §6.1, §6.2, §5.4, §5.5 | context derivation under the closed label registry: `group/<digest>` and its generations, `pair/…`, `persona/…`, `device/<n>/<c>`; the name pipeline pinned to Unicode 15.0 |
112
+ | `identity-state` | Identity §2, §5.4, §5.5 | the label register with monotone sets, device names and revocation marks, the community anchor's rotation as a procedure over the Log port (register before log before duties) |
113
+ | `encounter`, `encounter/ceremony` | Encounter 0.31 §4–§7 | `encounter-scan@0.25`: challenges and their aging latch (5.3), the enactment record (5.5), the 5.6 acceptance in its order, both paths of 5.8, `encounter-bundle/0.1` and `encounter-credential-delivery/0.1` with signed acks, the confirm prompt |
114
+ | `trust/mapping` | Trust Section 2, 2a, §6.1–§6.5 | the trust act and the class settings (2720–2850), `anchor-mapping@4` issuance (6300/6310, 6420) and verification (6.3, conditions 4a, 4b, 8), the entry commit and the admission bound |
115
+ | `trust/star`, `trust/group-star` | Trust §5.1–§5.4, §5.2b | `star@2` and `group-star@1` reconciled per recipient (5.4: salt scope, baseline automaton, completion by the ack), reception with assemblies and lower bounds |
116
+ | `trust/entries` | Trust Section 2, §6a.1 | merge (net and manual, 6640), split (2080–2115), departure (2260), the head rule and the binding rule (6605) |
117
+ | `trust/continuity` | Trust §6a | `continuity-probe@1` and `continuity-mapping@1`, chaining a re-encounter into the existing relationship (6a.4) |
118
+ | `trust/profile`, `profile` | Trust §6b | `profile@1`: the one revision counter (6450), delivery on the class gates (6455), reception and the image bounds (6475) |
119
+ | `trust/duties` | Trust 2850, 6370, 6455, 6595, 6596 | the duties a commit leaves: class on, re-issue after a rotation, the lag rule |
120
+ | `membership`, `membership/holder` | Membership Tasks 0.19 on Access 0.57 | founding (Access 3.4.1), the prelude, invite, accept, admission and welcome, evidence relay and `vouch@2` (Access 5.3), an in-library Access over the holder's groups |
121
+ | `introduction`, `introduction/holder` | Trust §8 on Delivery §4.4 | the Introduction Act in its three roles: request, forward, reply, the mediator's one ack at its declared delay, the vouchers; the requester's timeout at the act's expiry (8340; A20, spec follow-up 18: no ack timeout of 8690) |
122
+ | `receive`, `engine` | Delivery 0.81 §5, §6.1, §6.2 | the sealed envelope, stages 1–9 once per envelope, the task register by full task URI, the completed-effect cache with stored acks, the outbox with the status trias, retry by backoff, give-up at the horizon, the act journal |
123
+ | `carrier-identity`, `carrier`, `carrier-holder` | Identity §7a, Delivery §4.4, §5a, Identity §9.3 | the carrier-relationship principal, the carrier port's state machine, proofs, verdicts and recovery |
124
+ | `views`, `prompts` | — | the read model: contacts by entry (2120, 2140), groups, prompts, the delivery status |
125
+
126
+ ## Runtime
127
+
128
+ | Runtime | Status |
129
+ |---|---|
130
+ | Node 22 | tested in CI (the library job) |
131
+ | Node 24 | tested locally |
132
+ | Node 20 | the declared minimum (`engines`); Ed25519 and X25519 are in its WebCrypto |
133
+ | Chromium 145 | tested by the workbench's end-to-end run (`apps/gesamtsimulator/tests/local-perspective.mjs`) |
134
+ | Chromium ≥ 137, Firefox ≥ 129, Safari ≥ 17 | expected: they carry Ed25519 and X25519 in WebCrypto; not tested here |
135
+
136
+ The WebCrypto handle is resolved when a holder is built, not at import: an
137
+ app may pass its own (`createHolder({crypto})`, `seedKeys(seed, crypto)`),
138
+ and a runtime without Ed25519 or X25519 is reported then as
139
+ `CryptoUnavailableError`.
140
+
141
+ ## State, codec and storage
142
+
143
+ A holder's state is pure JSON under a codec version. The Store port takes
144
+ the state as opaque JSON and gives it back; the library decodes it
145
+ (`decodeState`), the adapter never migrates.
146
+
147
+ - **Codec 1** is the format of 0.4.0, the first stable one. Its golden
148
+ fixtures (`test/fixtures/state-v1*.json`, deterministic runs of
149
+ `test/fixtures/gen-state-v1.mjs`, reproduced in `npm test`) cover a
150
+ settled holder, one with work open (prompts, a joining invite, duties, a
151
+ rotation at step 2) and one with a tombstone, a revoked device and
152
+ carrier entries; each decodes and re-encodes byte for byte. A later codec brings its validator, a pure migration from the
153
+ one before, and its own fixture with a migration test.
154
+ - A newer codec is refused as `codec-newer`, a codec without a validator
155
+ as `codec-unknown`; nothing is read best-effort.
156
+ - `schemas/holder-state-v1.schema.json` describes codec 1 as a
157
+ non-normative JSON Schema (published with the package). The type
158
+ `HolderStateV1` (`./internal`) is generated from it
159
+ (`scripts/build-holder-state-types.mjs`); the build holds it against the
160
+ library's `HolderState`, and the tests hold the schema against the
161
+ codec's validator on the fixtures and on mutations of them. Two rules
162
+ only the codec states: a record's id equals its key, and an envelope's
163
+ `rkid` equals its recipient's `keyAgreement`.
164
+
165
+ **Writing a Store adapter.** Implement `read(): Promise<{state, version}>`
166
+ (a deep, independent copy; `null` state and version for an empty store) and
167
+ `commit(version, next): Promise<{ok: true, version} | {ok: false, reason:
168
+ 'conflict'}>` as compare-and-swap: atomic, storing a copy, a version token
169
+ never reused in the store's lifetime. One writer per holder; the adapter
170
+ guarantees it. Then run the contract against it:
49
171
 
50
172
  ```ts
51
- import { …, visibility } from '@real-life/trust-protocol' // wire-normative core
52
- const { trust, continuity, acks } = visibility // Network Visibility, graduated
53
- import { introduce, membership }
54
- from '@real-life/trust-protocol/probe' // draft — will change
173
+ import { test } from 'node:test'
174
+ import assert from 'node:assert/strict'
175
+ import { storeContract } from '@real-life/trust-protocol'
176
+
177
+ storeContract(() => new MyStore(), { test, assert }) // a fresh, empty store per call
55
178
  ```
56
179
 
57
- The **`/probe` subpath is deliberate.** Its modules exercise the converged
58
- *semantics* of their layers, but their transport shapes carry `@probe` and
59
- are **not wire-normative**: mediated introductions over a rendezvous drop
60
- and group membership (founding, VIC invite, admission, vouching). The
61
- trust act, the blinded star, §6a continuity and the deniable ack **left
62
- this subpath on 04.09.2026** — they are wire-normative now and live under
63
- `visibility` at the package root; the Encounter ceremony's transmission
64
- **left on 05.09.2026** and lives under `ceremony`. Experiment with the rest; do not treat its wire forms
65
- as an interoperability target. A subpath import is a decision someone has
66
- to make — nothing behind it can be reached by accident.
67
-
68
- **The redelivery contract of probe receivers:** a receiver's inbound
69
- effect is complete the moment it returns `handled: true` without `error` —
70
- that delivery is cached and answers `duplicate-known` forever after.
71
- Outbound work a receiver triggers (star refreshes, continuity mappings)
72
- is built per recipient and never discards partial results; what failed is
73
- named in `outboundError` / `outbound.failures`, and **redelivering the
74
- inbound document is not the retry path** — re-invoking the producing call
75
- (e.g. `trust.starRefreshAll`) is, and it is the host application's job.
180
+ It checks snapshot isolation, CAS, atomicity, a lost result, the codec
181
+ boundary and reopening. Durability across process loss is the persistent
182
+ adapter's own test.
183
+
184
+ ## Invariants and the generator
185
+
186
+ `invariants.ts` states predicates over a commit (`state → state'`): the
187
+ register and revocation marks only grow, tombstones never shrink, a salt is
188
+ issued at most once per tuple, the effect cache holds only completed
189
+ digests, `accepted` arises only from a report, a duty leaves only by its
190
+ specified fulfilment. `createHolder(ports, {invariants: true})` checks them
191
+ on every commit. `test/invariants-generator.test.mjs` runs random sequences
192
+ of acts, receives, reports and ticks over two and three holders and checks
193
+ every predicate after every step.
76
194
 
77
195
  ## Versions
78
196
 
79
- This package uses its own semver; the specification uses castings. Each
80
- release states, per module, whose **wire forms** it implements — and what
81
- it deliberately does not:
82
-
83
- | Module (0.3.x) | Casting | Implemented | Not in this package |
84
- |---|---|---|---|
85
- | `identity` | Identity 0.50 | context derivation under the closed label registry (§6.1, unchanged since 0.13) with the ordered persona pipeline (§6.2, Unicode 15.0 pinned) | recovery derivation (§5.3), service identities (§7), the label register and its state |
86
- | `carrier-identity` | Identity 0.51 (§7a) | the carrier-relationship principal derivation, identifier validation | the register's generation state (held by the holder's store) |
87
- | `carrier` + `holder` | Delivery 0.79 (§4.4, §5a) + Identity 0.51 (§9.3) | the port state machine, proofs for all four purposes, verdicts, recovery | transport adapters and their policies (a carrier's budgets enter through hooks) |
88
- | `encounter` | Encounter 0.29 — wire `0.25` (`rltp-card/0.25`, `encounter-scan@0.25`) | challenges, both card profiles, the enactment binding, encounter credentials (the primitives) | — (the ceremony machine lives in `ceremony`) |
89
- | `ceremony` | Encounter 0.29 §5.3–5.8 + Delivery 0.79 (§3, §4.1–4.3, §6) | `encounter-bundle/0.1`, `encounter-credential-delivery/0.1`, signed `delivery-ack/0.1`; own-challenge state model (aging latch, whole seconds), enactment record + record-key lock set, 5.6 acceptance in order, both 5.8 paths, thread freshness incl. re-issue, resumable issuance, chain-head carry | storage, transport adapters, the host's UI correlation (duties listed in the handoff) |
90
- | `delivery` | Delivery 0.64 | the sealed envelope (§5) and the generic receive stages 1–4 | the type-specific stages, residual bookkeeping, transport adapters |
91
- | `core`, `crypto` | cross-layer | canonical form, digest equality, timestamps, validator; primitives + `eddsa-jcs-2022` proof | — |
92
- | `visibility` | Network Visibility 0.29 + Delivery 0.79 (§3, §4.2, §4.4) | the trust act (`anchor-mapping@2`, `grade-declaration@1`), `star@1` with 5.2a chunk assembly, the admission layer (Section 2), the 5.4 reconciliation automaton with completion via deniable acks, §6a continuity (`continuity-probe@1`, `continuity-mapping@1`, chaining), the registered task documents and `delivery-ack/0.1` with the 4.2 class rule; full §6.2 stage order on every receive path, peek→build→commit issuance on every producer | storage, transport adapters and their policies (the Encounter ceremony's flows live in `ceremony`) |
93
- | `/probe` | Network Visibility (introductions) · Membership Tasks + Access | mediated introductions, group membership — semantics of the running castings, transport shapes `@probe` | any wire-form stability whatsoever |
94
-
95
- Persona-name validation is **pinned to Unicode 15.0** as Identity §6.2
96
- requires: the repertoire ships with the package (`unicode15.ts`, ~5 kB of
97
- ranges) and no platform Unicode data is consulted — the same name derives
98
- the same anchor on every runtime, whatever ICU it carries.
197
+ This package uses its own semver; the specification uses castings.
198
+
199
+ | Area | Casting | Not in this package |
200
+ |---|---|---|
201
+ | identity | Identity 0.54 | recovery derivation (§5.3), service identities (§7) |
202
+ | encounter | Encounter 0.31, cards 0.25, credentials 0.26 | — |
203
+ | trust | Trust Layer 0.31 | — |
204
+ | membership | Membership Tasks 0.19, Access 0.57 | the Access envelope (Membership 3.3: `access-operation/0.1` with `member.add` and the sealed welcome; this library sends its own `x-membership-welcome/0.1`, and the prelude as `x-membership-prelude-*`); roster replication: a holder knows the members it admitted and the roster its welcome carried |
205
+ | introduction | Trust 0.31 §8 | how a requester learns the mediator's designator (8.3): this library's mediator sends `x-introduction-offer/0.1` |
206
+ | delivery | Delivery 0.81 | transport adapters beyond `./adapters/vti-mediator` |
207
+ | devices | Identity §5.5 | multi-device production: one writer per holder |
99
208
 
100
209
  ## The contract: the vectors
101
210
 
102
- A version that does not reproduce `vectors/` byte-for-byte is not a valid
103
- version — the conformance runner checks every cryptographic claim, and every
104
- negative case must fail at its declared stage. If you implement RLTP in
105
- another language, those same vectors are how you check yourself; you do not
106
- need this package to do it.
211
+ A version that does not reproduce `vectors/` byte for byte is not a valid
212
+ version. The conformance runner checks every cryptographic claim, and every
213
+ negative case fails at its declared stage. An implementation in another
214
+ language checks itself against the same vectors; it does not need this
215
+ package.
107
216
 
108
217
  ## License
109
218
 
110
- [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) — specification and
219
+ [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), specification and
111
220
  reference implementation alike.
package/dist/ack.d.ts ADDED
@@ -0,0 +1,84 @@
1
+ /** The registered type of the acknowledgement (Delivery §4.2). */
2
+ export declare const ACK_TYPE = "https://real-life.org/trust-tasks/delivery-ack/0.1";
3
+ /** The ack document without its proof: the MAC preimage (4.2). */
4
+ export interface AckDocument {
5
+ id: string;
6
+ type: typeof ACK_TYPE;
7
+ issuer: string;
8
+ recipient: string;
9
+ threadId: string;
10
+ issuedAt: string;
11
+ payload: {
12
+ ref: string;
13
+ meaning: 'received';
14
+ };
15
+ }
16
+ /** The HKDF info of the channel ack key, issuer first (4.2). */
17
+ export declare const ackInfo: (issuerPairAnchor: string, recipientPairAnchor: string) => string;
18
+ /** The channel ack key from the tuple's X25519 shared secret (4.2). */
19
+ export declare function ackKeyOf(shared: Uint8Array, issuerPairAnchor: string, recipientPairAnchor: string, subtle?: SubtleCrypto): Promise<Uint8Array>;
20
+ /** "u" + base64url HMAC-SHA-256 over the JCS of `doc` under `key`. */
21
+ export declare function ackMac(key: Uint8Array, doc: unknown, subtle?: SubtleCrypto): Promise<string>;
22
+ /** The proof-less ack of `ref` on the acked document's thread (4.2):
23
+ * issuer = the acked document's recipient, recipient = its issuer. */
24
+ export declare const ackDocument: (f: {
25
+ id: string;
26
+ issuer: string;
27
+ recipient: string;
28
+ threadId: string;
29
+ issuedAt: string;
30
+ ref: string;
31
+ }) => AckDocument;
32
+ /** The deniable ack: `doc` with its MAC-form proof under the channel ack key. */
33
+ export declare function macAck(doc: AckDocument, key: Uint8Array, subtle?: SubtleCrypto): Promise<AckDocument & {
34
+ proof: {
35
+ mac: string;
36
+ };
37
+ }>;
38
+ /** Where a received ack's form fails, by the stage of Delivery §6.2 that names it. */
39
+ export type AckFormFailure = {
40
+ stage: 5 | 7;
41
+ reason: 'malformed';
42
+ } | {
43
+ stage: 8;
44
+ reason: 'validation-failed';
45
+ };
46
+ /**
47
+ * The context-free checks of a received MAC-form ack, in stage order
48
+ * (Delivery §6.2): 5 document profile (schema, closed form, calendar), 7
49
+ * payload schema, 8 the class rule's proof form (exactly `{mac}`), no
50
+ * enactment, `meaning`. What needs the arrival context stays the
51
+ * caller's: recipient = own anchor (5), issuer = the tuple's counterpart,
52
+ * `ref` = a document sent on that thread, and the MAC itself (8).
53
+ */
54
+ export declare function macAckForm(doc: unknown): {
55
+ ok: true;
56
+ ref: string;
57
+ threadId: string;
58
+ } | ({
59
+ ok: false;
60
+ } & AckFormFailure);
61
+ /**
62
+ * The context-free checks of a received SIGNED ack (the class rule's
63
+ * form for a signature-class payload, 4.2/4.4), in stage order: 5 the
64
+ * document profile, 7 the payload schema, 8 the proof form (an
65
+ * eddsa-jcs-2022 DataIntegrityProof, no `mac`, calendar-valid), no
66
+ * enactment, `meaning`. The signature itself (under `issuer`) and the
67
+ * consistency against what the holder sent stay the caller's.
68
+ */
69
+ export declare function signedAckForm(doc: unknown): {
70
+ ok: true;
71
+ ref: string;
72
+ threadId: string;
73
+ } | ({
74
+ ok: false;
75
+ } & AckFormFailure);
76
+ /** The proof form a received ack carries: the MAC object, a signature, or neither. */
77
+ export declare const ackProofForm: (doc: unknown) => "mac" | "signed" | null;
78
+ /** Does the ack's MAC verify under `key`? (4.2; over JCS of the document without proof) */
79
+ export declare function macAckValid(doc: {
80
+ proof?: {
81
+ mac?: unknown;
82
+ };
83
+ } & Record<string, unknown>, key: Uint8Array, subtle?: SubtleCrypto): Promise<boolean>;
84
+ //# sourceMappingURL=ack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ack.d.ts","sourceRoot":"","sources":["../src/ack.ts"],"names":[],"mappings":"AAwBA,kEAAkE;AAClE,eAAO,MAAM,QAAQ,uDAAuD,CAAA;AAE5E,kEAAkE;AAClE,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,OAAO,QAAQ,CAAA;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,UAAU,CAAA;KAAE,CAAA;CAC9C;AAED,gEAAgE;AAChE,eAAO,MAAM,OAAO,GAAI,kBAAkB,MAAM,EAAE,qBAAqB,MAAM,KAAG,MACJ,CAAA;AAE5E,uEAAuE;AACvE,wBAAsB,QAAQ,CAAE,MAAM,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAGrJ;AAED,sEAAsE;AACtE,wBAAsB,MAAM,CAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAInG;AAED;uEACuE;AACvE,eAAO,MAAM,WAAW,GAAI,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,KAAG,WAGlI,CAAA;AAEF,iFAAiF;AACjF,wBAAsB,MAAM,CAAE,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,GAAG;IAAE,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC,CAEzI;AAMD,sFAAsF;AACtF,MAAM,MAAM,cAAc,GAAG;IAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAA;CAAE,CAAA;AAE9G;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAE,GAAG,EAAE,OAAO,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,CAAC;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,GAAG,cAAc,CAAC,CAUxH;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAE,GAAG,EAAE,OAAO,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,CAAC;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,GAAG,cAAc,CAAC,CAY3H;AAED,sFAAsF;AACtF,eAAO,MAAM,YAAY,GAAI,KAAK,OAAO,KAAG,KAAK,GAAG,QAAQ,GAAG,IAI9D,CAAA;AAED,2FAA2F;AAC3F,wBAAsB,WAAW,CAAE,GAAG,EAAE;IAAE,KAAK,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CAGzJ"}
package/dist/ack.js ADDED
@@ -0,0 +1,114 @@
1
+ // ack — the delivery acknowledgement's pure parts (Delivery §4.2, the
2
+ // acknowledgement class rule of §4.4): the type, the deniable MAC form's
3
+ // channel-keyed derivation, the MAC, and the context-free form checks of
4
+ // a received ack. Used by the holder's receive core (engine.ts);
5
+ // byte-compatible with vectors/delivery-ack.json
6
+ // (lib/test/vectors4.test.mjs).
7
+ //
8
+ // k = HKDF-SHA-256(ikm = X25519(pairX_issuer, pairX_recipient),
9
+ // salt = empty, info = "rltp/v1/delivery/mac/ack/"
10
+ // || issuerPairAnchor || "/" || recipientPairAnchor,
11
+ // 32 bytes; an all-zero shared secret is rejected)
12
+ // proof = { mac: "u" + base64url(HMAC-SHA-256(k, JCS(document without proof))) }
13
+ //
14
+ // Deniable by construction: either party of the tuple derives k, so for a
15
+ // designated-verifier payload the ack is evidence to its sender alone,
16
+ // never an attestation (4.2). The key agreement itself is the caller's:
17
+ // the holder asks its KeyProvider.
18
+ import { jcs, b64uOf, calOK, makeValidator } from './core.js';
19
+ import { hkdf, platformSubtle } from './crypto.js';
20
+ import { SCHEMAS } from './schemas.js';
21
+ const te = new TextEncoder();
22
+ /** The registered type of the acknowledgement (Delivery §4.2). */
23
+ export const ACK_TYPE = 'https://real-life.org/trust-tasks/delivery-ack/0.1';
24
+ /** The HKDF info of the channel ack key, issuer first (4.2). */
25
+ export const ackInfo = (issuerPairAnchor, recipientPairAnchor) => 'rltp/v1/delivery/mac/ack/' + issuerPairAnchor + '/' + recipientPairAnchor;
26
+ /** The channel ack key from the tuple's X25519 shared secret (4.2). */
27
+ export async function ackKeyOf(shared, issuerPairAnchor, recipientPairAnchor, subtle) {
28
+ if (shared.every((b) => b === 0))
29
+ throw new Error('all-zero X25519 output');
30
+ return hkdf(shared, ackInfo(issuerPairAnchor, recipientPairAnchor), 32, subtle);
31
+ }
32
+ /** "u" + base64url HMAC-SHA-256 over the JCS of `doc` under `key`. */
33
+ export async function ackMac(key, doc, subtle) {
34
+ const S = subtle ?? platformSubtle();
35
+ const k = await S.importKey('raw', key, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
36
+ return 'u' + b64uOf(new Uint8Array(await S.sign('HMAC', k, te.encode(jcs(doc)))));
37
+ }
38
+ /** The proof-less ack of `ref` on the acked document's thread (4.2):
39
+ * issuer = the acked document's recipient, recipient = its issuer. */
40
+ export const ackDocument = (f) => ({
41
+ id: f.id, type: ACK_TYPE, issuer: f.issuer, recipient: f.recipient, threadId: f.threadId, issuedAt: f.issuedAt,
42
+ payload: { ref: f.ref, meaning: 'received' },
43
+ });
44
+ /** The deniable ack: `doc` with its MAC-form proof under the channel ack key. */
45
+ export async function macAck(doc, key, subtle) {
46
+ return { ...doc, proof: { mac: await ackMac(key, doc, subtle) } };
47
+ }
48
+ const V = makeValidator(SCHEMAS);
49
+ const schemaOk = (file, data) => V.validate(data, SCHEMAS[file], SCHEMAS[file]).length === 0;
50
+ const ACK_FIELDS = new Set(['id', 'type', 'issuer', 'recipient', 'threadId', 'issuedAt', 'payload', 'proof', 'ceremony']);
51
+ /**
52
+ * The context-free checks of a received MAC-form ack, in stage order
53
+ * (Delivery §6.2): 5 document profile (schema, closed form, calendar), 7
54
+ * payload schema, 8 the class rule's proof form (exactly `{mac}`), no
55
+ * enactment, `meaning`. What needs the arrival context stays the
56
+ * caller's: recipient = own anchor (5), issuer = the tuple's counterpart,
57
+ * `ref` = a document sent on that thread, and the MAC itself (8).
58
+ */
59
+ export function macAckForm(doc) {
60
+ const d = doc;
61
+ if (!schemaOk('rltp-delivery-document.schema.json', d) || d?.type !== ACK_TYPE)
62
+ return { ok: false, stage: 5, reason: 'malformed' };
63
+ if (Object.keys(d).some((k) => !ACK_FIELDS.has(k)) || !calOK(d.issuedAt))
64
+ return { ok: false, stage: 5, reason: 'malformed' };
65
+ if (!schemaOk('payload-delivery-ack.schema.json', d.payload))
66
+ return { ok: false, stage: 7, reason: 'malformed' };
67
+ const p = d.proof;
68
+ if (p === null || typeof p !== 'object' || Object.keys(p).length !== 1 || typeof p.mac !== 'string')
69
+ return { ok: false, stage: 8, reason: 'validation-failed' };
70
+ if (d.ceremony?.enactment !== undefined)
71
+ return { ok: false, stage: 8, reason: 'validation-failed' };
72
+ if (d.payload.meaning !== 'received')
73
+ return { ok: false, stage: 8, reason: 'validation-failed' };
74
+ return { ok: true, ref: d.payload.ref, threadId: d.threadId };
75
+ }
76
+ /**
77
+ * The context-free checks of a received SIGNED ack (the class rule's
78
+ * form for a signature-class payload, 4.2/4.4), in stage order: 5 the
79
+ * document profile, 7 the payload schema, 8 the proof form (an
80
+ * eddsa-jcs-2022 DataIntegrityProof, no `mac`, calendar-valid), no
81
+ * enactment, `meaning`. The signature itself (under `issuer`) and the
82
+ * consistency against what the holder sent stay the caller's.
83
+ */
84
+ export function signedAckForm(doc) {
85
+ const d = doc;
86
+ if (!schemaOk('rltp-delivery-document.schema.json', d) || d?.type !== ACK_TYPE)
87
+ return { ok: false, stage: 5, reason: 'malformed' };
88
+ if (Object.keys(d).some((k) => !ACK_FIELDS.has(k)) || !calOK(d.issuedAt))
89
+ return { ok: false, stage: 5, reason: 'malformed' };
90
+ if (!schemaOk('payload-delivery-ack.schema.json', d.payload))
91
+ return { ok: false, stage: 7, reason: 'malformed' };
92
+ const p = d.proof;
93
+ if (p === null || typeof p !== 'object' || p.mac !== undefined || p.type !== 'DataIntegrityProof' || p.cryptosuite !== 'eddsa-jcs-2022' || !calOK(p.created)) {
94
+ return { ok: false, stage: 8, reason: 'validation-failed' };
95
+ }
96
+ if (d.ceremony?.enactment !== undefined)
97
+ return { ok: false, stage: 8, reason: 'validation-failed' };
98
+ if (d.payload.meaning !== 'received')
99
+ return { ok: false, stage: 8, reason: 'validation-failed' };
100
+ return { ok: true, ref: d.payload.ref, threadId: d.threadId };
101
+ }
102
+ /** The proof form a received ack carries: the MAC object, a signature, or neither. */
103
+ export const ackProofForm = (doc) => {
104
+ const p = doc?.proof;
105
+ if (p === null || typeof p !== 'object')
106
+ return null;
107
+ return typeof p.mac === 'string' ? 'mac' : typeof p.proofValue === 'string' ? 'signed' : null;
108
+ };
109
+ /** Does the ack's MAC verify under `key`? (4.2; over JCS of the document without proof) */
110
+ export async function macAckValid(doc, key, subtle) {
111
+ const { proof, ...rest } = doc;
112
+ return typeof proof?.mac === 'string' && (await ackMac(key, rest, subtle)) === proof.mac;
113
+ }
114
+ //# sourceMappingURL=ack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ack.js","sourceRoot":"","sources":["../src/ack.ts"],"names":[],"mappings":"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","sourcesContent":["// ack — the delivery acknowledgement's pure parts (Delivery §4.2, the\n// acknowledgement class rule of §4.4): the type, the deniable MAC form's\n// channel-keyed derivation, the MAC, and the context-free form checks of\n// a received ack. Used by the holder's receive core (engine.ts);\n// byte-compatible with vectors/delivery-ack.json\n// (lib/test/vectors4.test.mjs).\n//\n// k = HKDF-SHA-256(ikm = X25519(pairX_issuer, pairX_recipient),\n// salt = empty, info = \"rltp/v1/delivery/mac/ack/\"\n// || issuerPairAnchor || \"/\" || recipientPairAnchor,\n// 32 bytes; an all-zero shared secret is rejected)\n// proof = { mac: \"u\" + base64url(HMAC-SHA-256(k, JCS(document without proof))) }\n//\n// Deniable by construction: either party of the tuple derives k, so for a\n// designated-verifier payload the ack is evidence to its sender alone,\n// never an attestation (4.2). The key agreement itself is the caller's:\n// the holder asks its KeyProvider.\nimport { jcs, b64uOf, calOK, makeValidator } from './core.js'\nimport type { Json } from './core.js'\nimport { hkdf, platformSubtle } from './crypto.js'\nimport { SCHEMAS } from './schemas.js'\n\nconst te = new TextEncoder()\n\n/** The registered type of the acknowledgement (Delivery §4.2). */\nexport const ACK_TYPE = 'https://real-life.org/trust-tasks/delivery-ack/0.1'\n\n/** The ack document without its proof: the MAC preimage (4.2). */\nexport interface AckDocument {\n id: string\n type: typeof ACK_TYPE\n issuer: string\n recipient: string\n threadId: string\n issuedAt: string\n payload: { ref: string, meaning: 'received' }\n}\n\n/** The HKDF info of the channel ack key, issuer first (4.2). */\nexport const ackInfo = (issuerPairAnchor: string, recipientPairAnchor: string): string =>\n 'rltp/v1/delivery/mac/ack/' + issuerPairAnchor + '/' + recipientPairAnchor\n\n/** The channel ack key from the tuple's X25519 shared secret (4.2). */\nexport async function ackKeyOf (shared: Uint8Array, issuerPairAnchor: string, recipientPairAnchor: string, subtle?: SubtleCrypto): Promise<Uint8Array> {\n if (shared.every((b) => b === 0)) throw new Error('all-zero X25519 output')\n return hkdf(shared, ackInfo(issuerPairAnchor, recipientPairAnchor), 32, subtle)\n}\n\n/** \"u\" + base64url HMAC-SHA-256 over the JCS of `doc` under `key`. */\nexport async function ackMac (key: Uint8Array, doc: unknown, subtle?: SubtleCrypto): Promise<string> {\n const S = subtle ?? platformSubtle()\n const k = await S.importKey('raw', key, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'])\n return 'u' + b64uOf(new Uint8Array(await S.sign('HMAC', k, te.encode(jcs(doc)))))\n}\n\n/** The proof-less ack of `ref` on the acked document's thread (4.2):\n * issuer = the acked document's recipient, recipient = its issuer. */\nexport const ackDocument = (f: { id: string, issuer: string, recipient: string, threadId: string, issuedAt: string, ref: string }): AckDocument => ({\n id: f.id, type: ACK_TYPE, issuer: f.issuer, recipient: f.recipient, threadId: f.threadId, issuedAt: f.issuedAt,\n payload: { ref: f.ref, meaning: 'received' },\n})\n\n/** The deniable ack: `doc` with its MAC-form proof under the channel ack key. */\nexport async function macAck (doc: AckDocument, key: Uint8Array, subtle?: SubtleCrypto): Promise<AckDocument & { proof: { mac: string } }> {\n return { ...doc, proof: { mac: await ackMac(key, doc, subtle) } }\n}\n\nconst V = makeValidator(SCHEMAS)\nconst schemaOk = (file: string, data: unknown): boolean => V.validate(data, SCHEMAS[file], SCHEMAS[file]).length === 0\nconst ACK_FIELDS = new Set(['id', 'type', 'issuer', 'recipient', 'threadId', 'issuedAt', 'payload', 'proof', 'ceremony'])\n\n/** Where a received ack's form fails, by the stage of Delivery §6.2 that names it. */\nexport type AckFormFailure = { stage: 5 | 7, reason: 'malformed' } | { stage: 8, reason: 'validation-failed' }\n\n/**\n * The context-free checks of a received MAC-form ack, in stage order\n * (Delivery §6.2): 5 document profile (schema, closed form, calendar), 7\n * payload schema, 8 the class rule's proof form (exactly `{mac}`), no\n * enactment, `meaning`. What needs the arrival context stays the\n * caller's: recipient = own anchor (5), issuer = the tuple's counterpart,\n * `ref` = a document sent on that thread, and the MAC itself (8).\n */\nexport function macAckForm (doc: unknown): { ok: true, ref: string, threadId: string } | ({ ok: false } & AckFormFailure) {\n const d = doc as Record<string, any>\n if (!schemaOk('rltp-delivery-document.schema.json', d) || d?.type !== ACK_TYPE) return { ok: false, stage: 5, reason: 'malformed' }\n if (Object.keys(d).some((k) => !ACK_FIELDS.has(k)) || !calOK(d.issuedAt)) return { ok: false, stage: 5, reason: 'malformed' }\n if (!schemaOk('payload-delivery-ack.schema.json', d.payload)) return { ok: false, stage: 7, reason: 'malformed' }\n const p = d.proof as Record<string, Json> | undefined\n if (p === null || typeof p !== 'object' || Object.keys(p).length !== 1 || typeof p.mac !== 'string') return { ok: false, stage: 8, reason: 'validation-failed' }\n if (d.ceremony?.enactment !== undefined) return { ok: false, stage: 8, reason: 'validation-failed' }\n if (d.payload.meaning !== 'received') return { ok: false, stage: 8, reason: 'validation-failed' }\n return { ok: true, ref: d.payload.ref, threadId: d.threadId }\n}\n\n/**\n * The context-free checks of a received SIGNED ack (the class rule's\n * form for a signature-class payload, 4.2/4.4), in stage order: 5 the\n * document profile, 7 the payload schema, 8 the proof form (an\n * eddsa-jcs-2022 DataIntegrityProof, no `mac`, calendar-valid), no\n * enactment, `meaning`. The signature itself (under `issuer`) and the\n * consistency against what the holder sent stay the caller's.\n */\nexport function signedAckForm (doc: unknown): { ok: true, ref: string, threadId: string } | ({ ok: false } & AckFormFailure) {\n const d = doc as Record<string, any>\n if (!schemaOk('rltp-delivery-document.schema.json', d) || d?.type !== ACK_TYPE) return { ok: false, stage: 5, reason: 'malformed' }\n if (Object.keys(d).some((k) => !ACK_FIELDS.has(k)) || !calOK(d.issuedAt)) return { ok: false, stage: 5, reason: 'malformed' }\n if (!schemaOk('payload-delivery-ack.schema.json', d.payload)) return { ok: false, stage: 7, reason: 'malformed' }\n const p = d.proof as Record<string, Json> | undefined\n if (p === null || typeof p !== 'object' || p.mac !== undefined || p.type !== 'DataIntegrityProof' || p.cryptosuite !== 'eddsa-jcs-2022' || !calOK(p.created)) {\n return { ok: false, stage: 8, reason: 'validation-failed' }\n }\n if (d.ceremony?.enactment !== undefined) return { ok: false, stage: 8, reason: 'validation-failed' }\n if (d.payload.meaning !== 'received') return { ok: false, stage: 8, reason: 'validation-failed' }\n return { ok: true, ref: d.payload.ref, threadId: d.threadId }\n}\n\n/** The proof form a received ack carries: the MAC object, a signature, or neither. */\nexport const ackProofForm = (doc: unknown): 'mac' | 'signed' | null => {\n const p = (doc as { proof?: Record<string, unknown> } | null)?.proof\n if (p === null || typeof p !== 'object') return null\n return typeof p.mac === 'string' ? 'mac' : typeof p.proofValue === 'string' ? 'signed' : null\n}\n\n/** Does the ack's MAC verify under `key`? (4.2; over JCS of the document without proof) */\nexport async function macAckValid (doc: { proof?: { mac?: unknown } } & Record<string, unknown>, key: Uint8Array, subtle?: SubtleCrypto): Promise<boolean> {\n const { proof, ...rest } = doc\n return typeof proof?.mac === 'string' && (await ackMac(key, rest, subtle)) === proof.mac\n}\n"]}