paritytech / paritytech/web3-storage

Provider hot/cold key separation: document public_key purpose, add restricted proxies, close operational gaps

Open
#300 0 comments 0 reactions 1 assignee View on GitHub

@ilchu is already working on this.

Since Aug 3, 2026.

Dominant language
Rust
Stars
12
Forks
3
Avg merge
2d 2h
Merged PRs (30d)
33

Description

Background

ProviderInfo.public_key exists but its purpose is documented nowhere — only mechanics ("raw bytes, multiple key types"). Investigation (design discussion, 2026-07-18) concluded it is exactly the right mechanism for provider hot/cold key separation and should be kept and documented as such:

  • Cold account: holds stake, signs rare admin extrinsics (register, settings, deregister).
  • Hot signing key (public_key, lives on the always-online provider server): signs off-chain payloads (CommitmentPayload, CheckpointProposal, replica sync roots).
  • Hot proxy key: submits time-critical operational extrinsics via pallet_proxy without spend authority.

Server compromise then yields: false commitments (self-slash exposure only, never fund theft), filtered extrinsics, loss of two small fee floats. Stake stays cold.

Key rotation is deliberately not supported. Rotating a key under an active agreement invalidates client-held signed commitments (commit receipts, challenge_offchain evidence) — every mitigation examined (previous-key grace window bounded by MaxNonceAge, per-agreement key freezing with re-anchoring at extension) adds machinery disproportionate to the benefit. Compromise playbook instead: cold key removes the proxy (remove_proxy, immediate), stops accepting agreements via settings, winds down existing agreements, then replaces the key.

Tasks

1. Document the key's purpose
  • Rustdoc on ProviderInfo.public_key and register_provider: hot signing key, may (and for hot/cold setups should) differ from the account key; account key can be used where separation isn't wanted.
  • Design doc (implementation doc): new "Provider key management" subsection — threat model, cold/hot split, compromise playbook, explicit no-rotation decision + rationale (the extension/evidence-invalidation problem).
  • Resolve the 64-byte inconsistency: registration accepts 32/33/64 (register_provider_internal), but verify_signature only handles 32/33 — a 64-byte key registers fine and fails at first use. Either drop 64 at registration or document/implement what it's for ("reserved for future schemes" comment is stale either way).
2. Proxy support (runtime config, both runtimes)
  • Add pallet_proxy with custom ProxyType::{Any, NonTransfer, StorageProvider} (+ optional CancelProxy — only meaningful if delayed proxies are ever used; we start with delay 0). Pallet requires ProxyType::default() to be the most permissive variant (Any).
  • StorageProvider filter = explicit whitelist: respond_to_challenge, provider_checkpoint, confirm_replica_sync, Utility::batch/batch_all/force_batch. Everything else denied — notably Proxy::* (nested-proxy origin reset is the known filter-escape), Balances::*, deregister_provider/complete_deregister/cancel_deregister, add_stake, update_provider_settings, key replacement.
  • Config: ProxyDepositBase/Factor, MaxProxies, MaxPending, BlockNumberProvider = RelaychainDataProvider (delays in relay blocks; precedent: AssetHub). Precedent for the filter shape: AssetHub StakingOperator (polkadot-fellows/runtimes#1033, tightened in #1093).
  • Unit tests asserting the filter lattice (what StorageProvider can/cannot call), mirroring AssetHub's proxy filter tests.
3. Gaps to close for hot keys to actually work
  • replace_signing_key extrinsic, cold-signed, allowed only when the provider has zero active agreements (exact predicate TBD — an active-agreement count or the committed_bytes bookkeeping; mind expired-but-unsettled agreements). Excluded from the proxy filter.
  • Provider node: submit operational extrinsics as proxy(cold_account, call); config gains cold-account address + proxy signer. Must watch ProxyExecuted { result } — the proxy extrinsic succeeds even when the inner call is filtered/fails; a silently failed respond_to_challenge is a slash. Alert on inner failure.
  • Fee float management: hot proxy account pays all fees (incl. inner-call weight) from its own balance; provider node should monitor the float and alert/top-up below threshold. (Announcement deposits would also land on the hot account if delayed proxies are ever used.)
4. Restricted hot-key setup as the default in UI and tooling
  • Provider dashboard (user-interfaces/provider): registration flow defaults to hot/cold — generate/accept a separate server signing key, register public_key ≠ account key, guide through add_proxy(hot, StorageProvider); single-key setup demoted to an explicit "advanced/dev" option. Show proxy status, fee float, and ProxyExecuted failures.
  • SDKs / test helpers (packages/layer0, packages/sdk, user-interfaces/shared/test-helpers, Rust client): registration helpers take (cold signer, hot signing key, hot proxy key) as the primary shape; demo/CI flows may keep single-key via the dev signers but the documented default is split keys.
  • Docs (provider setup guides, EXTRINSICS_REFERENCE, README quick starts): hot/cold as the canonical setup; single-key documented as dev-only shortcut.
  • Optional QoL: expose pallet-proxy view functions (check_permissions) so UIs can display what the hot key may call instead of hardcoding.

Non-goals

  • Key rotation under active agreements (see Background).
  • Fee sponsorship via pallet-meta-tx (merged upstream in polkadot-sdk#6428, not deployed anywhere yet) — possible future alternative to the hot fee float; out of scope.

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.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.