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:
co-authored by
Claude Opus 5
parent
6d33fea157
commit
fe46c898d1
Generated
-7
@@ -171,7 +171,6 @@ dependencies = [
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"uuid",
|
||||
"zbase32",
|
||||
"zeroize",
|
||||
"zip",
|
||||
]
|
||||
@@ -6819,12 +6818,6 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbase32"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f9079049688da5871a7558ddacb7f04958862c703e68258594cb7a862b5e33f"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.33"
|
||||
|
||||
@@ -109,8 +109,10 @@ data-encoding = "2.6"
|
||||
zeroize = { version = "1.8.2", features = ["derive"] }
|
||||
|
||||
# Mainline DHT (did:dht — BitTorrent DHT for decentralized identity)
|
||||
# z-base-32 is implemented in-tree at src/network/zbase32.rs: the `zbase32`
|
||||
# crate is LGPL-3.0+, the only hard copyleft dep in the graph and a blocker for
|
||||
# the MIT release (docs/LICENSE-COMPLIANCE-AUDIT.md §2).
|
||||
mainline = "2"
|
||||
zbase32 = "0.1"
|
||||
bytes = "1"
|
||||
|
||||
# Mesh networking (Meshcore serial protocol over USB LoRa radios)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
//!
|
||||
//! The did:dht identifier is the z-base-32 encoding of the Ed25519 public key.
|
||||
|
||||
use crate::network::zbase32;
|
||||
use anyhow::{Context, Result};
|
||||
use ed25519_dalek::{SigningKey, VerifyingKey};
|
||||
use std::collections::HashMap;
|
||||
@@ -41,13 +42,21 @@ pub fn did_from_pubkey(pubkey: &VerifyingKey) -> String {
|
||||
format!("did:dht:{}", encoded)
|
||||
}
|
||||
|
||||
/// Round-trip guard: a `did:dht` identifier IS the z-base-32 encoding of the
|
||||
/// key, so a change in that encoding silently rotates every node's DID and
|
||||
/// orphans its published DHT records. Pinned here as well as in the encoder's
|
||||
/// own tests because this is the call site that gives the string its meaning.
|
||||
#[cfg(test)]
|
||||
const DID_FOR_TEST_MNEMONIC_NODE_KEY: &str =
|
||||
"did:dht:1o96jdeaigue33hrds3ph6shicehgssx6q6c88yo638curyx7i4y";
|
||||
|
||||
/// Extract the Ed25519 public key bytes from a did:dht identifier.
|
||||
pub fn pubkey_from_did(did: &str) -> Result<[u8; 32]> {
|
||||
let id = did
|
||||
.strip_prefix("did:dht:")
|
||||
.ok_or_else(|| anyhow::anyhow!("Not a did:dht identifier: {}", did))?;
|
||||
let bytes = zbase32::decode_full_bytes_str(id)
|
||||
.map_err(|e| anyhow::anyhow!("Invalid z-base-32: {:?}", e))?;
|
||||
let bytes =
|
||||
zbase32::decode_full_bytes_str(id).map_err(|e| anyhow::anyhow!("Invalid z-base-32: {e}"))?;
|
||||
if bytes.len() != 32 {
|
||||
anyhow::bail!("Expected 32-byte pubkey, got {} bytes", bytes.len());
|
||||
}
|
||||
@@ -188,6 +197,30 @@ mod tests {
|
||||
assert!(pubkey_from_did("did:key:z123").is_err());
|
||||
}
|
||||
|
||||
/// The identifier for a known key must not move. Guards the LGPL `zbase32`
|
||||
/// → in-tree encoder swap, and any future change to it.
|
||||
#[test]
|
||||
fn did_for_a_known_key_is_stable() {
|
||||
let key_bytes: [u8; 32] =
|
||||
hex::decode("943fe48d18a9a68ce7841db2de7adcab11c35acff3bcc39c10f64ec9900fed74")
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let pubkey = VerifyingKey::from_bytes(&key_bytes).unwrap();
|
||||
|
||||
assert_eq!(did_from_pubkey(&pubkey), DID_FOR_TEST_MNEMONIC_NODE_KEY);
|
||||
assert_eq!(
|
||||
pubkey_from_did(DID_FOR_TEST_MNEMONIC_NODE_KEY).unwrap(),
|
||||
key_bytes
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_a_did_whose_body_is_not_zbase32() {
|
||||
// `l` and `v` are not in the z-base-32 alphabet.
|
||||
assert!(pubkey_from_did("did:dht:lllvvv").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_did_document() {
|
||||
let key = SigningKey::generate(&mut rand::rngs::OsRng);
|
||||
|
||||
@@ -3,3 +3,4 @@ pub mod dns;
|
||||
pub mod dwn_store;
|
||||
pub mod dwn_sync;
|
||||
pub mod router;
|
||||
pub mod zbase32;
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
//! z-base-32 encoding, as used by `did:dht` identifiers.
|
||||
//!
|
||||
//! [z-base-32](https://philzimmermann.com/docs/human-oriented-base-32-encoding.txt)
|
||||
//! is Zooko's human-oriented base-32 alphabet: same 5-bits-per-character idea as
|
||||
//! RFC 4648 base32, but with the characters permuted so the ones people confuse
|
||||
//! (`0`/`O`, `1`/`l`/`I`, `2`/`Z`, `v`/`u`) are either absent or arranged to
|
||||
//! minimise transcription errors, and with no `=` padding.
|
||||
//!
|
||||
//! # Why this exists rather than a crate
|
||||
//!
|
||||
//! This replaces the `zbase32` crate, which is **LGPL-3.0+** — the only hard
|
||||
//! copyleft dependency in the Rust graph and a 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 encoding itself is an alphabet substitution over a bit
|
||||
//! stream, so an original implementation is a few dozen lines and adds no
|
||||
//! dependency at all.
|
||||
//!
|
||||
//! # Compatibility
|
||||
//!
|
||||
//! Output is **byte-identical** to `zbase32 0.1.2`'s `encode_full_bytes` /
|
||||
//! `decode_full_bytes_str`, which is what the previous implementation called.
|
||||
//! That matters because a `did:dht` identifier *is* this encoding of an Ed25519
|
||||
//! public key: a different output would silently change every node's DID and
|
||||
//! break already-published DHT records. The tests below pin the crate's own
|
||||
//! documented vectors, the canonical vectors from Zimmermann's spec, and
|
||||
//! several 32-byte keys.
|
||||
//!
|
||||
//! # Bit layout
|
||||
//!
|
||||
//! Bits are taken most-significant-first from the byte stream and grouped into
|
||||
//! 5-bit chunks. When the bit count is not a multiple of 5 the final chunk is
|
||||
//! padded on the right (low side) with zero bits. A 32-byte key is 256 bits →
|
||||
//! 52 characters (260 bits), so the last character carries 4 padding bits.
|
||||
|
||||
/// The z-base-32 alphabet. Index = 5-bit value.
|
||||
const ALPHABET: &[u8; 32] = b"ybndrfg8ejkmcpqxot1uwisza345h769";
|
||||
|
||||
/// Reverse of [`ALPHABET`]: ASCII byte → 5-bit value, `None` if not a digit.
|
||||
/// Built at compile time so decoding is a table lookup and stays in sync with
|
||||
/// the alphabet by construction.
|
||||
const DECODE_TABLE: [Option<u8>; 256] = {
|
||||
let mut table = [None; 256];
|
||||
let mut i = 0;
|
||||
while i < 32 {
|
||||
table[ALPHABET[i] as usize] = Some(i as u8);
|
||||
i += 1;
|
||||
}
|
||||
table
|
||||
};
|
||||
|
||||
/// Encode every bit of `data` as z-base-32.
|
||||
///
|
||||
/// Equivalent to the `zbase32` crate's `encode_full_bytes`.
|
||||
pub fn encode_full_bytes(data: &[u8]) -> String {
|
||||
let bits = data.len() * 8;
|
||||
// ceil(bits / 5)
|
||||
let out_len = bits.div_ceil(5);
|
||||
let mut out = String::with_capacity(out_len);
|
||||
|
||||
// `acc` holds the not-yet-emitted low `acc_bits` bits, MSB-first.
|
||||
let mut acc: u32 = 0;
|
||||
let mut acc_bits: u32 = 0;
|
||||
for &byte in data {
|
||||
acc = (acc << 8) | u32::from(byte);
|
||||
acc_bits += 8;
|
||||
while acc_bits >= 5 {
|
||||
acc_bits -= 5;
|
||||
let idx = (acc >> acc_bits) & 0x1f;
|
||||
out.push(ALPHABET[idx as usize] as char);
|
||||
}
|
||||
}
|
||||
// Trailing bits: left-align them in a 5-bit group (pad right with zeros).
|
||||
if acc_bits > 0 {
|
||||
let idx = (acc << (5 - acc_bits)) & 0x1f;
|
||||
out.push(ALPHABET[idx as usize] as char);
|
||||
}
|
||||
|
||||
debug_assert_eq!(out.len(), out_len);
|
||||
out
|
||||
}
|
||||
|
||||
/// Decode a z-base-32 string, keeping only whole bytes.
|
||||
///
|
||||
/// Equivalent to the `zbase32` crate's `decode_full_bytes_str`: the input
|
||||
/// carries `len * 5` bits, and everything below the next lower byte boundary is
|
||||
/// discarded. So 52 characters (260 bits) yield 32 bytes and the final 4 bits
|
||||
/// are ignored — which is exactly why a 32-byte key round-trips.
|
||||
///
|
||||
/// Returns `Err` with the offending character if the input is not z-base-32.
|
||||
pub fn decode_full_bytes_str(s: &str) -> Result<Vec<u8>, String> {
|
||||
let total_bits = s.len() * 5;
|
||||
let keep_bits = total_bits / 8 * 8;
|
||||
let mut out = Vec::with_capacity(keep_bits / 8);
|
||||
|
||||
let mut acc: u32 = 0;
|
||||
let mut acc_bits: u32 = 0;
|
||||
let mut emitted_bits = 0usize;
|
||||
for ch in s.chars() {
|
||||
// Non-ASCII can't be a digit; `as usize` on a multi-byte char would
|
||||
// index the table wrongly, so reject before the lookup.
|
||||
let value = u8::try_from(ch as u32)
|
||||
.ok()
|
||||
.and_then(|b| DECODE_TABLE[b as usize])
|
||||
.ok_or_else(|| format!("not a z-base-32 digit: {ch:?}"))?;
|
||||
acc = (acc << 5) | u32::from(value);
|
||||
acc_bits += 5;
|
||||
while acc_bits >= 8 && emitted_bits < keep_bits {
|
||||
acc_bits -= 8;
|
||||
out.push(((acc >> acc_bits) & 0xff) as u8);
|
||||
emitted_bits += 8;
|
||||
}
|
||||
}
|
||||
|
||||
debug_assert_eq!(out.len(), keep_bits / 8);
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// The three doc-test vectors from `zbase32 0.1.2` itself. If these hold,
|
||||
/// this module is a drop-in for the calls the crate used to serve.
|
||||
#[test]
|
||||
fn matches_the_replaced_crates_own_doctests() {
|
||||
assert_eq!(
|
||||
encode_full_bytes("Just an arbitrary sentence.".as_bytes()),
|
||||
"jj4zg7bycfznyam1cjwzehubqjh1yh5fp34gk5udcwzy"
|
||||
);
|
||||
assert_eq!(decode_full_bytes_str("qb1ze3m1").unwrap(), b"peter");
|
||||
// `encode(b"testdata", 64)` — 64 bits is exactly 8 whole bytes, so
|
||||
// encode_full_bytes agrees with the crate's bit-precision form here.
|
||||
assert_eq!(encode_full_bytes(b"testdata"), "qt1zg7drcf4gn");
|
||||
}
|
||||
|
||||
/// Canonical vectors from Zimmermann's z-base-32 spec (the whole-byte
|
||||
/// subset — the spec's sub-byte cases exercise an API we deliberately
|
||||
/// don't expose).
|
||||
#[test]
|
||||
fn matches_the_spec_vectors() {
|
||||
assert_eq!(encode_full_bytes(&[0xf0, 0xbf, 0xc7]), "6n9hq");
|
||||
assert_eq!(encode_full_bytes(&[0xd4, 0x7a, 0x04]), "4t7ye");
|
||||
}
|
||||
|
||||
/// A `did:dht` identifier is this encoding of a 32-byte Ed25519 key, so
|
||||
/// these pin the exact strings that must not drift. Computed independently
|
||||
/// and cross-checked against the spec vectors above.
|
||||
#[test]
|
||||
fn known_answers_for_32_byte_keys() {
|
||||
let seq: Vec<u8> = (0u8..32).collect();
|
||||
assert_eq!(
|
||||
encode_full_bytes(&seq),
|
||||
"yyyoryarywdyqnyjbefoadeqbhebnrounoktcfaadrpbs8y7daxo"
|
||||
);
|
||||
assert_eq!(
|
||||
encode_full_bytes(&[0xff; 32]),
|
||||
"999999999999999999999999999999999999999999999999999o"
|
||||
);
|
||||
assert_eq!(
|
||||
encode_full_bytes(&[0x00; 32]),
|
||||
"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
|
||||
);
|
||||
// The node Ed25519 public key derived from seed.rs's TEST_MNEMONIC.
|
||||
let node_key =
|
||||
hex::decode("943fe48d18a9a68ce7841db2de7adcab11c35acff3bcc39c10f64ec9900fed74")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
encode_full_bytes(&node_key),
|
||||
"1o96jdeaigue33hrds3ph6shicehgssx6q6c88yo638curyx7i4y"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_32_byte_key_is_52_chars_and_round_trips() {
|
||||
for seed in 0u8..64 {
|
||||
let key: Vec<u8> = (0u8..32).map(|i| i.wrapping_mul(7).wrapping_add(seed)).collect();
|
||||
let encoded = encode_full_bytes(&key);
|
||||
assert_eq!(encoded.len(), 52, "256 bits must encode to 52 characters");
|
||||
assert_eq!(decode_full_bytes_str(&encoded).unwrap(), key);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trips_every_length_up_to_a_block() {
|
||||
for len in 0..40usize {
|
||||
let data: Vec<u8> = (0..len).map(|i| (i as u8).wrapping_mul(31) ^ 0x5a).collect();
|
||||
let encoded = encode_full_bytes(&data);
|
||||
// decode_full_bytes only recovers whole bytes, and encoding N bytes
|
||||
// produces ceil(8N/5) chars which always carry at least 8N bits.
|
||||
assert_eq!(decode_full_bytes_str(&encoded).unwrap(), data, "len {len}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_input() {
|
||||
assert_eq!(encode_full_bytes(&[]), "");
|
||||
assert_eq!(decode_full_bytes_str("").unwrap(), Vec::<u8>::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_non_alphabet_characters() {
|
||||
// `l`, `v`, `2`, `0` are deliberately absent from the z-base-32
|
||||
// alphabet — they're the characters it exists to avoid.
|
||||
for bad in ["l", "v", "2", "0", "A", "yyy!", "yyyé"] {
|
||||
assert!(
|
||||
decode_full_bytes_str(bad).is_err(),
|
||||
"{bad:?} must not decode"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The alphabet must stay a permutation of 32 distinct ASCII characters, or
|
||||
/// the compile-time decode table silently loses entries.
|
||||
#[test]
|
||||
fn alphabet_is_32_distinct_ascii_characters() {
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for &c in ALPHABET.iter() {
|
||||
assert!(c.is_ascii(), "non-ASCII in alphabet");
|
||||
assert!(seen.insert(c), "duplicate character in alphabet: {c}");
|
||||
}
|
||||
assert_eq!(seen.len(), 32);
|
||||
for (i, &c) in ALPHABET.iter().enumerate() {
|
||||
assert_eq!(DECODE_TABLE[c as usize], Some(i as u8));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user