# 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

| Actor | Trusted for | Can it bias randomness? |
| --- | --- | --- |
| **Coordinator** (contract) | Correct, immutable execution | No — it only combines committed values |
| **Provider** | Keeping `seed` secret until each reveal; liveness | **No** (bias). Yes: withhold reveals (liveness) |
| **Requester** | Its own contribution; liveness (pull flow) | **No** (bias). Yes: withhold its reveal (pull) |
| **Coordinator owner** | Setting protocol fee, pausing new requests | No — 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 `delete`d 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](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.)
