Skip to content

Commit f030ed7

Browse files
docs: post-release hygiene + round-3 reviewer delta
Four trailing-doc updates following the 0.1.0-alpha.5 + 0.2.0-alpha.1 + 0.2.0-alpha.2 releases: - README.md: `cargo add` snippet bumped alpha.1 → alpha.2; spec pointer refreshed draft-01 → draft-03; note that 0.1.x alphas are wire-incompatible at the signed-consent-body layer. - FOLLOW_UPS.md: "stay at 0.1.0-alpha.x until review" policy replaced with post-Phase-B reality ("stay at 0.2.0-alpha.x until round-3 review"); Ricardian-binding entry draft-02 → draft-03; new Closed-items section capturing #1#5 resolutions. - plans/OPEN_ISSUES_PLAN.md: top-of-file status flipped from "Phase B deferred" to "CLOSED"; release table gains the alpha.2 hardening row. - plans/REVIEW_DELTA_DRAFT_03.md (new): scoped round-3 reviewer document. ~650 words. §1 deltas since draft-02r1 (receiver- local draft-02r2, breaking draft-03, no-wire alpha.2 hardening). §2 six targeted questions ranked by cryptographic weight (fingerprint info composition; BE request_id choice; timing side-channel in verify_fingerprint_either_epoch; transition-table corner case for LegacyBypass; Response vs Revocation timestamp asymmetry; overall §12.8 / §12.10 coherence). §3 source-file pointers. Intent: the reviewer reads ~10 minutes, answers six scoped questions, we close the round-3 cycle without requiring a full spec re-read. docs.rs verified: status:true on 0.1.0-alpha.5, 0.2.0-alpha.1, and 0.2.0-alpha.2 (no silent feature-matrix failures).
1 parent ec89c21 commit f030ed7

4 files changed

Lines changed: 286 additions & 21 deletions

File tree

FOLLOW_UPS.md

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ dependency.
9393
### v1.1 — Ricardian causal-binding on ConsentRequest
9494

9595
- **Placeholder**: `causal_binding: Option<CausalPredicate>` field
96-
already in the draft-02 wire format.
96+
already in the draft-03 wire format (MUST be `None` in draft-03).
9797
- **What**: authority binding ("valid while ticket #1234 is
9898
In-Progress") evaluated against an external truth source.
9999
- **Blocker**: decentralized-identity layer on Holochain must have
@@ -157,18 +157,21 @@ dependency.
157157

158158
## Decisions being deferred
159159

160-
### When to bump to 0.1.0 stable
160+
### When to bump to 0.2.0 stable
161161

162-
The original Track A plan targeted `0.1.0` at Week 6. We are at
163-
Week 6 without external crypto review. Bumping stable now would be
164-
a discipline failure — the spec is unreviewed, the wire format is
165-
still subject to change, and the consent ceremony has seven days of
166-
in-repo history.
162+
**Updated 2026-04-18 (post-Phase-B).** The 0.1.0 target became a
163+
0.2.0 target once draft-03 added mandatory `session_fingerprint` (a
164+
breaking wire change at the signed-consent-body layer). Published
165+
state: `0.2.0-alpha.2`, SPEC draft-03, all four round-2 review
166+
issues closed.
167167

168-
**Current policy**: stay at `0.1.0-alpha.x` until at least one
169-
independent cryptographer has signed off on SPEC §3 + §5 + §12.
170-
If review finds issues, iterate alpha.4+. If review is clean, bump
171-
to `0.1.0-rc.1` as a test run before `0.1.0` stable.
168+
**Current policy**: stay at `0.2.0-alpha.x` until a round-3
169+
independent cryptographer review signs off on the draft-03-specific
170+
additions — specifically SPEC §12.3.1 (session_fingerprint HKDF
171+
construction), §12.6.1 (normative transition table), and §12.8
172+
(timing-channel assumption). A scoped delta doc for the reviewer
173+
is at `plans/REVIEW_DELTA_DRAFT_03.md`. If review finds issues,
174+
iterate alpha.3+. If review is clean, bump to `0.2.0-rc.1`.
172175

173176
### Whether to host a live demo
174177

@@ -188,8 +191,18 @@ main track.
188191

189192
## Closed items
190193

191-
*(None yet — this file was created at Week 6 launch. Closed items
192-
move here with a one-line note.)*
194+
- **Round-2 review issues #1-#4** (2026-04-18). All four items
195+
flagged by the round-2 cryptographic review are now closed. #2
196+
split `Pending` + #4 configurable replay window shipped as
197+
`0.1.0-alpha.5` (SPEC draft-02r2, receiver-local). #1 mandatory
198+
`session_fingerprint` + #3 normative transition table shipped as
199+
`0.2.0-alpha.1` (SPEC draft-03, breaking at the signed-body
200+
layer). Receiver hardening (rekey-aware verify, timing-channel
201+
note, violation vectors, fuzz harness, MIGRATION.md) shipped as
202+
`0.2.0-alpha.2`.
203+
- **Per-key-epoch replay bug** (#5) (2026-04-18). Closed in
204+
`0.1.0-alpha.4`. `ReplayWindow` keyed by `(source_id, pld_type,
205+
key_epoch)` per SPEC §5.3.
193206

194207
---
195208

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,17 @@ caller's), and no opinion about handshake. Those live at higher layers.
6565

6666
## Install
6767

68-
Because `0.1.0-alpha.1` is a pre-release, add it with the `@` form —
68+
Because `0.2.0-alpha.2` is a pre-release, add it with the `@` form —
6969
`cargo add --version ...` rejects pre-release specifiers:
7070

7171
```console
72-
$ cargo add 'xenia-wire@0.1.0-alpha.1'
72+
$ cargo add 'xenia-wire@0.2.0-alpha.2'
7373
```
7474

75-
Once a stable `0.1.0` ships, `cargo add xenia-wire` will just work.
75+
Once a stable `0.2.0` ships, `cargo add xenia-wire` will just work.
76+
Earlier `0.1.x` alphas are still on crates.io but are wire-incompatible
77+
at the signed-consent-body layer (see SPEC Appendix B for the draft
78+
matrix); new integrations should start on `0.2.x`.
7679

7780
## Quick start
7881

@@ -186,7 +189,7 @@ exposition; [`SPEC.md`](SPEC.md) is the normative reference.
186189

187190
## Specification
188191

189-
Reading [`SPEC.md`](SPEC.md) (**draft-01**) should be sufficient to
192+
Reading [`SPEC.md`](SPEC.md) (**draft-03**, current) should be sufficient to
190193
write an interoperable implementation in any language. Reading the
191194
source of this crate should NOT be necessary. If you find gaps in
192195
the spec, please file an issue — the spec is the normative reference,

plans/OPEN_ISSUES_PLAN.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# Open-issues plan (post-alpha.4)
1+
# Open-issues plan (post-alpha.4) — CLOSED
22

3-
**Status**: Phase A **SHIPPED** as `0.1.0-alpha.5` on 2026-04-18 (commit `4a337a7`, tag `v0.1.0-alpha.5`). Phase B deferred — needs user sign-off on design questions enumerated in issues [#1](https://github.com/Luminous-Dynamics/xenia-wire/issues/1) and [#3](https://github.com/Luminous-Dynamics/xenia-wire/issues/3).
3+
**Status**: Phase A + Phase B both **SHIPPED**; all four round-2 review issues resolved. This document is retained as the design-decision provenance for the shipped work; nothing in it is still pending execution. Further hardening ([`0.2.0-alpha.2`](https://github.com/Luminous-Dynamics/xenia-wire/releases/tag/v0.2.0-alpha.2)) is captured in the CHANGELOG, not here.
44
**Scope**: all 4 GitHub issues left open after `xenia-wire 0.1.0-alpha.4`.
5-
**Last updated**: 2026-04-18 — Phase A closeout.
5+
**Last updated**: 2026-04-18 — Phase B closeout.
66

77
This document collapsed issues #1#4 into an executable plan. Each issue
88
got a concrete design (API shape, state transitions, file list, test
@@ -11,7 +11,8 @@ plan). The four were batched into two releases by wire-format compatibility:
1111
| Release | Wire change? | Issues | Status |
1212
|---------|--------------|--------|--------|
1313
| **`0.1.0-alpha.5`** (SPEC draft-02r2) | No | [#2](https://github.com/Luminous-Dynamics/xenia-wire/issues/2) split `Pending`, [#4](https://github.com/Luminous-Dynamics/xenia-wire/issues/4) configurable replay window |**shipped 2026-04-18** |
14-
| **`0.2.0-alpha.1`** (SPEC draft-03) | Yes (breaking) | [#1](https://github.com/Luminous-Dynamics/xenia-wire/issues/1) `session_binding`, [#3](https://github.com/Luminous-Dynamics/xenia-wire/issues/3) duplicate/conflict transition table | ⏸️ deferred pending sign-off |
14+
| **`0.2.0-alpha.1`** (SPEC draft-03) | Yes (breaking) | [#1](https://github.com/Luminous-Dynamics/xenia-wire/issues/1) `session_binding`, [#3](https://github.com/Luminous-Dynamics/xenia-wire/issues/3) duplicate/conflict transition table |**shipped 2026-04-18** |
15+
| **`0.2.0-alpha.2`** (draft-03 hardening) | No | Rekey-aware verify; timing-channel assumption; violation vectors; fuzz target; MIGRATION.md |**shipped 2026-04-18** |
1516

1617
The batching matters: draft-03 breaks the canonical bytes of signed
1718
`ConsentRequestCore`, so anyone consuming consent test vectors or

plans/REVIEW_DELTA_DRAFT_03.md

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
# Round-3 review — delta since draft-02r1
2+
3+
**Target version**: `xenia-wire 0.2.0-alpha.2` / SPEC **draft-03**.
4+
**Assumed prior context**: you last reviewed **draft-02r1**
5+
(`xenia-wire 0.1.0-alpha.3` / `alpha.4`, April 2026), which flagged
6+
four open design items: session-binding (loose), split-Pending,
7+
duplicate/conflict consent semantics, configurable replay window.
8+
9+
This document is the scoped follow-up. It is deliberately short:
10+
11+
- **§1** is the deltas since draft-02r1 — nothing you haven't seen
12+
before if you tracked Appendix B, but collected here for quick
13+
orientation.
14+
- **§2** is six targeted questions ranked by cryptographic weight.
15+
Questions 1-3 are where we genuinely would like an independent
16+
opinion; 4-6 are lower-risk "any concerns?" items.
17+
18+
Section-number references (e.g., §12.3.1) are to SPEC.md at
19+
`v0.2.0-alpha.2`. Source-file references are to the reference
20+
implementation at the same tag.
21+
22+
---
23+
24+
## 1. Deltas since draft-02r1
25+
26+
### draft-02r2 (shipped `0.1.0-alpha.5`, no wire change)
27+
28+
- Split `ConsentState::Pending` into **`LegacyBypass`** (default,
29+
sticky) and **`AwaitingRequest`** (opt-in via
30+
`SessionBuilder::require_consent(true)`). Pure receiver-local;
31+
no envelope bytes change.
32+
- Replay window parameterized: `W ∈ {64, 128, 256, 512, 1024}`
33+
bits, default 64. Multi-word bitmap shift internal to
34+
`ReplayWindow`. Peers agree on `W` out-of-band.
35+
36+
These are bookkeeping; we don't expect review input unless you see
37+
a hazard.
38+
39+
### draft-03 (shipped `0.2.0-alpha.1`, **breaking** at signed-body layer)
40+
41+
- **Mandatory `session_fingerprint: [u8; 32]`** on all three signed
42+
cores (`ConsentRequestCore`, `ConsentResponseCore`,
43+
`ConsentRevocationCore`). Canonical field order pinned in §12.3
44+
and is normative. Derivation specified in §12.3.1:
45+
46+
```
47+
salt = b"xenia-session-fingerprint-v1" (28 bytes ASCII)
48+
ikm = current AEAD session_key (32 bytes)
49+
info = source_id || epoch || request_id_be (8 + 1 + 8 = 17 bytes)
50+
L = 32
51+
HKDF-SHA-256(salt, ikm).expand(info, L) -> fingerprint
52+
```
53+
54+
- **Normative transition table for the consent state machine**
55+
(§12.6.1). Covers every (state, event, `request_id`-predicate)
56+
tuple. Three defined protocol violations surface as
57+
`WireError::ConsentProtocolViolation(ConsentViolation)`:
58+
59+
- `RevocationBeforeApproval { request_id }`
60+
- `ContradictoryResponse { request_id, prior_approved, new_approved }`
61+
- `StaleResponseForUnknownRequest { request_id }`
62+
63+
`ConsentEvent` variants now carry `{ request_id }`.
64+
`Session::observe_consent` returns `Result<ConsentState,
65+
ConsentViolation>`. On violation, state is NOT mutated; the wire
66+
does not tear down the transport.
67+
68+
- **UI-guidance subsection §12.6.2** — "change of mind after
69+
approval" MUST be expressed as a fresh `ConsentRevocation`, never
70+
as a contradictory `ConsentResponse`. This was the contentious
71+
decision during internal review: we considered "later-wins" on
72+
contradictory `ConsentResponse`, rejected it because (a) the
73+
`ConsentResponseCore` body carries no timestamp, and (b) a
74+
captured late-Denied from a prior session could force teardown
75+
on a new Approved one if the fingerprint ever reused across
76+
ceremonies. Rejection aligns with the decision to make
77+
fingerprint mandatory.
78+
79+
- **Security-properties rewrite §12.8**: the "LOOSE binding"
80+
bullet from draft-02r1 becomes "TIGHT binding". New
81+
protocol-violation-detection bullet for the transition table.
82+
83+
### draft-03 hardening (shipped `0.2.0-alpha.2`, no wire or API change)
84+
85+
- **Rekey-aware fingerprint verify.**
86+
`Session::verify_consent_{request,response,revocation}` now
87+
probe **both** the current and (if present) the previous session
88+
keys when comparing the embedded fingerprint. A consent message
89+
AEAD-verified under the previous key during the grace window
90+
now also passes the fingerprint check. §12.3.1 rekey
91+
interaction spells this out.
92+
- **Timing-channel assumption (§12.8)** — new paragraph. Asserts
93+
the verify pipeline (bincode deserialize, Ed25519 verify,
94+
fingerprint compare) MUST NOT branch on secret-dependent bytes.
95+
Reference impl ships an inline constant-time 32-byte compare
96+
(`src/session.rs::ct_eq_32`); alternate-language implementers
97+
are on the hook for auditing bincode-equivalents and Ed25519
98+
libs.
99+
- **Test vectors 10/11/12** for the three `ConsentViolation`
100+
variants (event-sequence format; grammar in
101+
`test-vectors/10_revocation_before_approval.txt`).
102+
- **`cargo-fuzz` target `fuzz_observe_consent`** asserting four
103+
invariants per step: no panic, state always valid, seal-gate
104+
matches state, violations never mutate state.
105+
106+
Not in this review cycle: the paper draft (no crypto changes), the
107+
reference LZ4 path (unchanged since draft-02), the replay window
108+
parameterization (mechanical generalization of the draft-02 fixed
109+
64).
110+
111+
---
112+
113+
## 2. Questions ranked by weight
114+
115+
### 2.1 `session_fingerprint` `info` composition (§12.3.1)
116+
117+
**The question.** Is `info = source_id || epoch || request_id_be`
118+
the right input set?
119+
120+
**Alternatives considered:**
121+
122+
| Variant | Property |
123+
|---|---|
124+
| `info = []` | Session-key-only binding. Per-ceremony replay opens up inside a single session. |
125+
| `source_id ‖ epoch` | Session-identity binding. A `ConsentResponse` signed for `request_id=7` is replayable as one for `request_id=8` — undesirable. |
126+
| **`source_id ‖ epoch ‖ request_id_be` (chosen)** | Per-ceremony binding. What's shipped. |
127+
| `source_id ‖ epoch ‖ request_id_be ‖ pld_type` | Per-message-type per-ceremony binding. Would give a distinct fingerprint per message kind. Avoids a hypothetical attack where a captured Response is replayed as a Revocation; we don't currently see that attack, but it's not impossible. |
128+
129+
**Specific ask.** Is the chosen set sufficient for a signed-consent
130+
replay threat model, or should `pld_type` be mixed into `info`?
131+
132+
### 2.2 Big-endian `request_id` in `info`
133+
134+
**The question.** SPEC §12.3.1 specifies `request_id_be` — the u64
135+
in big-endian. Every other integer on the Xenia wire is
136+
little-endian (per bincode v1 default; nonce sequence bytes are
137+
explicitly LE per §3). The mixed convention is intentional — we
138+
wanted the `info` byte-order to differ from the ambient wire so
139+
that an implementation reaching for a "byte-encode this u64 the
140+
usual way" path would produce the wrong fingerprint and fail
141+
loudly rather than silently.
142+
143+
**Specific ask.** Is this mistake-reduction smart, or is it a
144+
footgun that buys nothing because the endianness is already
145+
specified normatively either way?
146+
147+
### 2.3 Timing side-channel in `verify_fingerprint_either_epoch`
148+
149+
**The question.** On the receive path, `verify_consent_*` derives
150+
the fingerprint against the **current** key, compares, and — only
151+
if that fails — derives again against the **previous** key.
152+
That's one HKDF call on match, two on mismatch. Relevant source:
153+
`src/session.rs::verify_fingerprint_either_epoch` (around line
154+
340-360 in the 0.2.0-alpha.2 tree).
155+
156+
An attacker observing verify-path timing could distinguish
157+
"accepted under current key" from "accepted under prev key" from
158+
"rejected". The latency delta is ~one HKDF-SHA-256 derivation
159+
(~a few microseconds on commodity hardware). Is this exploitable?
160+
161+
**Mitigation considered.** Always derive both fingerprints
162+
regardless of first-match outcome; compare each; OR the
163+
constant-time compare results. Constant-time verify path at the
164+
cost of one extra HKDF call per verify. Open to your opinion on
165+
whether this is worth the complexity.
166+
167+
### 2.4 Transition-table completeness (§12.6.1)
168+
169+
**The question.** The table enumerates (state × event ×
170+
`request_id`-predicate). One corner case I want to probe:
171+
`LegacyBypass` + any event → sticky no-op, including a perfectly
172+
valid `ConsentRequest` whose fingerprint matches a local
173+
derivation.
174+
175+
Is sticky the right rule, or should there be a documented
176+
"upgrade from LegacyBypass to ceremony mode" path for deployments
177+
that start permissive and later want to enforce? Currently the
178+
only upgrade path is constructing a new `Session` via
179+
`SessionBuilder::require_consent(true)`.
180+
181+
### 2.5 `ConsentResponse` / `ConsentRevocation` timestamp asymmetry
182+
183+
**The question.** `ConsentRevocationCore` has `issued_at: u64`
184+
(Unix epoch seconds). `ConsentResponseCore` does NOT. This was
185+
intentional in draft-02 and carried forward; we want to confirm
186+
it's still OK given the draft-03 `session_fingerprint`.
187+
188+
Argument for leaving the asymmetry: within a single ceremony, the
189+
state machine forbids a replayed Response (idempotent same-value
190+
Response is a no-op; differing-value is `ContradictoryResponse`
191+
hard-error). Across ceremonies, the fingerprint's `request_id_be`
192+
distinguishes them. So no replay window exists.
193+
194+
Argument for adding `issued_at` to ConsentResponse anyway: the
195+
audit-trail use case (§12.8 third-party-verifiable signed consent)
196+
benefits from knowing *when* the user signed the approval, not
197+
just *that* they did. Currently a replayed (same-value)
198+
`ConsentResponse` would be indistinguishable from the original in
199+
a frozen transcript.
200+
201+
### 2.6 General coherence of §12.8 security properties + §12.10 out-of-scope
202+
203+
**The question.** §12.8 now enumerates: third-party-verifiable
204+
signed consent, TIGHT session binding, protocol-violation
205+
detection, no human-identity binding, and the timing-channel
206+
assumption. §12.10 lists the explicit out-of-scope items (coerced
207+
consent, human-identity fraud, clock-skew attacks).
208+
209+
Is there a property draft-03 implicitly offers that §12.8 should
210+
claim explicitly? Or a threat draft-03 implicitly tolerates that
211+
§12.10 should flag? Open-ended reality-check question.
212+
213+
---
214+
215+
## 3. Pointers
216+
217+
- **SPEC**: `SPEC.md` at tag `v0.2.0-alpha.2` on
218+
`Luminous-Dynamics/xenia-wire`. §12.3, §12.3.1, §12.6.1, §12.6.2,
219+
§12.8 are the draft-03 deltas. Appendix B row for draft-03
220+
records the changelog.
221+
- **Reference implementation**: `src/consent.rs` (all three Core
222+
types + ConsentViolation + ConsentEvent), `src/session.rs`
223+
(`session_fingerprint`, `session_fingerprint_from_key`,
224+
`verify_fingerprint_either_epoch`, `observe_consent` with the
225+
draft-03 transition table, `ct_eq_32`), `src/error.rs`
226+
(`WireError::ConsentProtocolViolation`).
227+
- **Test vectors**: `test-vectors/07_consent_request.*`,
228+
`08_consent_response.*`, `09_consent_revocation.*` for the
229+
happy-path signed bodies; `10_revocation_before_approval.txt`,
230+
`11_contradictory_response.txt`, `12_stale_response.txt` for the
231+
violation event-sequence DSL (grammar documented in file 10).
232+
- **Fuzz target**: `fuzz/fuzz_targets/fuzz_observe_consent.rs`
233+
with the four invariants.
234+
- **Migration**: `MIGRATION.md` (0.1.x → 0.2.0-alpha.1, plus
235+
alpha.1 → alpha.2). Paired with `CHANGELOG.md` per-release
236+
entries.
237+
238+
## 4. Feedback logistics
239+
240+
Any form works. If you prefer structured feedback: a per-section
241+
reply keyed to the six questions above is the highest-bandwidth
242+
format. If you'd rather leave comments inline in a PR against the
243+
`SPEC.md` tree, that's also fine — we'll open a draft `spec-review`
244+
PR on request.
245+
246+
Thank you for taking the time. Rounds 1 and 2 caught real issues;
247+
this round is targeted at confirming the close-out rather than a
248+
full re-read.

0 commit comments

Comments
 (0)