4 Commits

Author SHA1 Message Date
vsecoder
1d9206494a feat(chain): multi-device registry (v2.2.0-alpha1)
PR #1 of the multi-device roadmap. Adds per-device X25519 keys registered
on-chain so senders can fan out envelopes across all of a recipient's
physical devices — fixes the single-device limitation where a second
phone / desktop loses messages as soon as the first one reads them.

Chain (blockchain/):
  - New event types LINK_DEVICE / UNLINK_DEVICE, signed by the identity's
    master Ed25519.
  - LinkDevicePayload {x25519_pub_key, device_name} +
    UnlinkDevicePayload {x25519_pub_key} on the wire.
  - State: prefixDevice + x25519_pub → DeviceRecord{owner, name,
    added_at, revoked_at?}; reverse index prefixDevicesByOwner for
    O(k) listing. Revoke is a soft-delete — the row stays as a visible
    tombstone so offline clients can detect their own revocation and
    wipe local state.
  - MaxDevicesPerOwner = 10 slot cap; MaxDeviceNameLen = 64.
  - Strict lowercase-hex validation on x25519_pub so clients can't
    desync on letter case.
  - Same-owner re-link is a rename/refresh (recreates reverse index too
    — needed after a revoke).
  - Chain.DevicesOf(master_pub) returns the active records; empty slice
    for legacy identities so senders can fall back to IdentityInfo.X25519Pub.

HTTP (node/):
  - GET /api/devices/{master_pub_or_addr} — returns {master_pub, count,
    devices[]}. Revoked records filtered out.
  - /api/identity/{pub} gains `device_count` so senders can decide
    upfront whether to fan out or take the legacy path.

Tests (blockchain/devices_test.go):
  - Happy paths (1, 3 devices), foreign-owner rejection, same-owner
    refresh after revoke, unlink removes from active set,
    foreign-signer unlink rejection, idempotent double-unlink,
    malformed pub/name rejection, MaxDevices cap + recovery after
    unlink frees a slot, empty list for unknown master.

Also in this commit:
  - deploy/single/join.sh — convenience script operators have been
    iterating on in this session (joiner-node bring-up + firewall
    port patching + Caddy opt-out).
  - client-app/app.json — `usesCleartextTraffic: true` on Android so
    installed APKs can talk to http:// dev nodes without TLS.

See docs/ROADMAP.md for PRs #2..#4 (client fan-out, pairing flow,
desktop Electron shell).
2026-04-22 16:20:07 +03:00
vsecoder
f726587ac6 fix(docker): pre-create /data as dchain user so named volumes inherit ownership
Running Dockerfile.slim with a fresh named volume crashed on startup:

  [NODE] open chain: open badger: Error Creating Dir: "/data/chain"
    error: mkdir /data/chain: permission denied

Docker copies the mount-point's directory ownership (from the image)
into a new named volume at first attach. In the previous Dockerfile
/data was created implicitly by the VOLUME directive, which means it
was owned by root — but the container runs as the unprivileged
`dchain` user, so it couldn't `mkdir /data/chain` on first boot.

Fix: explicitly `mkdir /data && chown dchain:dchain /data` in the
same RUN that creates the user, before the VOLUME directive. Fresh
volumes now inherit dchain:dchain ownership automatically; no
operator-side `docker run --user root chown` workaround needed.

Operators already running with a root-owned volume from before this
fix need to chown once manually:

  docker run --rm -v dchain_data:/data --user root alpine \
    sh -c 'chown -R 100:101 /data'

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 22:43:31 +03:00
vsecoder
1e7f4d8da4 fix(docker): bump build-stage Go to 1.25 (matches go.mod)
When v2.0.0 added the golang.org/x/image/webp dependency (used by the
media scrubber for WebP decoding), go mod tidy bumped the module's
minimum Go version in go.mod:

  module go-blockchain
  go 1.25.0

The three Dockerfiles in the repo were still pinned to older images:

  /Dockerfile              FROM golang:1.24-alpine
  /deploy/prod/Dockerfile.slim  FROM golang:1.24-alpine
  /docker/media-sidecar/Dockerfile  FROM golang:1.22-alpine

Result: `docker build` on any of them fails at `go mod download` with
  go: go.mod requires go >= 1.25.0 (running go 1.24.13; GOTOOLCHAIN=local)
because Alpine's golang image pins GOTOOLCHAIN=local to keep the
toolchain reproducible.

Fix: bump all three to golang:1.25-alpine. The media-sidecar module
doesn't actually need 1.25 (it's self-contained and only uses stdlib),
but keeping all three in sync avoids surprise the next time somebody
adds a dep.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 22:32:22 +03:00
vsecoder
7e7393e4f8 chore: initial commit for v0.0.1
DChain single-node blockchain + React Native messenger client.

Core:
- PBFT consensus with multi-sig validator admission + equivocation slashing
- BadgerDB + schema migration scaffold (CurrentSchemaVersion=0)
- libp2p gossipsub (tx/v1, blocks/v1, relay/v1, version/v1)
- Native Go contracts (username_registry) alongside WASM (wazero)
- WebSocket gateway with topic-based fanout + Ed25519-nonce auth
- Relay mailbox with NaCl envelope encryption (X25519 + Ed25519)
- Prometheus /metrics, per-IP rate limit, body-size cap

Deployment:
- Single-node compose (deploy/single/) with Caddy TLS + optional Prometheus
- 3-node dev compose (docker-compose.yml) with mocked internet topology
- 3-validator prod compose (deploy/prod/) for federation
- Auto-update from Gitea via /api/update-check + systemd timer
- Build-time version injection (ldflags → node --version)
- UI / Swagger toggle flags (DCHAIN_DISABLE_UI, DCHAIN_DISABLE_SWAGGER)

Client (client-app/):
- Expo / React Native / NativeWind
- E2E NaCl encryption, typing indicator, contact requests
- Auto-discovery of canonical contracts, chain_id aware, WS reconnect on node switch

Documentation:
- README.md, CHANGELOG.md, CONTEXT.md
- deploy/single/README.md with 6 operator scenarios
- deploy/UPDATE_STRATEGY.md with 4-layer forward-compat design
- docs/contracts/*.md per contract
2026-04-17 14:16:44 +03:00