From 5b64fe14b3f9303c1b12d4aea68c58f710ef003a Mon Sep 17 00:00:00 2001 From: vjrj Date: Wed, 15 Jul 2026 20:27:45 +0200 Subject: [PATCH] docs: de-link spike findings from spike/ code (moved to private repo) --- docs/design/spike-block2-findings.md | 48 +++++++++++++++------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/docs/design/spike-block2-findings.md b/docs/design/spike-block2-findings.md index dbd8ec2..1102749 100644 --- a/docs/design/spike-block2-findings.md +++ b/docs/design/spike-block2-findings.md @@ -1,9 +1,9 @@ # Spike — Block 2 (social layer) de-risking findings *Throwaway research spike. Language: English (findings), per code convention. -Code lives in [`spike/block2_spike/`](../../spike/block2_spike/) and is marked -throwaway — **not production, not on the pub workspace, no deps added to Block -1**. This document is the deliverable: what we learned, the risks, and a +The throwaway spike code (`spike/block2_spike/`) has been moved out of this +public repo and preserved privately — it was **not production, not on the pub +workspace, no deps added to Block 1**. This document is the deliverable: what we learned, the risks, and a recommendation on whether/how to take on the social round (Phase 3).* > **Status: this is a spike, not a green light to build Block 2.** CLAUDE.md and @@ -34,7 +34,7 @@ See "How to run" at the end. **Result: works, deterministic, one-way. This unblocks "one identity, one backup".** -- Implemented [`NostrKey.deriveFromSeed`](../../spike/block2_spike/lib/src/nostr_key.dart): +- Implemented `NostrKey.deriveFromSeed`: `HKDF-SHA256(rootSeed, info: "org.comunes.tane/nostr/secp256k1/v1")` → reduce to a valid secp256k1 scalar `d ∈ [1, n-1]` by rejection (bump a counter in the HKDF info on the astronomically-rare miss, so the result stays a pure function @@ -80,13 +80,13 @@ trust do NOT fit behind the same interface — they share a *connection*, not a *contract*.** ### The seam works (offers) -- [`OfferTransport`](../../spike/block2_spike/lib/src/offer_transport.dart) = +- `OfferTransport` = `publish(Offer)` / `discover(DiscoveryQuery)` / `retract(id)`. The Nostr - NIP-99 backend ([`NostrOfferTransport`](../../spike/block2_spike/lib/src/nostr_offer_transport.dart) - + [`Nip99Codec`](../../spike/block2_spike/lib/src/nip99.dart)) sits entirely + NIP-99 backend (`NostrOfferTransport` + + `Nip99Codec`) sits entirely behind it. A second backend (ActivityPub/FEP-0837) could replace it without the domain noticing. -- [`Offer`](../../spike/block2_spike/lib/src/offer.dart) is agnostic by +- `Offer` is agnostic by construction: a chosen *summary* + coarse geohash, **no FK into seed tables, no full inventory, no exact address** — exactly the Offer↔Lot split [sharing-model.md](sharing-model.md) §2 and @@ -114,14 +114,14 @@ manageable**: private encrypted DM vs. assert/read a signed certification. Forcing DMs and certifications behind `OfferTransport.publish/discover` would overload it. - **Recommendation — now demonstrated in code, not just asserted:** - [`NostrConnection`](../../spike/block2_spike/lib/src/nostr_connection.dart) is + `NostrConnection` is **one shared** socket + key + sign + REQ/EOSE lifecycle, and **all three thin interfaces sit on top of the same instance** — - [`OfferTransport`](../../spike/block2_spike/lib/src/nostr_offer_transport.dart) + `OfferTransport` (NIP-99), - [`MessageTransport`](../../spike/block2_spike/lib/src/nostr_message_transport.dart) + `MessageTransport` (NIP-17) and - [`TrustTransport`](../../spike/block2_spike/lib/src/nostr_trust_transport.dart) + `TrustTransport` (custom WoT). The trust test even runs offer discovery and trust annotation over one connection. This is the shape to lift into `commons_core`: shared connection, per-concern contract. Trying to make `OfferTransport` also carry @@ -139,7 +139,7 @@ that whole happy path now runs — what's left is production hardening (below). **Result: the flow works end to end. The real risk is NIP maturity/ecosystem, not the mechanics.** -- Built a hermetic in-process [`MiniRelay`](../../spike/block2_spike/lib/src/mini_relay.dart) +- Built a hermetic in-process `MiniRelay` (NIP-01 subset: EVENT/REQ/EOSE/CLOSE, filter by `kinds`/`authors`/`#g`, addressable-event replacement for kind 30402). No network → CI-safe, no flaky public-relay dependency. @@ -183,13 +183,13 @@ The plan ([network-trust.md](network-trust.md) §4) calls messaging "grande" and [open-decisions.md](open-decisions.md) §D.3 singled it out as the tightest coupling to Nostr. So the spike built it rather than hand-waving: -- **NIP-44 v2 encryption** ([`Nip44`](../../spike/block2_spike/lib/src/nip44.dart)): +- **NIP-44 v2 encryption** (`Nip44`): secp256k1 ECDH → HKDF conversation key → ChaCha20 + HMAC-SHA256 with length-hiding padding. Conversation key is symmetric (A→B == B→A, tested); a wrong key cannot decrypt (throws, tested); short messages pad to the same bucket so ciphertext length doesn't leak plaintext length (tested). - **NIP-17 / NIP-59 gift-wrap onion** - ([`NostrMessageTransport`](../../spike/block2_spike/lib/src/nostr_message_transport.dart)): + (`NostrMessageTransport`): rumor (kind 14, unsigned) → seal (kind 13, signed by sender, NIP-44 to recipient) → gift wrap (kind 1059, signed by a **throwaway ephemeral key**, NIP-44 to recipient). @@ -223,12 +223,12 @@ There is no settled NIP for a web of trust, so — exactly as anticipates — the spike models **its own WoT, Duniter-compatible**, over Nostr: - **Certifications as events** - ([`NostrTrustTransport`](../../spike/block2_spike/lib/src/nostr_trust_transport.dart)): + (`NostrTrustTransport`): a custom addressable kind (30777) keyed by (issuer, `d`=subject), so one live "A vouches for B" per pair; re-certifying renews, revoking replaces (tested). Certifications expire (Ğ1 semantics) and are public (like the on-chain Ğ1 WoT). - **The membership rule is pure and separately tested** - ([`WebOfTrust`](../../spike/block2_spike/lib/src/web_of_trust.dart)) — the + (`WebOfTrust`) — the `commons_core`-worthy piece: Duniter's two rules, **N certifications from existing members** (sigQty) and **within distance D of the bootstrap referents** (stepMax), iterated to a fixpoint (becoming a member can push @@ -263,8 +263,9 @@ entity, and optionally importing the on-chain Ğ1 WoT (level 3) as a trust sourc ([open-decisions.md](open-decisions.md) §D.1, §D.6). That is what should drive the Phase-3 estimate and the funding ask. 4. **Do not start Block 2 from this spike.** It is throwaway research code (not - vector-verified, no offline delivery, no persistence, single relay). Delete - `spike/block2_spike/` once these findings are absorbed. The funded social + vector-verified, no offline delivery, no persistence, single relay). The + `spike/block2_spike/` code has been moved out of this public repo (preserved + privately) now these findings are captured. The funded social round should **rebuild on vetted client libraries in `commons_core`**, using the shapes proven here — one `NostrConnection`, three interfaces, the pure `WebOfTrust` rule — and spend its risk budget on hardening and bootstrap, not @@ -283,11 +284,12 @@ entity, and optionally importing the on-chain Ğ1 WoT (level 3) as a trust sourc ## How to run +The throwaway spike code has been moved out of this public repo and is preserved +privately; nothing here was wired into `app_seeds` or `commons_core`'s production +graph, and the Block 1 suite is untouched. For the record, it ran as: + ```sh -cd spike/block2_spike +cd spike/block2_spike # in the private archive dart pub get dart test # derivation · privacy · roundtrip · messaging · trust · web_of_trust (30 tests) ``` - -Nothing here is wired into `app_seeds` or `commons_core`'s production graph; the -Block 1 suite is untouched.