paritytech / paritytech/web3-storage

Client trusts the provider-returned MMR root at commit/checkpoint time

Open
#302 0 comments 0 reactions 0 assignees View on GitHub

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

  1. 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.
  2. Modify (= append of the new version; directory repoints) — provider acks B-v2 but returns a root without it. Fix: same fold; expected root includes the new leaf ⇒ mismatch ⇒ reject.
  3. Remove (delete_before) — tree is rebuilt over survivors; peaks-only state cannot check the new root. The Deleted challenge defense is an admin-signed commitment, but no SDK path produces that signature yet (AdminClient::delete_before is 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.
  4. 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_index for each own leaf, check proof.leaf.data_root equals the uploaded data_root, verify with the bound verify_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, bound verify_mmr_proof (no_std, client-usable) — #301.
  • /commit returns (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

  1. Per-bucket accumulator {peaks, leaf_count, total_size}.
  2. Commit: fold own leaves, compare roots, check leaf_indices/start_seq; reject on mismatch.
  3. Interleaved writers: bound inclusion proofs + consistency check.
  4. Delete: rebuild expected post-delete root from fetched, root-verified survivor leaf hashes before producing the admin signature.
  5. Bootstrap/recovery: accept GET /mmr_peaks iff bag_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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.