History

DeDi Node — Design Document

History. This is the original plan, written in July 2026 before most of the node existed. It is kept because its reasoning still explains the code, but it describes intentions, not the node you are running. Where it and the other pages disagree, they are right. In particular, none of these were built as described here:

  • A separate publisher listener on :8443 with mTLS. Writes are served on the same listener as reads, under /admin, gated by HTTP Basic auth and an Ed25519 signature per request (API).
  • A dedi CLI (publish, get --verify, watch, keys rotate, witness). The binary is dedid, with keygen, pubkey, pubkeygen, sign, seed and serve (configuration).
  • registrar-svc and the Beckn subscribe/on_subscribe state machine. Onboarding is an operator decision, made in the console or a signed script (onboarding).
  • Witness cosigning via C2SP tlog-witness. A witness records its verdict in its own log instead; nothing co-signs this node's checkpoints (witnessing).
  • Publisher keys as a <ns>/_keys registry. They are configured in DEDI_PUBLISHER_KEYS.
  • Records keyed by subscriber_id. Beckn records are named by key id (onboarding).
  • A webhook outbox table. Delivery is a replicated cursor over the log (push).
  • "No federation beyond witnessing." Replication, delegation and a crawler now exist (architecture).

Status: Draft v0.3 (v0.2 + Addendum D: ledger-anchor plane revises the v1 non-goal) Author: Chakshu (FWAI Technologies / Flywheel) Date: July 2026 License: Apache-2.0


1. Summary

An independent, self-hostable, open-source implementation of the Decentralized Directory (DeDi) protocol — a registry node that lets any organization publish tamper-evident, provenance-enabled public directories behind the standard DeDi API, without depending on the hosted dedi.global platform or its CORD blockchain substrate.

The core architectural bet: DeDi's required trust properties (tamper-resistance, provenance, version history, offline verifiability) are fully satisfied by a transparency-log architecture — a Merkle tree over an append-only record log with signed checkpoints and external witnessing — at a fraction of the operational weight of a blockchain-anchored deployment.

A first-class compatibility target is Beckn: ONIX v0.6.0 onboards networks against DeDi as the Beckn One registry, making a self-hostable DeDi node the missing piece for anyone running a sovereign Beckn network. "The first self-hostable Beckn One-compatible registry" is the opening claim; "an independent DeDi implementation" is the general one.

2. Context and gap

Opportunity: an independent implementation + upstream conformance suite + Beckn drop-in compatibility.

3. Goals

  1. Spec conformance. Implement the DeDi lookup/query/versions API per the LFDT OpenAPI specs.
  2. Self-hostability. Single Go binary + Postgres. docker compose up to a working node. No blockchain dependency.
  3. Verifiability. Every record lookup can return an inclusion proof against a signed checkpoint; relying parties verify offline. Consistency proofs between checkpoints make history rewriting detectable.
  4. Beckn drop-in. The ONIX dediregistry plugin works against this node by changing only the registry URL. Starter-kit network passes end-to-end.
  5. Conformance suite as a separate, upstreamable package. Contributed to the LFDT lab independent of this implementation.

Non-goals (v1)

4. Architecture overview

                        ┌─────────────────────────────────────────┐
                        │                 dedid                   │
   Public read plane    │  ┌───────────┐   ┌──────────────────┐   │
  (unauthenticated,     │  │ Lookup /  │   │  Transparency    │   │
   CDN-cacheable)  ────►│  │ Query API │◄──│  Log (tlog,      │   │
                        │  └───────────┘   │  Merkle)         │   │
                        │  ┌───────────┐   └──────────────────┘   │
   Publisher plane      │  │ Publish / │            │             │
  (authenticated,  ────►│  │ Admin API │            ▼             │
   separate listener)   │  └───────────┘   ┌──────────────────┐   │
                        │        │         │  Checkpoint      │   │
                        │        ▼         │  signer (Ed25519)│   │
                        │  ┌───────────┐   └────────┬─────────┘   │
                        │  │ Postgres  │            │             │
                        │  │ (append-  │            ▼             │
                        │  │  only)    │      external witnesses  │
                        │  └───────────┘   (other nodes, CT-style │
                        └────────────────── witness, dedi.global) ┘

   Layered services (separate deployables, pure API clients of dedid):
   - registrar-svc  : Beckn subscriber lifecycle state machine
   - explorer UI    : public directory browser + in-browser proof verification
   - console UI     : publisher/registrar admin
   - dedi CLI       : publish, verify, monitor

4.1 Storage model

Postgres, strictly append-only. A record "update" is a new version row; nothing is ever mutated or deleted. Raw signed payload bytes are the source of truth (payload_raw BYTEA); a derived JSONB column exists only to serve queries (see Addendum A, finding 2). Leaf indices are assigned by a serialized writer under an advisory lock — never by a Postgres sequence (Addendum A, finding 1).

Properties that fall out of the schema:

4.2 Tamper-evidence: the transparency log

Why not a blockchain: the threat model for a public directory is a malicious or compromised operator rewriting history, not Byzantine consensus among mutually distrusting writers. A single-writer log + external witnesses addresses exactly that threat, keeps ops to "a Go binary and Postgres," and preserves offline verifiability for relying parties. CORD anchoring remains compatible — it is simply one more witness.

4.3 Two planes

Read plane Publisher plane
Auth None (public data) Publisher keys (detached JWS on writes) + operator RBAC
Surface DeDi lookup/query/versions, proofs, checkpoints namespace/registry CRUD, record publish, key registration/rotation
Listener :8080, CDN in front :8443, private network / mTLS optional
Scaling Read replicas + cache; effectively static Single writer, low volume

The node identity key signs checkpoints only — read-plane responses are not individually signed; TLS covers transport and proofs cover content (Addendum A, finding 6).

4.4 Keys

5. API surface

5.1 DeDi conformance (read plane)

Implement api/openapi.yaml from the LFDT lab (v2.0.0) verbatim — see Addendum B for the teardown. Surface: GET /dedi/lookup/{ns}[/{registry}[/{record}]] (with version_id, as_on), GET /dedi/query/{ns}[/{registry}] (name/date/state filters, sort, pagination), GET /dedi/versions/{ns}[/{registry}[/{record}]]. Responses use the {message, data} envelope; errors use {message, error, code}. Extensions are additive and namespaced (?proof=, /dedi/log/*) so a spec-only client sees a conformant node.

Open question (tracked as risk R1): the ONIX dediregistry plugin was recently migrated to a "new wrapper API," implying the de facto wire contract is what dedi.global serves, which may drift from the LFDT spec. Action: diff the plugin's client code against the spec; where they diverge, serve both shapes behind content negotiation or a compat prefix, and file the divergence upstream.

5.2 Query (Beckn discovery path)

The spec's query endpoint filters by name/date/state only. Attribute filtering over payload fields (role, domain, city/coverage, status) — required for gateway discovery — is a namespaced extension backed by the JSONB GIN index (containment only, keyset-paginated by seq). Exact-key lookup stays on the GET path and is the latency-critical route.

5.3 Publisher plane (M2)

5.4 Freshness / push (pulled forward for Beckn)

6. Beckn compatibility layer

Everything Beckn-specific lives above the generic node.

6.1 What ONIX needs from the registry

Participants register (subscriber_id, role ∈ {BAP, BPP, BG}, signing public key (Ed25519), encryption public key (X25519), callback URI, domain(s), coverage, status, validity window). Two keys, not one (Addendum A, finding 3). The adapter performs (a) key lookup by participant ID on every inbound message for Ed25519 signature validation, and (b) routing resolution of callback URLs. Gateways additionally query by role/domain/city.

Mapping: one namespace per network, a participants registry with the Beckn subscriber JSON Schema (the LFDT lab ships schemas/Beckn_subscriber.json), records keyed by subscriber_id.

6.2 registrar-svc (subscriber lifecycle)

A separate deployable implementing the Beckn onboarding state machine, using only the public publisher API:

REGISTERED ──challenge sent──► UNDER_VERIFICATION ──on_subscribe OK──► INITIATED
INITIATED ──registrar approval──► SUBSCRIBED
SUBSCRIBED ──expiry/revocation──► EXPIRED / REVOKED   (new record versions)

6.3 Acceptance test

Run the Beckn starter kit's docker-compose network; point both ONIX adapters' registry URL at dedid + registrar-svc instead of fabric.nfh.global; the discover → select → init → confirm flow must pass end-to-end with signature validation and dynamic routing, with zero adapter changes.

7. Frontends and tooling

All pure API clients — zero privileged paths.

  1. Explorer (v1): static SPA embedded via go:embed; browse + in-browser inclusion-proof verification (WebCrypto Ed25519, with pure-JS fallback).
  2. dedi CLI (v1): publish, get --verify, watch, keys rotate, witness.
  3. Console (v2): publisher/registrar admin.

8. Conformance suite (upstream contribution)

A standalone repo/package: black-box HTTP tests runnable against any DeDi endpoint (conformance run --target <url>), generated from the LFDT OpenAPI specs plus semantic tests the schema can't express (version monotonicity, history immutability, pagination stability). Contributed to the LFDT lab first, before the implementation is announced. Run in CI against dedid and (read-only) against dedi.global; divergences become upstream issues.

9. Technology choices

Concern Choice Rationale
Language Go 1.24+ Team fluency; single static binary; tlog ecosystem
HTTP stdlib net/http (1.22+ pattern mux) Boring, zero deps
DB access pgx v5, hand-written queries Typed enough at this query count; sqlc when surface stabilizes
Merkle log golang.org/x/mod/sumdb/tlog + sumdb/note, C2SP checkpoint format Battle-tested; witness-ecosystem interoperable
Crypto stdlib Ed25519 via sumdb/note Minimal surface
Frontend Static SPA, go:embed Single-binary deploys
Testing Integration tests against real Postgres; reference-implementation cross-checks for log math Log correctness is the crown jewel
Packaging Docker Compose (node+Postgres), Helm later Self-host first

10. Milestones

# Deliverable Exit criterion
M0 Spec teardown + conformance suite skeleton Suite runs against dedi.global; divergence report filed upstream
M1 Core node: append-only store, tlog, checkpoints, read API Read API green; inclusion/consistency proofs verified offline
M2 Publisher plane + CLI + explorer Publish→lookup→verify round-trip via CLI and browser
M3 Beckn layer: attribute query, temporal keys, webhooks, registrar-svc Starter-kit network passes E2E against dedid (§6.3)
M4 Witness cosigning + console UI Two-node witness demo; checkpoint divergence detected and alarmed
M5 Public release Announce with conformance suite upstreamed + Beckn drop-in demo

11. Risks

12. Open questions

  1. Exact endpoint shapes — resolved, see Addendum B.
  2. registrar-svc in-repo vs separate — leaning monorepo until interfaces stabilize.
  3. Checkpoint cadence and witness policy defaults (v1 default: 30s / on-demand for proofs; freshness bound recommendation 5 min).
  4. Publish node to the LFDT lab vs keep under Flywheel — decide after M0 reception.
  5. One global log vs per-namespace logs — v1 ships a single global log; this is a one-way door for namespace portability (a namespace cannot later migrate to another node with proofs intact). Revisit before M5.

Addendum A — Design review findings (2026-07-15)

Accepted as v0.2 changes:

  1. Dense leaf indices. Postgres IDENTITY/sequences leave gaps and commit out of order — unusable as Merkle leaf indices. The write path is one serialized transaction holding an advisory lock, assigning seq = max(seq)+1.
  2. Raw bytes are the truth. JSONB normalizes (key order, duplicates, numbers), breaking digest round-trips. Store payload_raw BYTEA (compacted JSON) as the hashed/served source of truth; JSONB is a derived query index. Serve details via json.RawMessage so bytes survive to the client.
  3. Beckn dual keys. Subscribers carry a signing key (Ed25519) and an encryption key (X25519); the on_subscribe challenge is ECDH-derived AES, so registrar-svc needs its own X25519 pair.
  4. Data-protection policy. Append-only forever ⇒ v1 policy: no personal data requiring erasure in payloads; crypto-shredding as future escape hatch.
  5. Evaluate Tessera (transparency-dev/tessera) before hand-rolling tile storage.
  6. No read-plane response signing. Checkpoints are the only thing the node key signs; TLS + proofs cover the rest.
  7. Checkpoint freshness bound. Cadence default 30s; high-assurance clients reject checkpoints older than 5 min (revocation propagation bound).
  8. Keyset pagination by seq internally for stability (spec pagination is page-numbered; ordering must be deterministic).
  9. Global vs per-namespace log recorded as open question 5.

Addendum B — LFDT spec teardown notes (M0, 2026-07-15)

From api/openapi.yaml (DeDi API v2.0.0) at LF-Decentralized-Trust-labs/decentralized-directory-protocol@main:

Addendum C — ONIX dediregistry wire contract (M3 teardown, 2026-07-16)

Extracted from beckn-onix@e99b8c1 (v1.8.0). Supersedes §6.1's assumptions; resolves risk R1.

Trust invariant (binding on M2): the Beckn wildcard collapses the node's entire write surface into a single identity trust domain — any principal who can publish a record named {key_id} anywhere on the node can answer GET /lookup/{any_subscriber_id}/subscribers.beckn.one/{key_id}, and the ONIX client does not verify that the returned subscriber_id matches what it asked for. This is safe today because the only write paths are operator-controlled (dedid seed, direct DB). The M2 publisher plane MUST NOT ship without a wildcard eligibility constraint — e.g., only allowlisted namespaces/registries participate in wildcard resolution, or subscriber_id claims are verified at publish time. The namespace-preference ordering in FindBecknSubscriber is a routing heuristic, not a security mechanism.


Addendum D — Ledger-anchor plane (2026-07-22)

Revises the v1 non-goal "CORD/blockchain anchoring". The trust tiers are now deployment modes of one binary, toggled like feature flags (see docs/deployment-modes.md): standalone → witnessed (DEDI_WITNESS_*) → anchored (DEDI_ANCHOR_*). The anchor plane publishes the node's signed C2SP checkpoint to an external ledger on a cadence, adding an operator-independent ordered timeline of roots beside the witness plane. Decisions:

← All documentation