`zbase32 0.1.2` is LGPL-3.0+ — the only hard copyleft dependency in the whole
Rust graph and the last remaining blocker for the MIT release
(docs/LICENSE-COMPLIANCE-AUDIT.md §2). Statically linking LGPL code into a Rust
binary obliges us to ship relinkable objects, which is impractical for a node
image.
The audit offered two routes: the MIT `z32` crate, or an original
implementation. Took the latter — z-base-32 is an alphabet substitution over a
bit stream, so ~60 lines removes the blocker while adding *zero* new
dependencies rather than trading one supply-chain entry for another.
**Byte-compatibility was the requirement, not a nice-to-have.** A `did:dht`
identifier IS this encoding of an Ed25519 public key, so any drift would
silently rotate every node's DID and orphan its already-published DHT records.
So the semantics were not guessed: I read the vendored zbase32-0.1.2 source to
extract exactly what `encode_full_bytes` and `decode_full_bytes_str` do —
including that decode truncates to the next lower byte boundary, which is why a
52-character string round-trips to 32 bytes while discarding 4 padding bits.
A model implementation was then validated against three independent sources
before any Rust was written, all five vectors agreeing:
encode(b"testdata", 64) -> qt1zg7drcf4gn (crate doctest)
encode_full_bytes("Just an…") -> jj4zg7bycfzn… (crate doctest)
decode_full_bytes("qb1ze3m1") -> b"peter" (crate doctest)
encode([f0,bf,c7]) -> 6n9hq (Zimmermann spec)
encode([d4,7a,04]) -> 4t7ye (Zimmermann spec)
The module pins all of those plus four known 32-byte keys, a 0..40-byte
round-trip sweep, a 52-char/round-trip check over 64 keys, rejection of the
characters z-base-32 deliberately omits (`l`, `v`, `2`, `0`) and of non-ASCII,
and an alphabet/decode-table consistency check so the compile-time reverse table
can't drift from the alphabet.
`did_dht.rs` gains `did_for_a_known_key_is_stable`, which pins the full
identifier string for a known key — the regression that would actually hurt,
asserted at the call site that gives the string its meaning.
Dropped from Cargo.toml and Cargo.lock (7 lines); no other user in the tree.
Verified: 28/28 network tests pass, zero copyleft crates remain in the lockfile.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Archipelago documentation
Start here. This index groups the docs by what you're trying to do. The
authoritative behaviour is always the code in core/; where a doc and the code
disagree, the code wins and the doc is a bug.
Getting started
- User Walkthrough — setting up and using a node, from hardware to daily use
- Talking to your node — the conversational command surface
- Seed Verification — independently verify your 24-word backup
- Troubleshooting — common problems and how to resolve them
- Gamepad / Controller Navigation — driving the UI from a controller
- Pine voice commands — the voice-satellite phrase surface
Architecture
- Architecture — the system at a glance
- Multi-Node Architecture — how nodes relate across a fleet
- API Reference — the JSON-RPC surface
Contributing to Archipelago itself
- Developer Guide — building the workspace, the frontend, and an ISO
- Contributor guide (
CLAUDE.md) — invariants, build/verify, the production test gate - Bulletproof containers — why the reconciler is level-triggered
- Release signing runbook — the ceremony and key handling
- 1.8.0 Release Hardening Plan — the release-blocking checklist
- Third-party license audit — dependency licensing posture
- Demo build info — operating the public demo sandbox
App development
- App Developer Guide — build and package a containerized app
- App Manifest Specification — the manifest schema, field by field
- Manifest → Quadlet unit — how a manifest compiles to a systemd-owned container unit
- Container lifecycle — the reconciler state machine: install/adopt/start/stop/self-heal
- App secrets — declaring, generating and injecting per-install credentials
- Registry-Distributed Manifests — how manifests reach nodes via the signed catalog
- Decentralized Marketplace Protocol — publishing apps via an external registry
- Bitcoin RPC Relay — letting an external wallet reach the node's Bitcoin RPC
- Companion Pairing QR — the pairing handoff contract
- TV input inside iframe apps — keyboard/gamepad routing into embedded apps
Design docs
These record why a thing is built the way it is. They are design records, not step-by-step guides, and some predate the current implementation.
- Registry-Distributed Manifests
- DHT Distribution
- Bitcoin Multi-Version
- Dual Ecash
- Hardware Signer
- Manifest Hooks
- Meshroller Integration
- Nostr Git Source Hosting
- Nostr Identity Import · Nostr Signer Login (research)
- Streaming Ecash (phase 4)
- App Packaging Migration
Decisions (ADRs)
- ADR-001: Podman over Docker
- ADR-002: DID Key Method for Node Identity
- ADR-003: Nostr Relays for Discovery
- ADR-004: Tor Hidden Services for Peer Communication
- ADR-005: ChaCha20-Poly1305 for Backup Encryption
- ADR-006: Nostr Relays for Marketplace Discovery
- ADR-007: DID-Based Federation Trust
- ADR-008: Dual Key Strategy (Ed25519 + Secp256k1)
- ADR-009: Manifest-Level Container Security
- ADR-011: DWN Deprioritization
There is no ADR-010 — the number was never issued, so the gap is not a missing file.
Security
- Security Policy — how to report a vulnerability
- PSBT Signing Architecture
- Bitcoin RPC Proxy Exposure
- Entropy Enforcement (KEY-05)