Kavryx Dev
← All products
Financial SystemsPROTOTYPE

Cryptographic Records Registry

Tamper-evident public records registry where every correction is appended and provable instead of silently overwritten.

At a glance

  • Append-only record history with cryptographic chaining
  • Provable corrections instead of silent overwrites
  • Document storage in MinIO/S3 alongside the chain
  • PostgreSQL-backed registry with audit queries
  • Verification path for third parties

From the repository

README

A government-grade civic ledger where land titles, permits and vital certificates are append-only by database trigger, anchored into RFC 6962 Merkle trees, signed by an Ed25519 registry authority — and provably verifiable by any citizen, offline, with zero trust in the registry.

Features • Architecture • Workflow • Tech Stack • Quick Start • API • Verifying a Document


Public registries fail in the quiet places: a deed "corrected" after a dispute, a permit that appears in one archive and not another, a record whose history cannot be proven because the database admin could always edit the table. This ledger makes that class of tampering cryptographically visible. Every registered document becomes a leaf in a domain-separated Merkle tree; each epoch's root is sealed with the registry's Ed25519 key; and anyone holding the original document plus a small JSON proof can independently recompute the root — no database, no network, no trust required.

✨ Core Features

  • 🗄️ True append-only storage — UPDATE, DELETE and TRUNCATE on records raise MUTATION_FORBIDDEN at the PostgreSQL engine level, and anchored Merkle roots are frozen. Amending a record means issuing a superseding registration that links to the predecessor's hash — never editing history.
  • 🌳 RFC 6962 Merkle anchoring — domain-separated hashing (0x00 leaf / 0x01 node) with length-prefixed canonical preimages blocks second-preimage and preimage-collision attacks. Audit paths are O(log N).
  • 🔏 Ed25519-signed batch roots — every sealed epoch publishes a Merkle root signed over a domain-separated envelope, so a root signature can never be replayed as a signature over any other message.
  • 🧍 Zero-knowledge citizen verification — a citizen verifies their own deed with only: the raw file, an audit path, the signed root, and the authority public key. The verification algorithm reads nothing from the registry and is byte-identical in the server, the CLI, and any reimplementation.
  • 📦 Content-addressed archival — physical documents live in S3/MinIO keyed by urn:sha256:<digest>; every retrieval re-hashes the bytes and rejects drift between key and content.
  • 🕐 Epoch auto-anchoring — a daemon seals batches by size (default 1,000 leaves) or interval, with lone partial epochs always sealing so records never sit unanchored.

🏗️ System Architecture

System architecture — registry trust boundary, append-only ledger, Merkle anchoring and the offline citizen verification zone

🔍 Interactive version (zoom, lens, path tracing, theme switch, PNG/SVG export): docs/architecture.html — generated with Archify and passing its schema, provenance and real-browser readability gates.

Component map

ComponentTrust zoneResponsibilitySource
Registry APIRegistrychi HTTP surface: registration intake, proof issuance, public verify endpointcmd/registry_server · handlers.go
Document ArchiveRegistryS3/MinIO object store keyed by urn:sha256:<digest>; verifies digest on reads3_archiver.go
Public Records LedgerRegistryPostgreSQL 16 public_records: one immutable row per registrationrecord_repo.go
Epoch BatchesRegistryregistry_epochs lifecycle OPEN → ANCHORED, leaf counters, audit logepoch_repo.go
RFC 6962 Crypto CoreRegistryDomain-separated hashing, binary Merkle tree, audit-path generatorhasher.go · tree.go · proof_generator.go
Epoch AnchorerRegistryDaemon that reads an epoch's leaves, builds the tree, signs the root and commits ANCHORED atomicallycmd/epoch_anchorer · sealer.go
Authority Key🔐 PrivateEd25519 keypair (PKCS#8 PEM, 0600) — the signing trust anchorsigner.go · cmd/keygen
Offline Verifier🌍 PublicRecomputes document → leaf → root from raw bytes and compares in constant timeverifier.go
Citizen CLI🌍 PublicStandalone verification tool — no network, no DB, no credentialscmd/verify_cli

Trust boundaries — read them carefully

  1. Inside the registry boundary: the API, the object store, both PostgreSQL tables and the crypto core. The registry operates these but cannot rewrite history: the append-only and anchored-root triggers block mutation, and the audit trail is itself append-only.
  2. The private key never leaves its box. Only the anchorer daemon reads it, and only to sign Merkle-root envelopes. It is stored as 0600-mode PKCS#8 PEM and git-ignored.
  3. Crossing outward is deliberately minimal: an audit proof and the hex public key. The citizen zone holds no database access and no registry credentials — compromise of a citizen's machine leaks nothing; the registry can see citizens verifying nothing (the offline check never calls home).

🔄 Document Lifecycle

Sequence diagram — registration, epoch anchoring with Ed25519 root signing, and offline citizen verification

🔍 Interactive sequence with path tracing: docs/verification-sequence.html

  1. A clerk submits POST /api/v1/records (multipart: document file + record_type + canonical JSON metadata).
  2. The API archives the raw bytes under PUT urn:sha256:<digest> before any row exists — identity in the store is the content itself.
  3. In a single SERIALIZABLE transaction the API locks the open epoch row FOR UPDATE, computes the RFC 6962 leaf hash, and INSERTs the record — concurrent clerks contend on that lock and retry with jittered backoff, so leaf indices never collide.
  4. On a timer (or batch threshold), the Epoch Anchorer reads leaf_hash ORDER BY leaf_index — the exact, deterministic list the root must cover.
  5. The tree is built with 0x01-prefixed interior hashing (odd trailing nodes promote unpaired); the root is signed with the Ed25519 authority key over a domain-separated envelope.
  6. Root + signature commit atomically to registry_epochs as ANCHORED, with an entry in the append-only epoch_audit_log. The root is now frozen by trigger.
  7. A citizen fetches GET /api/v1/records/{id}/proof — an O(log N) audit path plus the signed root and public key.
  8. The citizen runs verify_cli (or the public POST /verify): signature first (the only trust anchor), then document digest from raw bytes, then leaf hash, then audit-path replay, then a constant-time root compare.
  9. Match → VERIFIED. One flipped byte anywhere → PROOF_REJECTED. No database query is ever made.

🧱 Tech Stack

LayerTechnologyRole
LanguageGo 1.27Single typed codebase: server, daemon, CLI, crypto
HTTPgo-chi/chi/v5Routing, request IDs, timeouts, panic recovery
DatabasePostgreSQL 16Append-only ledger + epoch batches; PL/pgSQL immutability triggers
Object storeMinIO / S3-compatible (minio-go/v7)Content-addressed document archive
Cryptographystdlib crypto/sha256, crypto/ed25519RFC 6962 domain separation; Ed25519 root signatures; constant-time compare
Testingstretchr/testify, go test -race58 unit + integration + E2E civic-audit tests
OpsDocker Compose, MakeOne-command local stack

📁 Project Structure

.
├── cmd/
│   ├── registry_server/      # API gateway: intake, proofs, public verify
│   ├── epoch_anchorer/       # Batch-sealing daemon (Merkle root + Ed25519)
│   ├── keygen/               # Authority keypair generator (PKCS#8 PEM)
│   └── verify_cli/           # Standalone offline citizen verifier
├── internal/
│   ├── domain/               # PublicRecord, EpochBatch, canonical JSON
│   ├── crypto/               # hasher (RFC 6962) · signer (Ed25519) · verifier (offline)
│   ├── merkle/               # Tree construction · O(log N) audit-path generator
│   ├── anchoring/            # Epoch sealer orchestration
│   ├── storage/              # S3/MinIO content-addressed archiver
│   ├── repository/           # Append-only records · epochs · SERIALIZABLE tx pool
│   ├── registryerr/          # Sentinel errors (EPOCH_CLOSED, NOT_FOUND, …)
│   └── transport/http/       # Handlers + router
├── migrations/               # 000001_init_registry_schema.up.sql (triggers, checks, indexes)
├── tests/                    # merkle · inclusion-proof · immutability · e2e civic audit
├── docs/                     # 📊 Archify interactive HTML diagrams
│   └── assets/               # Static diagram exports (light + dark)
├── .archify/                 # Diagram source-of-truth JSON (regenerate docs/ from here)
├── docker-compose.yml        # PostgreSQL 16 + S3-compatible store
├── Makefile                  # up · down · migrate · keypair · build · test · clean
└── govtech_cryptographic_registry_autonomous_blueprint.md   # Original spec

🚀 Quick Start

Prerequisites: Go ≥ 1.27 (go.mod pins go 1.27.0), Docker, and (for make) a POSIX shell — Git Bash / WSL on Windows, or call the go commands directly as shown below.

# 1 — Clone
git clone https://github.com/<your-username>/cryptographic-public-records-ledger.git
cd cryptographic-public-records-ledger

# 2 — Bring up the data plane (PostgreSQL 16 on :15432, S3 store on :9010)
docker compose up -d

# 3 — Generate the registry authority keypair (.keys/ is git-ignored)
go run ./cmd/keygen
# or:  make keypair

# 4 — Start the registry API (reads defaults; no env needed for local dev)
go run ./cmd/registry_server

# 5 — In a second terminal: seal epochs on a timer, or --once style
EPOCH_BATCH_SIZE=10 go run ./cmd/epoch_anchorer   # PowerShell: $env:EPOCH_BATCH_SIZE=10; go run ...

Register a land title and anchor it end-to-end:

# Upload a document (multipart: file + type + canonical metadata JSON)
curl -sS -X POST http://localhost:8080/api/v1/records \
  -F "document=@deed.pdf" \
  -F "record_type=LAND_TITLE" \
  -F 'metadata={"parcel_id":"PL-7742","parties":["A. Owner"],"jurisdiction":"Metro County"}'
# → {"record_id":"<uuid>","epoch_id":1,"leaf_index":0,"leaf_hash":"...","status":"REGISTERED_PENDING_ANCHOR"}

# Seal the epoch (one pass, seal whatever is pending)
EPOCH_RUN_ONCE=true EPOCH_BATCH_SIZE=0 go run ./cmd/epoch_anchorer

# Fetch the inclusion proof
curl -sS "http://localhost:8080/api/v1/records/<record_id>/proof" -o proof.json

🧍 Citizen Offline Verification

This is the whole point of the system — verification needs no registry access:

go run ./cmd/verify_cli -document deed.pdf -proof proof.json
# VERIFIED
# record_id:   7d1c...
# document:    a3f1... (sha-256)
# leaf_hash:   9be0...
# merkle_root: c44d...

Tamper with one byte and the recomputed root no longer matches the signed root:

cp deed.pdf deed-tampered.pdf && printf 'X' >> deed-tampered.pdf
go run ./cmd/verify_cli -document deed-tampered.pdf -proof proof.json
# VERIFICATION FAILED: PROOF_REJECTED: recomputed root ... does not equal signed root ...

The identical algorithm is exposed as POST /api/v1/verify for tooling that can't run the CLI — it also performs zero database reads, by construction.

📡 API Reference

MethodPathPurposeSuccess
POST/api/v1/recordsMultipart registration (document, record_type, metadata JSON)201 REGISTERED_PENDING_ANCHOR
GET/api/v1/records/{id}/proofAudit path + signed root; PENDING_ANCHOR until sealed200
GET/api/v1/epochs/{id}Published SignedBatchRoot for an anchored epoch200
POST/api/v1/verifyOffline-proof re-check, DB-free (returns verified:false as 200)200
GET/healthzLiveness200

Record types: LAND_TITLE · MUNICIPAL_PERMIT · BIRTH_RECORD · MARRIAGE_CERTIFICATE · BUSINESS_LICENSE.

⚙️ Configuration

VariableDefaultNotes
REGISTRY_ADDR:8080API listen address
REGISTRY_DSNpostgres://registry:registry@localhost:15432/civic_registry?sslmode=disableConnection pool (25 open / 5 idle)
AUTHORITY_KEY_PATH.keys/authority_ed25519.keyMissing key ⇒ dev-mode ephemeral key with loud warning
S3_ENDPOINT / S3_ACCESS_KEY / S3_SECRET_KEY / S3_BUCKETlocalhost:9010 / — / — / civic-documentsObject store
EPOCH_BATCH_SIZE / EPOCH_INTERVAL_SECONDS1000 / 3600Sealing policy (0 batch ⇒ seal everything pending)
EPOCH_RUN_ONCEfalseSingle sweep — useful for demos and CI
S3_IMAGE (compose)minio/minio:latestSet to e.g. rustfs/rustfs:latest on restricted hosts; code speaks plain S3 either way

🧪 Tests

go test ./...          # unit + integration + e2e (requires `docker compose up -d`)
# or:  make test       # with -race

58 tests cover: known-answer Merkle vectors and domain separation · inclusion proofs for every leaf of multi-epoch trees · forged documents, broken audit paths, replayed signatures, and wrong-key rejection · UPDATE/DELETE/TRUNCATE engine-level immutability · transactional batch sealing with leaf-count mismatch guards · 50-deed end-to-end anchoring with tamper detection · concurrent registration with unique leaf assignment · content-addressed archive round-trips and digest-mismatch rejection.

🗺️ Security Model (non-negotiables)

  • No mutation path: records, anchored roots and the audit log are append-only by PL/pgSQL trigger, not by convention — even TRUNCATE is blocked.
  • No cross-domain hash ambiguity: leaf vs. interior nodes carry distinct 0x00/0x01 prefixes; the signature envelope is domain-separated too.
  • No preimage ambiguity: every leaf preimage field is length-prefixed, so no two distinct records hash alike; canonical metadata bytes are stored verbatim because PostgreSQL JSONB reorders keys.
  • No timing leaks on the final compare: root equality uses crypto/subtle.ConstantTimeCompare.
  • Offline ≠ weakened: the verifier's step order (signature → digest → leaf → path replay → compare) means arithmetic only ever has to agree with what the authority already signed.

🛠️ Regenerating the Diagrams

Both docs/*.html render from JSON sources in .archify/ via Archify:

node <archify>/bin/archify.mjs finalize architecture .archify/architecture-registry-*/candidate.json docs/architecture.html --quality showcase
node <archify>/bin/archify.mjs finalize sequence     .archify/sequence-anchor-verify-*/candidate.json docs/verification-sequence.html --quality showcase

🤝 Contributing

Fork → branch → docker compose up -d && go test ./... → open a PR. Changes to the crypto core or the immutability migration need matching proof tests — the E2E civic-audit suite is the contract.


Built for the public-interest stack that can't afford silent history edits. 🇳🇬