@real-life/trust-protocol 0.3.2 → 0.5.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 (277) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +209 -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 +169 -0
  8. package/dist/adapters/vti-mediator.d.ts.map +1 -0
  9. package/dist/adapters/vti-mediator.js +659 -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 +54 -0
  29. package/dist/codec.d.ts.map +1 -0
  30. package/dist/codec.js +494 -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 +303 -0
  56. package/dist/engine.d.ts.map +1 -0
  57. package/dist/engine.js +1509 -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 +198 -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 +165 -0
  80. package/dist/holder-api.d.ts.map +1 -0
  81. package/dist/holder-api.js +142 -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/holder-state-v2.d.ts +858 -0
  88. package/dist/holder-state-v2.d.ts.map +1 -0
  89. package/dist/holder-state-v2.js +2 -0
  90. package/dist/holder-state-v2.js.map +1 -0
  91. package/dist/identity-state.d.ts +43 -0
  92. package/dist/identity-state.d.ts.map +1 -0
  93. package/dist/identity-state.js +345 -0
  94. package/dist/identity-state.js.map +1 -0
  95. package/dist/identity.d.ts +24 -2
  96. package/dist/identity.d.ts.map +1 -1
  97. package/dist/identity.js +70 -11
  98. package/dist/identity.js.map +1 -1
  99. package/dist/index.d.ts +27 -1
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +81 -29
  102. package/dist/index.js.map +1 -1
  103. package/dist/internal.d.ts +37 -0
  104. package/dist/internal.d.ts.map +1 -0
  105. package/dist/internal.js +22 -0
  106. package/dist/internal.js.map +1 -0
  107. package/dist/introduction/holder.d.ts +18 -0
  108. package/dist/introduction/holder.d.ts.map +1 -0
  109. package/dist/introduction/holder.js +764 -0
  110. package/dist/introduction/holder.js.map +1 -0
  111. package/dist/introduction.d.ts +101 -0
  112. package/dist/introduction.d.ts.map +1 -0
  113. package/dist/introduction.js +132 -0
  114. package/dist/introduction.js.map +1 -0
  115. package/dist/invariants.d.ts +43 -0
  116. package/dist/invariants.d.ts.map +1 -0
  117. package/dist/invariants.js +953 -0
  118. package/dist/invariants.js.map +1 -0
  119. package/dist/keys-seed.d.ts +7 -0
  120. package/dist/keys-seed.d.ts.map +1 -0
  121. package/dist/keys-seed.js +133 -0
  122. package/dist/keys-seed.js.map +1 -0
  123. package/dist/log-memory.d.ts +12 -0
  124. package/dist/log-memory.d.ts.map +1 -0
  125. package/dist/log-memory.js +101 -0
  126. package/dist/log-memory.js.map +1 -0
  127. package/dist/membership/holder.d.ts +33 -0
  128. package/dist/membership/holder.d.ts.map +1 -0
  129. package/dist/membership/holder.js +929 -0
  130. package/dist/membership/holder.js.map +1 -0
  131. package/dist/membership.d.ts +123 -0
  132. package/dist/membership.d.ts.map +1 -0
  133. package/dist/membership.js +180 -0
  134. package/dist/membership.js.map +1 -0
  135. package/dist/message/holder.d.ts +49 -0
  136. package/dist/message/holder.d.ts.map +1 -0
  137. package/dist/message/holder.js +290 -0
  138. package/dist/message/holder.js.map +1 -0
  139. package/dist/message.d.ts +43 -0
  140. package/dist/message.d.ts.map +1 -0
  141. package/dist/message.js +51 -0
  142. package/dist/message.js.map +1 -0
  143. package/dist/ports.d.ts +237 -0
  144. package/dist/ports.d.ts.map +1 -0
  145. package/dist/ports.js +2 -0
  146. package/dist/ports.js.map +1 -0
  147. package/dist/profile.d.ts +56 -0
  148. package/dist/profile.d.ts.map +1 -0
  149. package/dist/profile.js +193 -0
  150. package/dist/profile.js.map +1 -0
  151. package/dist/prompts.d.ts +14 -0
  152. package/dist/prompts.d.ts.map +1 -0
  153. package/dist/prompts.js +69 -0
  154. package/dist/prompts.js.map +1 -0
  155. package/dist/receive.d.ts +134 -0
  156. package/dist/receive.d.ts.map +1 -0
  157. package/dist/receive.js +317 -0
  158. package/dist/receive.js.map +1 -0
  159. package/dist/results.d.ts +173 -0
  160. package/dist/results.d.ts.map +1 -0
  161. package/dist/results.js +21 -0
  162. package/dist/results.js.map +1 -0
  163. package/dist/schemas.d.ts.map +1 -1
  164. package/dist/schemas.js +45 -38
  165. package/dist/schemas.js.map +1 -1
  166. package/dist/sha256.d.ts +3 -0
  167. package/dist/sha256.d.ts.map +1 -0
  168. package/dist/sha256.js +69 -0
  169. package/dist/sha256.js.map +1 -0
  170. package/dist/star.d.ts +31 -0
  171. package/dist/star.d.ts.map +1 -0
  172. package/dist/star.js +56 -0
  173. package/dist/star.js.map +1 -0
  174. package/dist/state-builder.d.ts +42 -0
  175. package/dist/state-builder.d.ts.map +1 -0
  176. package/dist/state-builder.js +100 -0
  177. package/dist/state-builder.js.map +1 -0
  178. package/dist/state.d.ts +914 -0
  179. package/dist/state.d.ts.map +1 -0
  180. package/dist/state.js +37 -0
  181. package/dist/state.js.map +1 -0
  182. package/dist/store-contract.d.ts +11 -0
  183. package/dist/store-contract.d.ts.map +1 -0
  184. package/dist/store-contract.js +189 -0
  185. package/dist/store-contract.js.map +1 -0
  186. package/dist/store-memory.d.ts +8 -0
  187. package/dist/store-memory.d.ts.map +1 -0
  188. package/dist/store-memory.js +17 -0
  189. package/dist/store-memory.js.map +1 -0
  190. package/dist/testing.d.ts +22 -0
  191. package/dist/testing.d.ts.map +1 -0
  192. package/dist/testing.js +21 -0
  193. package/dist/testing.js.map +1 -0
  194. package/dist/trust/continuity.d.ts +71 -0
  195. package/dist/trust/continuity.d.ts.map +1 -0
  196. package/dist/trust/continuity.js +1222 -0
  197. package/dist/trust/continuity.js.map +1 -0
  198. package/dist/trust/duties.d.ts +67 -0
  199. package/dist/trust/duties.d.ts.map +1 -0
  200. package/dist/trust/duties.js +179 -0
  201. package/dist/trust/duties.js.map +1 -0
  202. package/dist/trust/entries.d.ts +139 -0
  203. package/dist/trust/entries.d.ts.map +1 -0
  204. package/dist/trust/entries.js +795 -0
  205. package/dist/trust/entries.js.map +1 -0
  206. package/dist/trust/group-star.d.ts +43 -0
  207. package/dist/trust/group-star.d.ts.map +1 -0
  208. package/dist/trust/group-star.js +188 -0
  209. package/dist/trust/group-star.js.map +1 -0
  210. package/dist/trust/mapping.d.ts +56 -0
  211. package/dist/trust/mapping.d.ts.map +1 -0
  212. package/dist/trust/mapping.js +584 -0
  213. package/dist/trust/mapping.js.map +1 -0
  214. package/dist/trust/profile.d.ts +25 -0
  215. package/dist/trust/profile.d.ts.map +1 -0
  216. package/dist/trust/profile.js +249 -0
  217. package/dist/trust/profile.js.map +1 -0
  218. package/dist/trust/star.d.ts +208 -0
  219. package/dist/trust/star.d.ts.map +1 -0
  220. package/dist/trust/star.js +588 -0
  221. package/dist/trust/star.js.map +1 -0
  222. package/dist/trust/state.d.ts +83 -0
  223. package/dist/trust/state.d.ts.map +1 -0
  224. package/dist/trust/state.js +154 -0
  225. package/dist/trust/state.js.map +1 -0
  226. package/dist/util-freeze.d.ts +3 -0
  227. package/dist/util-freeze.d.ts.map +1 -0
  228. package/dist/util-freeze.js +11 -0
  229. package/dist/util-freeze.js.map +1 -0
  230. package/dist/views.d.ts +179 -0
  231. package/dist/views.d.ts.map +1 -0
  232. package/dist/views.js +286 -0
  233. package/dist/views.js.map +1 -0
  234. package/dist/visibility.d.ts +5 -5
  235. package/dist/visibility.d.ts.map +1 -1
  236. package/dist/visibility.js +22 -28
  237. package/dist/visibility.js.map +1 -1
  238. package/dist/wire.d.ts +218 -83
  239. package/dist/wire.d.ts.map +1 -1
  240. package/dist/wire.js.map +1 -1
  241. package/package.json +60 -10
  242. package/schemas/holder-state-v1.schema.json +3880 -0
  243. package/schemas/holder-state-v2.schema.json +4123 -0
  244. package/dist/holder.d.ts.map +0 -1
  245. package/dist/holder.js.map +0 -1
  246. package/dist/probe/acks.d.ts +0 -27
  247. package/dist/probe/acks.d.ts.map +0 -1
  248. package/dist/probe/acks.js +0 -152
  249. package/dist/probe/acks.js.map +0 -1
  250. package/dist/probe/continuity.d.ts +0 -20
  251. package/dist/probe/continuity.d.ts.map +0 -1
  252. package/dist/probe/continuity.js +0 -805
  253. package/dist/probe/continuity.js.map +0 -1
  254. package/dist/probe/deps.d.ts +0 -61
  255. package/dist/probe/deps.d.ts.map +0 -1
  256. package/dist/probe/deps.js +0 -54
  257. package/dist/probe/deps.js.map +0 -1
  258. package/dist/probe/encounter.d.ts +0 -26
  259. package/dist/probe/encounter.d.ts.map +0 -1
  260. package/dist/probe/encounter.js +0 -938
  261. package/dist/probe/encounter.js.map +0 -1
  262. package/dist/probe/introduce.d.ts +0 -62
  263. package/dist/probe/introduce.d.ts.map +0 -1
  264. package/dist/probe/introduce.js +0 -306
  265. package/dist/probe/introduce.js.map +0 -1
  266. package/dist/probe/membership.d.ts +0 -96
  267. package/dist/probe/membership.d.ts.map +0 -1
  268. package/dist/probe/membership.js +0 -467
  269. package/dist/probe/membership.js.map +0 -1
  270. package/dist/probe/trust.d.ts +0 -156
  271. package/dist/probe/trust.d.ts.map +0 -1
  272. package/dist/probe/trust.js +0 -1233
  273. package/dist/probe/trust.js.map +0 -1
  274. package/dist/probe.d.ts +0 -7
  275. package/dist/probe.d.ts.map +0 -1
  276. package/dist/probe.js +0 -42
  277. package/dist/probe.js.map +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,97 @@
1
+ # Changelog
2
+
3
+ ## 0.5.0
4
+
5
+ Takes up Message Tasks 0.1, Delivery Contract 0.82 and Membership Tasks
6
+ 0.20.
7
+
8
+ - **Messages** (Message Tasks 0.1): `sendMessage(contactId, text,
9
+ {when, actId, replyTo?})` sends a `message@1` on a ready, unblocked
10
+ relationship (refused as `not-ready`, `blocked` or `message-invalid`;
11
+ event `message.sent` with the mid and the document digest). Reception
12
+ runs the stages of Delivery 6.2: the MAC under `k_msg`, the age bound
13
+ (`failed(stale-issuance)`) and the future bound on the holder's time
14
+ mark, the completion record per tuple (no new effect for a known body,
15
+ equivocation refused), the inbox entry committed with the cache entry
16
+ and the acknowledgement. `inbox()` lists the messages not yet taken
17
+ over, in display order; `takeMessages(ids, {when})` takes them over.
18
+ `ContactView.canMessage`. The pure forms (`messageKeyOf`,
19
+ `messageMac`, `messageProblem`, `MESSAGE_TYPE`, …) are exported from the
20
+ root.
21
+ - **The block** (Message Tasks 6): `block(contactId)` and
22
+ `unblock(contactId)`, chain-level state of every relationship of the
23
+ entry, kept through chaining, merge and split; event `contact.blocked`,
24
+ `ContactView.blocked`. Under a block messages are discarded and
25
+ invitations declined into the register of declined invitations,
26
+ silently and acknowledged; the holder's own messages, invitations and
27
+ introduction acts toward a blocked relationship are refused. The
28
+ treatment of the Trust Layer's types waits for that layer's next
29
+ casting.
30
+ - **Invitations** (Membership Tasks 0.20 §3.1): attributed to the
31
+ relationship of their prelude binding, or to none; at most 20 new
32
+ credential digests per relationship and day; one surfacing per
33
+ credential digest, decided with the register and the permanent record
34
+ of surfaced invitations.
35
+ - **The prelude** (Membership §3.5): the registered types
36
+ `membership-prelude-request/0.1` and `membership-prelude-reply/0.1` with
37
+ the binding `membership-inviter@1`, kept per relationship for its life;
38
+ genesis digests compared by their bytes. **Breaking:** a holder of 0.4.0
39
+ sends `x-membership-prelude-*`, which 0.5.0 refuses as `unknown-type`.
40
+ - The inviter admits without a human act only before
41
+ `validUntil + membership-skew + P90D` (Membership 3282).
42
+ - **Delivery 0.82**: the transient disposition `failed(rate-limited)`
43
+ (nothing committed, no ack, no cache entry, evaluated afresh when
44
+ delivered again); an outbound may carry its type's sender lifetime,
45
+ after which resume moves it to `failed(task-expired)` and never hands it
46
+ out again — for a message `sentAt + P179D`, after any retry horizon
47
+ (at most P90D), so an undelivered message fails as
48
+ `failed(expired-by-adapter-policy)` first.
49
+ - `./adapters/vti-mediator`: `conclude(collection, now, {keep})` leaves
50
+ transient items unconcluded and offers them again; `concludable(result)`.
51
+ - **State codec 2**, with a pure migration from codec 1 (no block, no
52
+ rate, no record, no inbox; the ready tuples read from the state; every
53
+ received invitation counted as surfaced), its non-normative schema
54
+ `schemas/holder-state-v2.schema.json` and the type `HolderStateV2`, and
55
+ golden fixtures reproduced in `npm test`. The codec 1 fixtures are
56
+ frozen as 0.4.0 wrote them and migrated in the tests.
57
+ - Closed unions grown: `ActError` (`blocked`, `not-ready`,
58
+ `message-invalid`), `ReceiveFailure` (`rate-limited`), `HolderEvent`
59
+ (`contact.blocked`, `message.sent`, `message.received`), the outbox's
60
+ failure reasons (`task-expired`), the entropy purposes (`message.mid`).
61
+ - Engine (for modules): `keepKeys(state, when)` gets the tick's time; the
62
+ module hook `accepted` runs in the stage-9 transaction of every unique
63
+ document.
64
+ - Tests: the scenario set of Message Tasks §10.2, the race of #103 on two
65
+ engines over one store, and a seeded interleaving generator over
66
+ messages, the block, take-over and the record's retention.
67
+
68
+ ## 0.4.0
69
+
70
+ - `createHolder`: one person's device state behind typed human acts
71
+ (`{when, actId}`, journaled), `receive`, `report`, `resume` and `tick`,
72
+ with the views `contacts`, `contact`, `groups`, `profile`, `prompts`,
73
+ `deliveryStatus` and `subscribe`. Results are a closed union with rule
74
+ references; events are a closed union, issued after the commit.
75
+ - Ports: `Store` (compare-and-swap), `KeyProvider` (`seedKeys` by default),
76
+ `Entropy` by closed purpose, `Log`, `Access`, `RetryPolicy`,
77
+ `DeliveryReport`, pure-JSON `Outbound`. `storeContract` is the suite a
78
+ Store adapter passes.
79
+ - Modules on the holder: identity (the register, device names, the
80
+ community anchor's rotation over the Log port), trust (the trust act and
81
+ the classes, the anchor mapping, the star and the group star, merge,
82
+ split and departure, continuity, `profile@1`, the duties), encounter
83
+ (`encounter-scan@0.25` with both paths), membership (founding, prelude,
84
+ invite, admission, welcome, `vouch@2`) and introduction (Trust §8, three
85
+ roles).
86
+ - Delivery: stages 1–9 once per envelope through a task register, the
87
+ completed-effect cache with stored acks, the outbox with the status
88
+ trias, retry by backoff, give-up at the horizon.
89
+ - State codec 1 with golden fixtures (`test/fixtures/`) and a
90
+ non-normative JSON Schema (`schemas/holder-state-v1.schema.json`) with
91
+ the generated type `HolderStateV1`; a newer codec is `codec-newer`, a
92
+ codec without a validator `codec-unknown`.
93
+ - Entry points: the root, `./testing`, `./internal`,
94
+ `./adapters/vti-mediator`. `visibility` and `ceremony` carry pure wire
95
+ builders and verifiers only. There is no `./probe` entry point.
96
+ - Invariant predicates over every commit (`invariants: true`) and a
97
+ 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,219 @@ 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`, `sendMessage`, `block`, `unblock`, `rotateAnchor`,
71
+ `addDevice`, `revokeDevice`. Not
72
+ human: `receive`, `report`, `resume`, `tick`, `recover`, `takeMessages`.
73
+ Reads: `contacts`, `contact`, `groups`, `profile`, `prompts`, `inbox`,
74
+ `deliveryStatus`, `deviceKey`, `snapshot`, `subscribe`.
75
+
76
+ ## What the root exports
77
+
78
+ - `createHolder` and its types (`Holder`, `HolderOptions`, `HumanActOptions`,
79
+ `HolderSnapshot`).
80
+ - The ports: `Store`, `KeyProvider`, `Entropy`, `Log`, `Access`,
81
+ `RetryPolicy`, `DeliveryReport`, `Outbound`; `seedKeys(seed)` is the
82
+ default KeyProvider, `randomEntropy` the default Entropy.
83
+ - The views: `ContactView`, `GroupView`, `PromptView`, `ProfileView`,
84
+ `InboxMessageView`, `DeliveryStatusView` and their parts.
85
+ - The results and events: `ActResult`, `ReceiveResult`, `ResumeResult`,
86
+ `InfraError`, `CryptoUnavailableError`, `HolderEvent`.
87
+ - The state as an opaque type (`HolderState`), `decodeState`, `CODEC`,
88
+ `StateCodecError`, and `storeContract`, the suite a Store adapter passes.
89
+ - The pure wire builders and verifiers of every layer (`core`, `crypto`,
90
+ `identity`, `profile`, `encounter`, `membership`, `message` —
91
+ `MESSAGE_TYPE`, `messageKeyOf`, `messageMac`, `messageProblem`,
92
+ `messageShapeOk` and the constants of Message Tasks 0.1 —,
93
+ `introduction`, `delivery`, `carrier-identity`, `carrier`,
94
+ `carrier-holder`), and two
95
+ namespaces of pure forms: `visibility` (anchor, star, group star,
96
+ continuity, acks) and `ceremony` (the encounter's, the group's and the
97
+ introduction's forms).
98
+
99
+ Other entry points:
100
+
101
+ | Entry point | What it holds |
102
+ |---|---|
103
+ | `./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. |
104
+ | `./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. |
105
+ | `./adapters/vti-mediator` | The VTI mediator as a carrier adapter: its submission states as `DeliveryReport`, collection and ack handling, the selective conclusion of a collection (`conclude(c, now, {keep})`, `concludable(result)`: a `failed(rate-limited)` item stays with the carrier). |
106
+
107
+ ## The modules and the rules they implement
108
+
109
+ Bare four-digit numbers are RLTP-TRU rules; the other layers name theirs.
110
+
111
+ | Module | Specification | Rules |
112
+ |---|---|---|
113
+ | `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 |
114
+ | `crypto` | primitives | HKDF, key pairs, did:key and multikey, X25519, the `eddsa-jcs-2022` Data Integrity proof |
115
+ | `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 |
116
+ | `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) |
117
+ | `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 |
118
+ | `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 |
119
+ | `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 |
120
+ | `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) |
121
+ | `trust/continuity` | Trust §6a | `continuity-probe@1` and `continuity-mapping@1`, chaining a re-encounter into the existing relationship (6a.4) |
122
+ | `trust/profile`, `profile` | Trust §6b | `profile@1`: the one revision counter (6450), delivery on the class gates (6455), reception and the image bounds (6475) |
123
+ | `trust/duties` | Trust 2850, 6370, 6455, 6595, 6596 | the duties a commit leaves: class on, re-issue after a rotation, the lag rule |
124
+ | `membership`, `membership/holder` | Membership Tasks 0.20 on Access 0.57 | founding (Access 3.4.1), the prelude (3.5: `membership-prelude-request/0.1`, `membership-prelude-reply/0.1`, the binding per relationship), invite, accept, admission within the bound of 3282 and welcome, evidence relay and `vouch@2` (Access 5.3), an in-library Access over the holder's groups |
125
+ | `message`, `message/holder` | Message Tasks 0.1 | `message@1`, `k_msg` and its MAC (3); sending on a ready, unblocked relationship with the sender lifetime (5); reception through the stages with the age and future bounds on the time mark, the completion record, the inbox taken over by the app, the rates and the inbox bound as `failed(rate-limited)` (4); the block of a relationship (6010–6030, 6510), its discard of messages and its treatment of invitations and prelude replies (Membership 3115, 3975), the holder's own offers refused (6410) |
126
+ | `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) |
127
+ | `receive`, `engine` | Delivery 0.82 §5, §6.1, §6.2 | the sealed envelope and the two key classes, stages 1–9 once per envelope with the dispositions `discarded` and the transient `failed(rate-limited)`, 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 |
128
+ | `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 |
129
+ | `views`, `prompts` | — | the read model: contacts by entry (2120, 2140), groups, prompts, the delivery status |
130
+
131
+ ## Runtime
132
+
133
+ | Runtime | Status |
134
+ |---|---|
135
+ | Node 22 | tested in CI (the library job) |
136
+ | Node 24 | tested locally |
137
+ | Node 20 | the declared minimum (`engines`); Ed25519 and X25519 are in its WebCrypto |
138
+ | Chromium 145 | tested by the workbench's end-to-end run (`apps/gesamtsimulator/tests/local-perspective.mjs`) |
139
+ | Chromium ≥ 137, Firefox ≥ 129, Safari ≥ 17 | expected: they carry Ed25519 and X25519 in WebCrypto; not tested here |
140
+
141
+ The WebCrypto handle is resolved when a holder is built, not at import: an
142
+ app may pass its own (`createHolder({crypto})`, `seedKeys(seed, crypto)`),
143
+ and a runtime without Ed25519 or X25519 is reported then as
144
+ `CryptoUnavailableError`.
145
+
146
+ ## State, codec and storage
147
+
148
+ A holder's state is pure JSON under a codec version. The Store port takes
149
+ the state as opaque JSON and gives it back; the library decodes it
150
+ (`decodeState`), the adapter never migrates.
151
+
152
+ - **Codec 1** is the format of 0.4.0, the first stable one. Its golden
153
+ fixtures (`test/fixtures/state-v1*.json`, written by lib 0.4.0 with
154
+ `test/fixtures/gen-state-v1.mjs` and frozen since) cover a settled
155
+ holder, one with work open (prompts, a joining invite, duties, a
156
+ rotation at step 2) and one with a tombstone, a revoked device and
157
+ carrier entries. They validate under codec 1 and migrate to codec 2.
158
+ - **Codec 2** is the format of 0.5.0: codec 1 with the state of Message
159
+ Tasks 0.1 (`messages`: the block, the rates, the ready tuples, the
160
+ completion record, the inbox), an outbox item's sender lifetime
161
+ (`expiresAt`) and `failed(task-expired)`, and the record of surfaced
162
+ and the register of declined invitations (`joining.surfaced`,
163
+ `joining.declined`); an invitation may be attributed to no
164
+ relationship. The migration from codec 1 is pure: no block, no rate, no
165
+ record, no inbox, the ready tuples read from what the counterparts'
166
+ documents left, every invitation received counted as surfaced. Its
167
+ fixtures (`test/fixtures/state-v2*.json`, deterministic runs of
168
+ `test/fixtures/gen-state-v2.mjs`, reproduced in `npm test`) add
169
+ messages to the settled holder and a block and a declined invitation to
170
+ the open one; each decodes and re-encodes byte for byte. A later codec brings its validator, a pure
171
+ migration from the one before, and its own fixture with a migration
172
+ test.
173
+ - A newer codec is refused as `codec-newer`, a codec without a validator
174
+ as `codec-unknown`; nothing is read best-effort.
175
+ - `schemas/holder-state-v1.schema.json` and
176
+ `schemas/holder-state-v2.schema.json` describe the codecs as
177
+ non-normative JSON Schemas (published with the package). The types
178
+ `HolderStateV1` and `HolderStateV2` (`./internal`) are generated from
179
+ them (`scripts/build-holder-state-types.mjs`); the build holds the
180
+ current one against the library's `HolderState`, and the tests hold each
181
+ schema against its codec's validator on the fixtures and on mutations
182
+ of them. Rules only the codec states: a record's id equals its key, an
183
+ envelope's `rkid` equals its recipient's `keyAgreement`, and (codec 2) a
184
+ set of strings is sorted.
185
+
186
+ **Writing a Store adapter.** Implement `read(): Promise<{state, version}>`
187
+ (a deep, independent copy; `null` state and version for an empty store) and
188
+ `commit(version, next): Promise<{ok: true, version} | {ok: false, reason:
189
+ 'conflict'}>` as compare-and-swap: atomic, storing a copy, a version token
190
+ never reused in the store's lifetime. One writer per holder; the adapter
191
+ guarantees it. Then run the contract against it:
49
192
 
50
193
  ```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
194
+ import { test } from 'node:test'
195
+ import assert from 'node:assert/strict'
196
+ import { storeContract } from '@real-life/trust-protocol'
197
+
198
+ storeContract(() => new MyStore(), { test, assert }) // a fresh, empty store per call
55
199
  ```
56
200
 
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.
201
+ It checks snapshot isolation, CAS, atomicity, a lost result, the codec
202
+ boundary and reopening. Durability across process loss is the persistent
203
+ adapter's own test.
204
+
205
+ ## Invariants and the generator
206
+
207
+ `invariants.ts` states predicates over a commit (`state → state'`): the
208
+ register and revocation marks only grow, tombstones never shrink, a salt is
209
+ issued at most once per tuple, the effect cache holds only completed
210
+ digests, `accepted` arises only from a report, a duty leaves only by its
211
+ specified fulfilment. `createHolder(ports, {invariants: true})` checks them
212
+ on every commit. `test/invariants-generator.test.mjs` runs random sequences
213
+ of acts, receives, reports and ticks over two and three holders and checks
214
+ every predicate after every step.
76
215
 
77
216
  ## Versions
78
217
 
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.
218
+ This package uses its own semver; the specification uses castings.
219
+
220
+ | Area | Casting | Not in this package |
221
+ |---|---|---|
222
+ | identity | Identity 0.54 | recovery derivation (§5.3), service identities (§7) |
223
+ | encounter | Encounter 0.31, cards 0.25, credentials 0.26 | — |
224
+ | trust | Trust Layer 0.31 | — |
225
+ | membership | Membership Tasks 0.20, 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`); an admission prompt after the bound of RLTP-MT-3282; roster replication: a holder knows the members it admitted and the roster its welcome carried |
226
+ | 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` |
227
+ | delivery | Delivery 0.82 | transport adapters beyond `./adapters/vti-mediator`; the block of the Trust Layer's types (Message Tasks 6240: their treatments wait for the next casting of the Trust Layer) |
228
+ | message | Message Tasks 0.1 | holding a message unsent until its tuple is ready (5020): `sendMessage` answers `not-ready` and the app keeps the message; checking `replyTo` against the relationship's history (5120), which is the app's; multi-device (§9); `failed(task-expired)` for a message: reachable only with a retry horizon beyond P179D, which the engine does not admit (at most P90D), so an undelivered message ends as `failed(expired-by-adapter-policy)` |
229
+ | devices | Identity §5.5 | multi-device production: one writer per holder |
99
230
 
100
231
  ## The contract: the vectors
101
232
 
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.
233
+ A version that does not reproduce `vectors/` byte for byte is not a valid
234
+ version. The conformance runner checks every cryptographic claim, and every
235
+ negative case fails at its declared stage. An implementation in another
236
+ language checks itself against the same vectors; it does not need this
237
+ package.
107
238
 
108
239
  ## License
109
240
 
110
- [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) — specification and
241
+ [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), specification and
111
242
  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