1.0: drop every runtime dependency, enforce the licensing claims, add MulanPSL-2.0 - #13
Merged
Merged
Conversation
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.
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".
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three related changes, released together as 1.0.0.
1. Zero runtime dependencies
cargo tree --edges normalnow prints onlyvisual-hashing. The ten-crategraph, the build script and the C-compiler requirement are gone.
The whole graph existed to serve four lines in
emojihashthat ask BLAKE3 fornine 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.rsimplements 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 anyprefix, 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:
vectors/pass byte-for-byte unchanged — theproof that matters;
tests/blake3_equivalence.rskeeps the upstreamblake3crate as a dev-onlyoracle and diffs against it across every block, chunk and subtree boundary
(~600 comparisons);
not rest solely on the oracle.
serde_jsongoes too, replaced by a strict reader for the generated vectorshape 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+allocand builds forthumbv7em-none-eabihfas well aswasm32.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.pygates the SPDX expression on every tracked file,agreement between headers and
REUSE.toml, the licence-text digests, and theno-runtime-dependencies property. It found a real gap on its first run:
REUSE.tomlcarried no header of its own.deny.tomlcovers 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.ORisoperative: 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.mddocuments rather than merely lists it — §6 makes the Chinesetext 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, soLICENSE-MULANis 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'sown scripts from its own reference implementation, and byte-identical copies
are redistributed by
purrdfandgmeow-gts; naming this repository canonicalsettles a declaration that differed between the three trees.
Verification
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.0is semver-incompatible, so neither consumer picks this upautomatically — for a
0.xrequirement the minor slot is the breaking one,so
"0.1.3"means>=0.1.3, <0.2.0. After this publishes:purrdf/Cargo.toml:187(workspace table)visual-hashing = "1"gmeow-gts/rust/Cargo.toml:58visual-hashing = "1"The public API and every rendering are unchanged.