fix(crypto): bring chia-bls onto the chia-wallet-sdk ceiling (0.36.1) - #57
Conversation
`digstore-crypto` declared `chia-bls = "=0.45.0"`, an exact pin ABOVE the chia-wallet-sdk ceiling. The SDK's latest is 0.36.0 and resolves the 0.36.1 primitives, so the 0.4x line is unreachable for every other crate in this workspace. This was the third of the three splits tracked by the epic, and it put four `chia-bls` lines in one lock (0.28.2 / 0.36.1 / 0.42.1 / 0.45.0). The pin carried no stated reason: it was introduced by 5fe545e (2026-06-08, "pin chia-bls=0.45.0 and scaffold digstore-crypto manifest"), the commit that CREATED the manifest — born with the scaffold, uncommented, and predating the SDK ceiling being understood. Moving to 0.36.1 also drops the orphaned chia-sha2 / chia-traits / chia_streamable_macro 0.45.1 line, taking `chia-bls` from four resolved lines to three. The two survivors are deep transitives of the SDK itself (0.42.1 via `chialisp`, 0.28.2 via `clvmr`), not declarations here. Nothing caught this for ~11 weeks because it CANNOT fail loudly: this crate wraps every chia-bls type in an opaque newtype and no `chia_bls` type reaches its public API, so a split would silently produce two BLS implementations in one workspace with no diagnostic. The frozen known-answer vectors are the only gate on that, which is why they are the whole proof here. The AugScheme vectors in `tests/fixtures/bls_vectors.json` reproduce BYTE-UNMODIFIED under 0.36.1: `committed_bls_fixture_matches_generated` recomputes every keypair and signature from the live crate via `bls_keygen` / `bls_sign` over four fixed seeds and compares against the committed file. The 0.45 -> 0.36.1 move therefore changed no cryptographic output and required no source change — the API this crate uses (sign / verify / SecretKey::from_seed / to_bytes / from_bytes) is identical across both lines. `digstore-chain` gains a COMMENT ONLY, no version change: its `chia-sdk-*` declarations cannot reach the 0.36 ceiling yet. `datalayer-driver` 4.0.0 is the latest published release and hard-requires `chia-wallet-sdk ^0.34.0`; because this crate passes DataLayer singleton types across the SDK boundary, moving it alone resolves `chia-sdk-driver` twice and `singleton.rs` fails to compile (`DataStore` 0.34 vs `Datastore<DataStoreMetadata>` 0.36, plus `Verification` / `VerificationAsserter` / `VerifiedData` removed in 0.36). Bridging the two lines with a shim would be the byte-drift class §4.1 exists to prevent, so the hold is recorded in the manifest instead. The rest of the family has already moved to ^0.36 (dig-wallet-backend 0.31.0, dig-cat/dig-tips/dig-offers 0.3.0, dig-options 0.4.0); datalayer-driver is the sole holdout. Patch bump: dependency-only, no behaviour change. Workspace 0.28.0 -> 0.28.1 with the coupled `[workspace.dependencies] digstore-core` declaration moved in lockstep, and digstore-crypto 0.1.0 -> 0.1.1. Refs DIG-Network/dig_ecosystem#3161 Co-Authored-By: Claude <noreply@anthropic.com>
loop-security audit — INTERIM (static leg complete, build leg running)Auditing head Confirmed so farThe split was real and in-tree. At the merge-base, Point 3 (opaque newtypes) — VERIFIED, exhaustively. Grepped the whole crate for Consequence, and it is the important one: the compiler structurally could not have caught this Point 2 (KAT is not circular) — VERIFIED. Stronger still, and worth recording because the lane undersold it:
Point 4 (pin provenance) — VERIFIED, and the framing sharpens. One detail that strengthens the evidence rather than weakening it: Still runningVersion-delta review of the |
loop-security audit — INTERIM 2: the version-delta review (point 1)Head Fetched
Nothing on the keygen / sign / verify / serialize path differs at all. The one security-relevant delta, and it runs in the downgrade direction
0.45.0 replaced that whole body with It is NOT reachable here, and I checked rather than assumed. Three independent reasons, all at
So this is defense-in-depth, not a live vulnerability, and I am not gating on it. Recommend a Supporting facts
Point 5 — lock collapse, verified from the resolved lockCounted
The 0.45.0 line and its private 0.45.1 subtree ( Point 4 addendum — the reversion is clean, and its comment is accurate
Every factual claim the comment makes checks out against the crates.io index:
One correction to the lane's reasoning (does not change the verdict)The PR reasons that the split could not fail loudly and that "only the golden KAT could" catch it. The manifest comment itself is honest about this ("pin the cryptographic output"), so nothing in the Remaining: the 3.5 installed-binary + |
loop-security VERDICT: PASSAudited head: No security defect in this diff. No LIVE vulnerability. Two defense-in-depth items below — neither gates; The fact that frames everything elseThis PR changes ZERO Rust source files. The four changed files are Area-by-area
1 — the delta is fully characterized. I diffed the two published crate sources fetched from Nothing on the keygen / sign / verify / serialize path differs at all. So there is no mechanism by Supporting facts: no RUSTSEC advisory exists for any chia crate (the advisory-db has no chia directory), 2 — the KAT recomputes, and I ran it. The evidence is stronger than the PR claims. The committed 6 — I ran the end-to-end leg rather than taking it on report. Separately, Defense-in-depth (NOT gating — follow-up tickets)D1 — MEDIUM. The downgrade re-adopts a Debug impl that renders the raw BLS private key. Unreachable Checked, not assumed. It is unreachable for three independent reasons, all in
There is no state and no attacker action that reaches the impl, so no exploit scenario exists. I am not D2 — LOW. Nothing mechanical prevents this drift recurring. One correction to the PR's reasoning, The manifest comment is honest about this (it says it pins the cryptographic output), so nothing in the D3 — trivial. Supporting verification detailPoint 3 — opaque newtypes, verified exhaustively. Point 4 — pin provenance. Point 5 — lock collapse, verified from the resolved lock. I counted chia-bls package blocks directly The reversion is clean and its comment is accurate. One lock rider, benign. What I could NOT verify, and why
Merge preconditions (observed, not acted on)6/6 required contexts present and SUCCESS by name — Process noteRead-only throughout; I made no edit, push, merge or undraft. All probes ran in a private worktree cut |
DRAFT — DO NOT MERGE. Handing back for the gate round.
Slice of https://github.com/DIG-Network/dig_ecosystem/issues/3161 for
DIG-Network/digs.The target is the chia-wallet-sdk ceiling, not crates.io latest: the SDK's latest is
0.36.0 and it resolves the 0.36.1 primitives, so the 0.48.x primitives are unreachable.
A crate already on the ceiling is compliant; being above it is the defect.
Two fixes were in scope. One landed. One is BLOCKED upstream and is reported rather than
forced — the delta does not line up, and bridging two SDK lines with a shim would be the
byte-drift class §4.1 exists to prevent.
1.
digstore-crypto— LANDED:chia-bls = "=0.45.0"->"0.36.1"This was the third of the three splits the epic is named for, and the one above the ceiling.
Why the exact pin existed
No reason was ever recorded, and the pin does not appear to have been deliberate.
git log -Lon the line resolves to a single commit:That is the commit that created the manifest — the pin was born with the scaffold, carried
no explanatory comment, and predates the SDK ceiling being understood. Nothing in the crate
depends on 0.4x behaviour. I judged the reason stale and lowered it; the replacement carries
a comment stating the ceiling so the next reader does not have to re-derive this.
Why nothing caught it for ~11 weeks — the important part
digstore-cryptowraps every chia-bls type in an opaque newtype(
pub struct PublicKey(ChiaPublicKey), private field) and returnsdigstore_core::Bytes48/
Bytes96. Verified: nochia_blstype reaches its public API, and no consumer cratenames
chia_blsat all.So a wrong version here fails silently, not loudly. The compiler structurally cannot see
the split — it would simply have produced two different BLS implementations in one workspace
with no diagnostic. The only thing standing between that and a signature-verification
divergence is the frozen known-answer vector set. That is the direction this fails in, and it
is why the golden evidence below is the whole proof rather than a formality.
BLS evidence — goldens reproduce BYTE-UNMODIFIED
crates/digstore-crypto/tests/fixtures/bls_vectors.jsonis unchanged in this diff(
git statusreports nothing undertests/fixtures/).committed_bls_fixture_matches_generatedis load-bearing, not vacuous — I checked thatBlsFixtureSet::generate()genuinely recomputes: it walks four fixed seeds and callsbls_keygen/bls_signagainst the live crate, then compares to the parsed on-diskfile. It does not reload the fixture. Any change to keygen or AugScheme signing output would
change
pubkey_hex/signature_hexand fail the comparison.The 0.45 -> 0.36.1 move therefore altered no cryptographic output and required no source
change — the API this crate uses (
sign/verify/SecretKey::from_seed/to_bytes/from_bytes) is identical across both lines. This matches thedig-capsuleprecedent.Lock effect
Removes the whole orphaned 0.45 family:
chia-bls0.45.0,chia-sha20.45.1,chia-traits0.45.1,chia_streamable_macro0.45.1.chia-blslines go 4 -> 3(was 0.28.2 / 0.36.1 / 0.42.1 / 0.45.0).
The two survivors are not this repo's declarations and are not fixable here — they are
deep transitives of the SDK itself, exactly the case CLAUDE.md §2.4b calls out:
2.
digstore-chain— BLOCKED, comment only, no version changeIntended:
chia-sdk-coinset/chia-sdk-test/chia-sdk-types/chia-wallet-sdk0.34 -> 0.36.0. I made that change, measured it, and reverted it.
datalayer-driver4.0.0 is the latest published release and hard-requireschia-wallet-sdk ^0.34.0. It is not optional here:digstore-chainpasses DataLayersingleton types straight across the SDK boundary, so moving only this crate resolves
chia-sdk-drivertwice and the build fails with the two lines meeting insingleton.rs:The rest of the family has already moved — the holdout is singular:
chia-wallet-sdkreqdig-wallet-backend^0.36dig-cat/dig-tips/dig-offers^0.36dig-options^0.36datalayer-driver^0.34.0Unblocked by a
datalayer-driverrelease onto 0.36; then this crate anddig-wallet-backendmove together in one cascade. Recorded as a manifest comment in the crate's existing
house style (it already documents a deliberate
dig-constantshold for the samesplits-the-tree-rather-than-advancing-it reason), so the next lane does not repeat this.
This needs a child ticket on the epic for the datalayer-driver release.
Blast radius checked
digstore-cryptois depended on by 9 crates (dig-client-wasm, cli, compiler, guest,host, prover, remote, stage, store) — effectively the whole workspace. Its public surface is
chia-free newtypes, so the radius is behavioural (BLS output), not type-level; the golden KAT
is the gate on that radius and it is green.
digstore-chainis depended on by dig-resolver, cli, subscription — untouched apart from acomment.
Verification
cargo fmt --all --checkcargo clippy --workspace --all-targets --locked -- -D warnings -A …(CI's exact line)cargo build --workspace --lockedcargo test --workspace --lockedcargo deny check advisories bans sourcesadvisories ok, bans ok, sources okscripts/check-workspace-dep-versions.shall in-repo workspace dependencies declare version 0.28.1Installed binary (§3.5)
Guest wasm built first (
cargo build -p digstore-guest --target wasm32-unknown-unknown --release), thencargo install --path crates/digstore-cli --force --locked(exit 0):scripts/local-push-test.sh— 5/5 pass, push/pull/clone over a real in-process §21server. This exercises the BLS request-signing path (
sign_request/verify_request)end-to-end under the new
chia-bls, includingclone_rejects_unauthenticated_or_forged_head.digs compileroundtrip on the installed artifact (no chain, no spend):deterministic root
bb53bead…8d77, exit 0.initwas deliberately not exercised — it mints on mainnet and nothing here needed it.Leftover agent worktree
.claude/worktrees/agent-afa366a6361dbae8c/confirmed untracked and gitignored(
.gitignore:17 .claude/worktrees/), so it is not part of any published crate. Its stale=0.45.0copy was left alone as instructed. No finding.Ceiling audit
.claude/scripts/chia-ceiling-audit.shreadsorigin/main, not the working tree, so itstill reports the pre-merge state — currently 5 violations for
apps/digs.chia-bls).chia-sdk-*/chia-wallet-sdkentries indigstore-chain, all blocked on thedatalayer-driverrelease above.Version
Patch — dependency-only, no behaviour change. Workspace
0.28.0->0.28.1, with the coupled[workspace.dependencies] digstore-coredeclaration moved in lockstep (CI enforces it);digstore-crypto0.1.0->0.1.1.