Security model
View raw

Quiver security model

This document states Quiver's trust assumptions, the threat model, and the mitigations for each attack. Read it before deploying with real value.

Status: Quiver has a thorough test suite (unit, adversarial, fuzz) but has not had an independent third-party audit. Treat the assumptions below as load-bearing.


1. Actors and trust

ActorTrusted forCan it bias randomness?
Coordinator (contract)Correct, immutable executionNo — it only combines committed values
ProviderKeeping seed secret until each reveal; livenessNo (bias). Yes: withhold reveals (liveness)
RequesterIts own contribution; liveness (pull flow)No (bias). Yes: withhold its reveal (pull)
Coordinator ownerSetting protocol fee, pausing new requestsNo — cannot touch in-flight randomness or seeds

Core claim: as long as at least one of {provider, requester} is honest, the output is unbiased and unpredictable. The two contributions are each committed before either is revealed, and the result is a fixed function of both.

2. Cryptographic assumptions

  • keccak256 is pre-image resistant — nobody can invert the published commitment chain[N] to learn earlier values, and nobody can find a value hashing to a required anchor other than the true pre-image.
  • keccak256 is collision resistant — a provider cannot craft two distinct chains sharing a tip.
  • The combined output keccak256(userRandom ‖ providerRevelation ‖ blockHash) behaves as a random oracle over inputs neither party solely controls.

3. Threats and mitigations

3.1 Provider forging or altering a revealed value

Attempt: submit a providerRevelation other than the committed chain[N-k]. Mitigation: the coordinator recomputes keccak256^numHashes(providerRevelation) and requires it to equal the request's stored anchor. Any other value fails. The chain is fixed at registration; the provider has no freedom. ✔ Enforced in _verifyAndResolve.

3.2 Requester submitting a mismatched revelation

Attempt: reveal a userRandom different from the committed one to steer the result. Mitigation: keccak256(userRandom) must equal the stored userCommitment. ✔

3.3 Replay / double-reveal

Attempt: reveal the same request twice, or reuse a revelation. Mitigation: the request slot is deleted on reveal; a second reveal hits NoSuchRequest. Sequence numbers are monotonic and never reused. ✔

3.4 Provider selective withholding (the main liveness risk)

Setup: in the push flow, useBlockhash is off and the raw userRandom is public, so the provider can compute the outcome as soon as the request appears — and could decline to reveal outcomes it dislikes. It cannot bias (the value is fixed), but it can stall. Mitigations:

  • Economic/reputation: run providers you trust or that are staked/slashable; a provider that withholds is publicly observable (open request, no reveal).
  • Pull flow + useBlockhash: fold in blockhash(requestBlock), unknown at request time, so the provider cannot even predict the outcome when deciding to reveal.
  • Multiple providers: consumers can choose among providers; a withholding provider loses fees and reputation.
  • Timeouts (app-level): design your consumer to allow re-requesting from another provider if a request isn't fulfilled within a deadline.

3.5 Requester withholding (pull flow)

Attempt: a requester that dislikes the (secret-to-others) outcome never reveals. Impact: only its own request stalls; it already paid the fee. No effect on others. Note: the requester cannot know the outcome before revealing (it doesn't have the provider's value), so this is not even a useful attack — just abandonment.

3.6 Block-producer / sequencer manipulation

With useBlockhash: on an Arbitrum Orbit chain the sequencer produces blocks and influences blockhash. Folding blockhash adds entropy the provider can't predict, but a malicious sequencer colluding with a party could grind block hashes. Guidance: blockhash is a hardening input, not the root of trust — the two-party commitment is. For adversarial-sequencer threat models, rely on the provider+user commitments (both fixed) and treat blockhash as optional defense-in-depth. Never make blockhash the sole entropy source.

3.7 Callback griefing

Attempt: a consumer whose quiverCallback always reverts or burns gas, to block the provider's reveal tx / waste its gas. Mitigation: revealWithCallback completes all state changes first, then calls the consumer inside try/catch. A revert/out-of-gas is caught; the randomness is buffered (CallbackFailed) and the provider's tx still succeeds and earns the fee. The consumer recovers via retryCallback. The griefer harms only itself. Fletcher additionally simulates the reveal before sending. ✔

3.8 Reentrancy

Surface: the callback and native-token transfers (fee refunds, withdrawals). Mitigations: reveals, withdrawals, and retryCallback are nonReentrant; all follow checks-effects-interactions (request deleted / balances updated before external calls). request refunds overpayment after writing state and advancing the sequence number, so a reentrant request from the refund cannot reuse a sequence number. ✔

3.9 Gas-griefing via long hash chains

Attempt: force a reveal to compute an enormous number of hashes. Mitigation: numHashes is bounded at request time by the provider's maxNumHashes (itself capped by MAX_NUM_HASHES = 10_000). Prompt reveals keep it at 1. ✔

3.10 Seed compromise

Impact: anyone who learns a provider's seed can predict all its future values. Mitigations: treat the seed like a private key (see fletcher-operations.md); on suspicion, rotate immediately (rotateCommitment) — all future values use a fresh seed and are safe again. In-flight requests on the old chain remain as-committed.

3.11 Admin powers

The coordinator owner (Ownable2Step) can:

  • set the protocol fee (affects only future request pricing),
  • withdraw accrued protocol fees,
  • pause() new requests (in-flight reveals and retries stay enabled).

The owner cannot alter seeds, forge values, change in-flight requests, or seize provider fees. Use a multisig/timelock for the owner in production, and consider renouncing pause once stable. Two-step ownership transfer prevents fat-fingering the owner to an inaccessible address.

4. Randomness quality notes

  • Uniformity & range reduction. The raw output is a uniform 256-bit value. Reduce to a range with modulo — the modulo bias for a small range over 2²⁵⁶ is ~range / 2²⁵⁶, i.e. cryptographically negligible. For a d6, bias ≈ 1/2²⁵³.
  • Independence. Each request consumes a distinct provider value and a distinct user value, so outputs are independent. Never reuse a userRandomNumber across requests.
  • Do not derive multiple "independent" draws by re-hashing one output with attacker- known inputs. If you need N independent values, request N times or expand with a domain-separated KDF over the single verifiable output.

5. Deployment hardening checklist

  • Owner is a multisig (and ideally behind a timelock).
  • Providers you rely on are ones you operate or that are economically accountable.
  • Consumers treat fulfillment as async and tolerate/retry withholding.
  • Consumer callbacks are lean, idempotent, and never assume they can't be retried.
  • Seeds are generated with a CSPRNG, stored like private keys, and rotation is rehearsed.
  • For high-value use, prefer the pull flow with useBlockhash, or add provider staking.
  • Independent audit before mainnet value at scale.

6. Responsible disclosure

Found a vulnerability? Do not open a public issue. Contact the maintainers privately with a description and, ideally, a proof-of-concept. (Wire up a security contact / bug-bounty before mainnet.)