fix(license): replace the LGPL zbase32 crate with an in-tree implementation

`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>
This commit is contained in:
archipelago
2026-08-08 04:55:51 -04:00
co-authored by Claude Opus 5
parent 6d33fea157
commit fe46c898d1
6 changed files with 286 additions and 16 deletions
+20 -6
View File
@@ -7,7 +7,8 @@ Audit date: 2026-07-22. Scope: entire repo (core Rust workspace, neode-ui, apps/
> **Updated 2026-08-08.** §1 (no license) and §3 (non-redistributable committed
> files) are now **closed** — root `LICENSE` (MIT) + `NOTICE` are in the tree, and
> the proprietary fonts and unused packages have actually been deleted. **§2
> (`zbase32`, LGPL-3.0+) is still open** and is the last hard blocker.
> (`zbase32`, LGPL-3.0+) is now closed too** — replaced by an in-tree
> implementation. No copyleft dependency remains in the Rust graph.
---
@@ -44,7 +45,7 @@ Audit date: 2026-07-22. Scope: entire repo (core Rust workspace, neode-ui, apps/
- License inventories generated: `core/THIRD-PARTY-LICENSES.md` (649 crates) and `neode-ui/THIRD-PARTY-LICENSES.md` (runtime deps + fonts + vendored).
**REMAINING (code changes, awaiting review — see sections below for detail):**
1. Replace `zbase32` (LGPL-3.0+) with `z32` or original impl — §2.
1. ~~Replace `zbase32` (LGPL-3.0+) with `z32` or original impl~~**DONE 2026-08-08**, original impl (§2).
2. Swap `redis:7.4.8` → Valkey in `scripts/image-versions.sh` and deploys — §3.
3. Delete dead StartOS-derived crates `core/{js-engine,container-init,models,helpers}` — §4.
4. Attribution build integration: cargo-about in CI → ship full license texts in ISO; vite/rollup license plugin (or UI licenses page) for the web bundle; Android OSS-licenses screen — §5.
@@ -52,7 +53,7 @@ Audit date: 2026-07-22. Scope: entire repo (core Rust workspace, neode-ui, apps/
6. ~~Before repo goes public: purge deleted fonts/APKs from git history (`git filter-repo`)~~ — **superseded**: the launch plan is a fresh-history publish, so there is no history to rewrite. What still applies is verifying the game-icons author credit, and actually deleting the files (see the correction above — they were never removed).
**Re-verified 2026-08-08:**
- `zbase32 0.1.2` (LGPL-3.0+) is **still a direct dependency** (`core/archipelago/Cargo.toml:113`), still used at `network/did_dht.rs:40,49`. Item 1 remains open and is the only hard copyleft blocker.
- ~~`zbase32 0.1.2` (LGPL-3.0+) is still a direct dependency.~~ **Removed 2026-08-08** — see §2.
- `LICENSE` (MIT) and `NOTICE` are present ✅. `core/THIRD-PARTY-LICENSES.md` and `neode-ui/THIRD-PARTY-LICENSES.md` are present ✅.
- The four StartOS-derived crates in item 3 (`core/{js-engine,container-init,models,helpers}`) **still exist** — note KEY-05 legitimately cites `core/models`, so that one needs a look before deletion rather than a blind `rm`.
@@ -70,9 +71,22 @@ There is no `LICENSE`/`COPYING` file anywhere in the repo. No crate in `core/` d
- [ ] Add `license = "MIT"` to all five workspace member `Cargo.toml`s (archipelago, container, openwrt, performance, security) and `Android/rust/archy-fips-core` (declares MIT but ships no license file — add one).
- [ ] Add `"license": "MIT"` to `neode-ui/package.json` and `apps/{morphos-server,router,did-wallet}/package.json`.
## 2. BLOCKER — copyleft dependency that must be replaced
## 2. BLOCKER — copyleft dependency that must be replaced ✅ CLOSED 2026-08-08
- [ ] **`zbase32 0.1.2` — LGPL-3.0+** — the only hard copyleft blocker in all 649 resolved Rust crates. Direct dep of `archipelago`, used in `core/archipelago/src/network/did_dht.rs` for did:dht z-base-32 encoding. LGPL statically linked into a Rust binary requires shipping relinkable objects/source — impractical. **Replace with the MIT `z32` crate** or a ~30-line original alphabet-substitution implementation.
- [x] **`zbase32 0.1.2` — LGPL-3.0+** — was the only hard copyleft blocker in all 649 resolved Rust crates. Direct dep of `archipelago`, used in `core/archipelago/src/network/did_dht.rs` for did:dht z-base-32 encoding. LGPL statically linked into a Rust binary requires shipping relinkable objects/source — impractical.
**DONE 2026-08-08.** Replaced with an original in-tree implementation at
`core/archipelago/src/network/zbase32.rs` (~60 lines incl. docs) rather than
the `z32` crate — the encoding is an alphabet substitution over a bit stream,
so this removes the blocker without adding any dependency or new supply-chain
surface. Dropped from `Cargo.toml` and `Cargo.lock`.
Byte-compatibility was the hard requirement: 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 published DHT records. The replacement is pinned
against the removed crate's own three doc-test vectors, the canonical vectors
from Zimmermann's z-base-32 spec, and four known 32-byte keys — plus a
`did_for_a_known_key_is_stable` test at the `did_dht.rs` call site.
No GPL, AGPL, SSPL, or unlicensed crates exist anywhere else in the Rust graph. (`r-efi` and `self_cell` list LGPL/GPL only as options in OR-expressions — elect MIT/Apache, no action.)
@@ -132,7 +146,7 @@ The ISO redistributes a full Debian (trixie) system plus ~29 container image tar
## Quick reference: what's already clean
- All 649 Rust crates except `zbase32`: permissive or dual-licensed.
- All Rust crates: permissive or dual-licensed (`zbase32` was the sole exception and is gone as of 2026-08-08).
- All 833 npm packages in neode-ui: no GPL/AGPL anywhere; only dev-tool LGPL (sharp's libvips, never distributed).
- Android Gradle deps: 100 % Apache-2.0, all pinned, no Play Services/telemetry.
- FIPS mesh: MIT (© 2026 Johnathan Corgan) — keep notice.