@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.
- package/CHANGELOG.md +97 -0
- package/README.md +209 -78
- package/dist/ack.d.ts +84 -0
- package/dist/ack.d.ts.map +1 -0
- package/dist/ack.js +114 -0
- package/dist/ack.js.map +1 -0
- package/dist/adapters/vti-mediator.d.ts +169 -0
- package/dist/adapters/vti-mediator.d.ts.map +1 -0
- package/dist/adapters/vti-mediator.js +659 -0
- package/dist/adapters/vti-mediator.js.map +1 -0
- package/dist/anchor.d.ts +226 -0
- package/dist/anchor.d.ts.map +1 -0
- package/dist/anchor.js +347 -0
- package/dist/anchor.js.map +1 -0
- package/dist/{holder.d.ts → carrier-holder.d.ts} +1 -1
- package/dist/carrier-holder.d.ts.map +1 -0
- package/dist/{holder.js → carrier-holder.js} +5 -4
- package/dist/carrier-holder.js.map +1 -0
- package/dist/carrier-identity.js +4 -4
- package/dist/carrier-identity.js.map +1 -1
- package/dist/carrier.d.ts.map +1 -1
- package/dist/carrier.js +4 -3
- package/dist/carrier.js.map +1 -1
- package/dist/ceremony.d.ts +3 -2
- package/dist/ceremony.d.ts.map +1 -1
- package/dist/ceremony.js +16 -26
- package/dist/ceremony.js.map +1 -1
- package/dist/codec.d.ts +54 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +494 -0
- package/dist/codec.js.map +1 -0
- package/dist/continuity.d.ts +67 -0
- package/dist/continuity.d.ts.map +1 -0
- package/dist/continuity.js +110 -0
- package/dist/continuity.js.map +1 -0
- package/dist/core.d.ts +4 -0
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js.map +1 -1
- package/dist/crypto.d.ts +23 -7
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +54 -20
- package/dist/crypto.js.map +1 -1
- package/dist/delivery.d.ts +31 -3
- package/dist/delivery.d.ts.map +1 -1
- package/dist/delivery.js +75 -28
- package/dist/delivery.js.map +1 -1
- package/dist/encounter/ceremony.d.ts +37 -0
- package/dist/encounter/ceremony.d.ts.map +1 -0
- package/dist/encounter/ceremony.js +662 -0
- package/dist/encounter/ceremony.js.map +1 -0
- package/dist/encounter.d.ts +91 -1
- package/dist/encounter.d.ts.map +1 -1
- package/dist/encounter.js +191 -12
- package/dist/encounter.js.map +1 -1
- package/dist/engine.d.ts +303 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +1509 -0
- package/dist/engine.js.map +1 -0
- package/dist/entropy.d.ts +28 -0
- package/dist/entropy.d.ts.map +1 -0
- package/dist/entropy.js +96 -0
- package/dist/entropy.js.map +1 -0
- package/dist/events.d.ts +198 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +2 -0
- package/dist/events.js.map +1 -0
- package/dist/fixtures.d.ts +30 -0
- package/dist/fixtures.d.ts.map +1 -0
- package/dist/fixtures.js +155 -0
- package/dist/fixtures.js.map +1 -0
- package/dist/forge.d.ts +47 -0
- package/dist/forge.d.ts.map +1 -0
- package/dist/forge.js +20 -0
- package/dist/forge.js.map +1 -0
- package/dist/group-star.d.ts +251 -0
- package/dist/group-star.d.ts.map +1 -0
- package/dist/group-star.js +348 -0
- package/dist/group-star.js.map +1 -0
- package/dist/holder-api.d.ts +165 -0
- package/dist/holder-api.d.ts.map +1 -0
- package/dist/holder-api.js +142 -0
- package/dist/holder-api.js.map +1 -0
- package/dist/holder-state-v1.d.ts +801 -0
- package/dist/holder-state-v1.d.ts.map +1 -0
- package/dist/holder-state-v1.js +2 -0
- package/dist/holder-state-v1.js.map +1 -0
- package/dist/holder-state-v2.d.ts +858 -0
- package/dist/holder-state-v2.d.ts.map +1 -0
- package/dist/holder-state-v2.js +2 -0
- package/dist/holder-state-v2.js.map +1 -0
- package/dist/identity-state.d.ts +43 -0
- package/dist/identity-state.d.ts.map +1 -0
- package/dist/identity-state.js +345 -0
- package/dist/identity-state.js.map +1 -0
- package/dist/identity.d.ts +24 -2
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +70 -11
- package/dist/identity.js.map +1 -1
- package/dist/index.d.ts +27 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +81 -29
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +37 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +22 -0
- package/dist/internal.js.map +1 -0
- package/dist/introduction/holder.d.ts +18 -0
- package/dist/introduction/holder.d.ts.map +1 -0
- package/dist/introduction/holder.js +764 -0
- package/dist/introduction/holder.js.map +1 -0
- package/dist/introduction.d.ts +101 -0
- package/dist/introduction.d.ts.map +1 -0
- package/dist/introduction.js +132 -0
- package/dist/introduction.js.map +1 -0
- package/dist/invariants.d.ts +43 -0
- package/dist/invariants.d.ts.map +1 -0
- package/dist/invariants.js +953 -0
- package/dist/invariants.js.map +1 -0
- package/dist/keys-seed.d.ts +7 -0
- package/dist/keys-seed.d.ts.map +1 -0
- package/dist/keys-seed.js +133 -0
- package/dist/keys-seed.js.map +1 -0
- package/dist/log-memory.d.ts +12 -0
- package/dist/log-memory.d.ts.map +1 -0
- package/dist/log-memory.js +101 -0
- package/dist/log-memory.js.map +1 -0
- package/dist/membership/holder.d.ts +33 -0
- package/dist/membership/holder.d.ts.map +1 -0
- package/dist/membership/holder.js +929 -0
- package/dist/membership/holder.js.map +1 -0
- package/dist/membership.d.ts +123 -0
- package/dist/membership.d.ts.map +1 -0
- package/dist/membership.js +180 -0
- package/dist/membership.js.map +1 -0
- package/dist/message/holder.d.ts +49 -0
- package/dist/message/holder.d.ts.map +1 -0
- package/dist/message/holder.js +290 -0
- package/dist/message/holder.js.map +1 -0
- package/dist/message.d.ts +43 -0
- package/dist/message.d.ts.map +1 -0
- package/dist/message.js +51 -0
- package/dist/message.js.map +1 -0
- package/dist/ports.d.ts +237 -0
- package/dist/ports.d.ts.map +1 -0
- package/dist/ports.js +2 -0
- package/dist/ports.js.map +1 -0
- package/dist/profile.d.ts +56 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +193 -0
- package/dist/profile.js.map +1 -0
- package/dist/prompts.d.ts +14 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +69 -0
- package/dist/prompts.js.map +1 -0
- package/dist/receive.d.ts +134 -0
- package/dist/receive.d.ts.map +1 -0
- package/dist/receive.js +317 -0
- package/dist/receive.js.map +1 -0
- package/dist/results.d.ts +173 -0
- package/dist/results.d.ts.map +1 -0
- package/dist/results.js +21 -0
- package/dist/results.js.map +1 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +45 -38
- package/dist/schemas.js.map +1 -1
- package/dist/sha256.d.ts +3 -0
- package/dist/sha256.d.ts.map +1 -0
- package/dist/sha256.js +69 -0
- package/dist/sha256.js.map +1 -0
- package/dist/star.d.ts +31 -0
- package/dist/star.d.ts.map +1 -0
- package/dist/star.js +56 -0
- package/dist/star.js.map +1 -0
- package/dist/state-builder.d.ts +42 -0
- package/dist/state-builder.d.ts.map +1 -0
- package/dist/state-builder.js +100 -0
- package/dist/state-builder.js.map +1 -0
- package/dist/state.d.ts +914 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +37 -0
- package/dist/state.js.map +1 -0
- package/dist/store-contract.d.ts +11 -0
- package/dist/store-contract.d.ts.map +1 -0
- package/dist/store-contract.js +189 -0
- package/dist/store-contract.js.map +1 -0
- package/dist/store-memory.d.ts +8 -0
- package/dist/store-memory.d.ts.map +1 -0
- package/dist/store-memory.js +17 -0
- package/dist/store-memory.js.map +1 -0
- package/dist/testing.d.ts +22 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +21 -0
- package/dist/testing.js.map +1 -0
- package/dist/trust/continuity.d.ts +71 -0
- package/dist/trust/continuity.d.ts.map +1 -0
- package/dist/trust/continuity.js +1222 -0
- package/dist/trust/continuity.js.map +1 -0
- package/dist/trust/duties.d.ts +67 -0
- package/dist/trust/duties.d.ts.map +1 -0
- package/dist/trust/duties.js +179 -0
- package/dist/trust/duties.js.map +1 -0
- package/dist/trust/entries.d.ts +139 -0
- package/dist/trust/entries.d.ts.map +1 -0
- package/dist/trust/entries.js +795 -0
- package/dist/trust/entries.js.map +1 -0
- package/dist/trust/group-star.d.ts +43 -0
- package/dist/trust/group-star.d.ts.map +1 -0
- package/dist/trust/group-star.js +188 -0
- package/dist/trust/group-star.js.map +1 -0
- package/dist/trust/mapping.d.ts +56 -0
- package/dist/trust/mapping.d.ts.map +1 -0
- package/dist/trust/mapping.js +584 -0
- package/dist/trust/mapping.js.map +1 -0
- package/dist/trust/profile.d.ts +25 -0
- package/dist/trust/profile.d.ts.map +1 -0
- package/dist/trust/profile.js +249 -0
- package/dist/trust/profile.js.map +1 -0
- package/dist/trust/star.d.ts +208 -0
- package/dist/trust/star.d.ts.map +1 -0
- package/dist/trust/star.js +588 -0
- package/dist/trust/star.js.map +1 -0
- package/dist/trust/state.d.ts +83 -0
- package/dist/trust/state.d.ts.map +1 -0
- package/dist/trust/state.js +154 -0
- package/dist/trust/state.js.map +1 -0
- package/dist/util-freeze.d.ts +3 -0
- package/dist/util-freeze.d.ts.map +1 -0
- package/dist/util-freeze.js +11 -0
- package/dist/util-freeze.js.map +1 -0
- package/dist/views.d.ts +179 -0
- package/dist/views.d.ts.map +1 -0
- package/dist/views.js +286 -0
- package/dist/views.js.map +1 -0
- package/dist/visibility.d.ts +5 -5
- package/dist/visibility.d.ts.map +1 -1
- package/dist/visibility.js +22 -28
- package/dist/visibility.js.map +1 -1
- package/dist/wire.d.ts +218 -83
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js.map +1 -1
- package/package.json +60 -10
- package/schemas/holder-state-v1.schema.json +3880 -0
- package/schemas/holder-state-v2.schema.json +4123 -0
- package/dist/holder.d.ts.map +0 -1
- package/dist/holder.js.map +0 -1
- package/dist/probe/acks.d.ts +0 -27
- package/dist/probe/acks.d.ts.map +0 -1
- package/dist/probe/acks.js +0 -152
- package/dist/probe/acks.js.map +0 -1
- package/dist/probe/continuity.d.ts +0 -20
- package/dist/probe/continuity.d.ts.map +0 -1
- package/dist/probe/continuity.js +0 -805
- package/dist/probe/continuity.js.map +0 -1
- package/dist/probe/deps.d.ts +0 -61
- package/dist/probe/deps.d.ts.map +0 -1
- package/dist/probe/deps.js +0 -54
- package/dist/probe/deps.js.map +0 -1
- package/dist/probe/encounter.d.ts +0 -26
- package/dist/probe/encounter.d.ts.map +0 -1
- package/dist/probe/encounter.js +0 -938
- package/dist/probe/encounter.js.map +0 -1
- package/dist/probe/introduce.d.ts +0 -62
- package/dist/probe/introduce.d.ts.map +0 -1
- package/dist/probe/introduce.js +0 -306
- package/dist/probe/introduce.js.map +0 -1
- package/dist/probe/membership.d.ts +0 -96
- package/dist/probe/membership.d.ts.map +0 -1
- package/dist/probe/membership.js +0 -467
- package/dist/probe/membership.js.map +0 -1
- package/dist/probe/trust.d.ts +0 -156
- package/dist/probe/trust.d.ts.map +0 -1
- package/dist/probe/trust.js +0 -1233
- package/dist/probe/trust.js.map +0 -1
- package/dist/probe.d.ts +0 -7
- package/dist/probe.d.ts.map +0 -1
- package/dist/probe.js +0 -42
- 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
|
|
9
|
-
the schemas and the test vectors it
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- Specification, schemas and vectors: <https://
|
|
13
|
-
- Zero dependencies. WebCrypto only (Ed25519, X25519, HKDF,
|
|
14
|
-
-
|
|
15
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
52
|
-
|
|
53
|
-
import {
|
|
54
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
`
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
|
84
|
-
|
|
85
|
-
|
|
|
86
|
-
|
|
|
87
|
-
|
|
|
88
|
-
|
|
|
89
|
-
|
|
|
90
|
-
|
|
|
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
|
|
103
|
-
version
|
|
104
|
-
negative case
|
|
105
|
-
|
|
106
|
-
|
|
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/)
|
|
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
|