paritytech / paritytech/web3-storage
Client trusts the provider-returned MMR root at commit/checkpoint time
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 12
- Forks
- 3
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 33
Description
StorageUserClient::commit accepts the provider's {mmr_root, start_seq, leaf_count, leaf_indices} without recomputing the expected root; CheckpointManager takes the majority of providers' claims (one primary ⇒ the provider's word). The root that reaches the chain originates from the provider being audited. Replica sync already follows the opposite, chain-first rule — never trust a provider-supplied root — the writer path has no equivalent.
#301 binds challenge proofs to (leaf_index, leaf_count) within the committed root; it cannot detect that the committed root itself is wrong.
Scenarios fixed
- Append — provider returns a root that omits/replaces/reorders/pads the committed leaves. Today: client checkpoints it; every challenge targets the provider's own tree, so all are defendable — silent data loss, no slash. Fix: client folds its own leaf hashes
(blake2_256(MmrLeaf{data_root, data_size, total_size}), all inputs known locally) into a per-bucket{peaks, leaf_count, total_size}accumulator; expected root ≠ returned root ⇒ reject before any checkpoint signature. - Modify (= append of the new version; directory repoints) — provider acks
B-v2but returns a root without it. Fix: same fold; expected root includes the new leaf ⇒ mismatch ⇒ reject. - Remove (
delete_before) — tree is rebuilt over survivors; peaks-only state cannot check the new root. TheDeletedchallenge defense is an admin-signed commitment, but no SDK path produces that signature yet (AdminClient::delete_beforeis a stub; deletion is off-chain by design) — whichever flow adds it has nothing to verify the provider-returned post-delete root against. Fix: fetch the surviving leaf hashes (32 B/leaf), verify the full list bags to the trusted pre-delete root, rebuild the expected post-delete root, compare — before signing. - Multi-user uploads — other writers' leaves interleave, so one writer cannot recompute the full root. Fix, per writer: fetch
GET /mmr_proof?bucket_id&leaf_indexfor each own leaf, checkproof.leaf.data_rootequals the uploaded data_root, verify with the boundverify_mmr_proof(proof, leaf_index, leaf_count, root)from #301, plus an MMR consistency check that the previously verified peaks embed in the new root (append-only history). Each writer vouches for its own leaves; jointly the root is covered. Inherent limit: content of other writers' leaves is verifiable only by their uploader.
Building blocks (already merged)
storage-primitives:bag_peaks,mmr_leaf_position, boundverify_mmr_proof(no_std, client-usable) — #301./commitreturns(mmr_root, start_seq, leaf_count, leaf_indices)atomically, signed — #301.- Provider endpoints:
GET /mmr_proof?bucket_id&leaf_index,GET /mmr_peaks,GET /mmr_subtree,POST /fetch_nodes. - Model tests in
provider-node/src/mmr.rs:client_computes_next_root_from_peaks,peaks_only_client_tracks_appends_not_removes.
Proposed fix
- Per-bucket accumulator
{peaks, leaf_count, total_size}. - Commit: fold own leaves, compare roots, check
leaf_indices/start_seq; reject on mismatch. - Interleaved writers: bound inclusion proofs + consistency check.
- Delete: rebuild expected post-delete root from fetched, root-verified survivor leaf hashes before producing the admin signature.
- Bootstrap/recovery: accept
GET /mmr_peaksiffbag_peaks(peaks) == on-chain checkpoint root.
Client state cost
- Writer: ≤ ~2 KB per bucket (≤ 64 peaks + 2 counters); reconstructible from the chain ⇒ stateless clients possible.
- Multi-writer proofs: ~2–4 KB per commit, transient.
- Delete (admin only): 32 B per live leaf, fetched on demand and verified against the trusted root; local storage optional (32 MB per 1M objects; one leaf per committed object).
Related: #301.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at StorageUserClient::commit and CheckpointManager, then review the model tests in provider-node/src/mmr.rs and the bound proof APIs from #301. Trace append, interleaved-writer, delete_before, and bootstrap flows; the work is done when client-side checks reject mismatched roots before signing and recovery accepts peaks only when they match the trusted on-chain root.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend, security
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100