Skip to content

1.0: drop every runtime dependency, enforce the licensing claims, add MulanPSL-2.0 - #13

Merged
paudley merged 5 commits into
mainfrom
paudley/trim-deps-and-mulan-license
Sep 17, 2026
Merged

paudley merged 5 commits into
mainfrom
paudley/trim-deps-and-mulan-license

Conversation

@paudley

@paudley paudley commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Three related changes, released together as 1.0.0.

1. Zero runtime dependencies

cargo tree --edges normal now prints only visual-hashing. The ten-crate
graph, the build script and the C-compiler requirement are gone.

The whole graph existed to serve four lines in emojihash that ask BLAKE3 for
nine bytes of XOF output from a 32-byte public key. For a library whose point
is being a small, auditable primitive you embed next to key material, that was
the main defect.

src/blake3.rs implements the part actually needed: unkeyed, one-shot,
extendable output. The reduction comes from dropping the incremental API,
not from capping the input — upstream carries a chunk state, a 54-deep
chaining-value stack and block bookkeeping because update() may be handed any
prefix, whereas a caller supplying the whole slice collapses all of that into
one recursive descent over the subtree structure. Full input range is kept;
capping at one chunk would have saved ~20 lines and eventually returned a wrong
answer for a 2 KB input.

Correctness is checked three ways rather than assumed:

  • the frozen vectors under vectors/ pass byte-for-byte unchanged — the
    proof that matters;
  • tests/blake3_equivalence.rs keeps the upstream blake3 crate as a dev-only
    oracle and diffs against it across every block, chunk and subtree boundary
    (~600 comparisons);
  • published known-answer vectors are pinned in unit tests, so agreement does
    not rest solely on the oracle.

serde_json goes too, replaced by a strict reader for the generated vector
shape that rejects anything outside it — including the key set, so a malformed
vector fails loudly instead of leaving an assertion silently unexercised.

With nothing left to link, the crate is now no_std + alloc and builds for
thumbv7em-none-eabihf as well as wasm32.

2. The claims are enforced, not asserted

The dependency audit came out clean, but a clean audit is a fact about one
afternoon, not a property of the repository.

scripts/check-licenses.py gates the SPDX expression on every tracked file,
agreement between headers and REUSE.toml, the licence-text digests, and the
no-runtime-dependencies property. It found a real gap on its first run:
REUSE.toml carried no header of its own. deny.toml covers advisories,
sources, duplicate versions and the dev-only licence graph.

It is a first-party script rather than a third-party action on purpose — every
action in this workflow is SHA-pinned, and widening the supply chain to run
what amounts to a grep would be a poor trade.

3. MulanPSL-2.0

The crate is now offered under MIT OR Apache-2.0 OR MulanPSL-2.0. OR is
operative: pick one, comply with that one. Nothing is taken from anyone already
using MIT or Apache-2.0. Blackcat Informatics is the sole author of everything
in this tree, so the grant is unilateral.

LICENSING.md documents rather than merely lists it — §6 makes the Chinese
text controlling
where the two versions diverge, §2's patent grant ends on
patent litigation, §3 withholds trademarks explicitly, and §4 carries the
notice-retention obligation. Every claim was read off the shipped text. It also
now carries a 中文说明 summarising the grant in Chinese, since documenting a
Chinese-controlling licence only in English serves the wrong audience.

The canonical host (license.coscl.org.cn) is unreachable, so LICENSE-MULAN
is the official bilingual text from the SPDX license-list-data mirror, pinned
by SHA-256 and re-verified in CI.

vectors/ takes the same grant. Those files are generated by this repository's
own scripts from its own reference implementation, and byte-identical copies
are redistributed by purrdf and gmeow-gts; naming this repository canonical
settles a declaration that differed between the three trees.

Verification

cargo fmt --check                                   OK
cargo clippy --all-targets -- -D warnings           OK
cargo test --locked                                 10 passed
cargo tree --edges normal                           visual-hashing only
cargo build --lib --target wasm32-unknown-unknown   OK
cargo build --lib --target thumbv7em-none-eabihf    OK
cargo deny check                                    advisories/bans/licenses/sources ok
python3 scripts/check-licenses.py                   OK
cargo publish --locked --dry-run                    OK  <- crates.io accepts the SPDX expression

4. 1.0

The API and both renderings are frozen, so this ships as 1.0.0 rather than
0.9.0-then-1.0.0 a week apart. What 1.0 commits to: the 64-emoji alphabet, the
randomart ramp and the public API do not change. A fingerprint already printed
in a log, a CLI banner or someone's notes has to keep meaning what it meant, so
altering either rendering would warrant a new crate rather than a new major
version. The frozen vectors hold that line and CI fails on drift.

Downstream note

0.1.3 → 1.0.0 is semver-incompatible, so neither consumer picks this up
automatically
— for a 0.x requirement the minor slot is the breaking one,
so "0.1.3" means >=0.1.3, <0.2.0. After this publishes:

File Change
purrdf/Cargo.toml:187 (workspace table) visual-hashing = "1"
gmeow-gts/rust/Cargo.toml:58 visual-hashing = "1"

The public API and every rendering are unchanged.

The crate's entire runtime graph - ten crates, a build script and a C
compiler invocation - existed to serve four lines in emojihash that ask
BLAKE3 for nine bytes of XOF output from a 32-byte public key. For a
library whose point is being a small, auditable primitive you embed next
to key material, that graph was the main defect.

src/blake3.rs implements the part actually needed: unkeyed, one-shot,
extendable output. Dropping the incremental API is what makes it small -
upstream carries a chunk state, a 54-deep chaining-value stack and block
bookkeeping because update() may be handed any prefix, whereas a caller
that supplies the whole slice lets all of that collapse into one
recursive descent over the subtree structure. Full input range is kept:
capping at one chunk would have saved about twenty lines and eventually
handed someone a wrong answer for a 2 KB input.

Correctness is checked three ways rather than assumed. The frozen
vectors under vectors/ pass byte-for-byte unchanged, which is the proof
that matters. tests/blake3_equivalence.rs keeps the upstream crate as a
dev-only oracle and diffs against it across every block, chunk and
subtree boundary. Published known-answer vectors are pinned in unit
tests so agreement does not rest solely on the oracle - if this code and
the oracle were wrong the same way, only a fixed external answer would
notice.

serde_json goes too, replaced by a strict reader for the generated
vector shape; it rejects anything outside that shape, including the key
set, so a malformed vector fails loudly instead of leaving an assertion
silently unexercised.

With nothing left to link, the crate becomes no_std + alloc and builds
for thumbv7em-none-eabihf as well as wasm32.
The dependency audit for this repository came out clean, but a clean
audit is a fact about one afternoon, not a property of the repository.
This turns each claim into a gate.

scripts/check-licenses.py checks that every tracked file declares the
expected SPDX expression - by header, or through a REUSE.toml annotation
for formats that cannot carry a comment - that REUSE.toml agrees with
those headers, that the root LICENSE-* files match their LICENSES/*.txt
counterparts, and that the library still has no runtime or build
dependencies. It found one gap on its first run: REUSE.toml carried no
header of its own.

It is a first-party script rather than a third-party action on purpose.
Every action in this workflow is SHA-pinned, and widening the supply
chain to run what amounts to a grep would be a poor trade.

deny.toml covers what the script cannot: advisories, sources, duplicate
versions, and the licences of the dev-only graph. The allow-list is
short and annotated with which crate needs each entry, so adding one
requires looking at the new dependency rather than waving it through.

CI also now builds for thumbv7em-none-eabihf, because a no_std claim is
only worth what a target with no std to fall back on says about it.
The crate is now available under MIT OR Apache-2.0 OR MulanPSL-2.0.
"OR" is the operative word: a user picks one of the three and complies
with that one. Nothing is taken away from anyone who was already using
MIT or Apache-2.0.

Blackcat Informatics is the sole author of everything in this tree - two
commits, one author, no vendored corpora - so the grant is unilateral
and needed no coordination.

MulanPSL-2.0 deserves to be documented rather than listed. Its §6 makes
the Chinese text controlling where the Chinese and English versions
diverge, which is not true of the other two and is the kind of thing a
user should learn from LICENSING.md rather than discover later. That
file now also states what else differs: MIT grants no patent rights,
Apache-2.0 and Mulan both grant patents and both end that grant on
patent litigation, Mulan §3 withholds trademarks explicitly, and §4
carries the notice-retention obligation. Every claim there was read off
the shipped text, not recalled.

The canonical host (license.coscl.org.cn) is unreachable, so
LICENSE-MULAN is the official bilingual text taken from the SPDX
license-list-data mirror and pinned by SHA-256, which CI re-verifies on
every run.

vectors/ takes the same grant. Those files are generated by this
repository's own scripts from its own reference implementation, and
byte-identical copies are redistributed by purrdf and gmeow-gts; naming
this repository canonical for them settles a declaration that differed
between the three trees.

Released as 0.9.0. For a 0.x crate that is a semver-incompatible jump,
so a "0.1.3" requirement will not pick it up - which is the right
default given the crate it lands in is a different shape now, even
though the API and every rendering are unchanged.
Adopting a licence whose Chinese text is controlling while documenting
it only in English serves the wrong audience. LICENSING.md now carries a
中文说明 section: the three-way choice, a table of what actually differs
between the options, §6's Chinese-prevails rule, §4's notice obligation,
and the trademark position - each keyed to the article numbers and using
the licence's own Chinese terminology rather than a back-translation of
the English summary.

It is explicitly marked as a convenience summary that grants nothing;
the three licence texts govern, and the summary defers to them where
they disagree. blackcatinformatics.cn is given as the contact point.

The licence text itself is untouched, as it must be: the only URL inside
LICENSE-MULAN is the licence's own canonical host, which recipients are
directed to under the "How to Apply" terms, and those bytes are what the
CI digest check pins. Pointing it at our own infrastructure would
misrepresent the licence.
@paudley
paudley requested a review from ErinAudley as a code owner September 17, 2026 20:42
The API and both renderings are frozen, so there is no reason to ship
0.9.0 and then 1.0.0 a week later. Going straight to 1.0 states the
guarantee the README was already describing in the future tense.

What 1.0 commits to: the 64-emoji alphabet, the randomart character
ramp, and the public API do not change. A fingerprint already printed in
a log, a CLI banner or someone's notes has to keep meaning what it
meant, so altering either rendering would warrant a new crate rather
than a new major version. The frozen vectors under vectors/ are what
holds that line, and CI fails on drift.

Downstream requirements become visual-hashing = "1".
@paudley paudley changed the title Drop every runtime dependency, enforce the licensing claims, and add MulanPSL-2.0 1.0: drop every runtime dependency, enforce the licensing claims, add MulanPSL-2.0 Sep 17, 2026
@paudley
paudley merged commit 519c5bb into main Sep 17, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant