# Quiver protocol design

This document explains **how Quiver produces verifiable randomness** and **why it is
secure**. It is the reference for auditors and integrators who want to understand the
mechanism rather than just call the API.

Quiver is an on-chain adaptation of the classic two-party **commit–reveal** protocol,
optimized with a **hash chain** so a provider can serve an effectively unbounded stream
of requests from a single on-chain commitment. This is the design pioneered by
[Pyth Entropy](https://docs.pyth.network/entropy/protocol-design); Quiver is an
independent implementation branded and tuned for Robinhood Chain.

---

## 1. The problem with "just use blockhash"

Randomness derived from a single on-chain source that a privileged actor controls is
biasable:

- `blockhash` / `block.prevrandao` are chosen (or at least influenced) by the block
  producer, who can re-roll by discarding blocks they dislike.
- A single-party oracle that "posts a random number" can pick whichever value benefits
  it, or front-run/withhold based on the outcome.

The fix is to require **two independent contributions**, each **committed before either
value is known**, so that:

- the result is a deterministic function of both contributions, and
- neither party can change its contribution after seeing the other's.

Then a fair outcome holds as long as **at least one** contributor is honest.

## 2. Commitments

### The provider's hash chain

A provider picks a secret 32-byte `seed` and a chain length `N`, then computes

```
chain[0] = seed
chain[i] = keccak256(chain[i-1])          for i = 1 … N
```

It publishes **only the tip**, `commitment = chain[N] = keccak256ᴺ(seed)`, via
`register(...)`. Every other value stays secret.

Only the tip `chain[N]` is public. The value the provider reveals for the *k-th*
request it serves is a pre-image deeper in the chain:

```
revelation(k) = chain[N - k]
```

Why this works: because `keccak256` is one-way, nobody can compute `chain[N-1]`,
`chain[N-2]`, … from the public `chain[N]`. But **verification is trivial**: given a
claimed `revelation(k) = chain[N-k]`, hashing it `k` times must reproduce the tip:

```
keccak256ᵏ(chain[N-k]) == chain[N] == commitment   ✓
```

The provider therefore cannot lie about a revealed value — any wrong value fails the hash check
— and cannot change it later, because it's fixed the moment the tip is published.

### The requester's contribution

The requester picks its own value `userRandomNumber` and commits
`userCommitment = keccak256(userRandomNumber)`. It reveals the pre-image only at reveal
time (pull flow) or supplies it up-front (push flow — see §6).

## 3. Producing the random number

When **both** revelations are on-chain, the coordinator computes:

```solidity
randomNumber = keccak256(abi.encodePacked(userRandom, providerRevelation, blockHash));
```

`blockHash` is `blockhash(requestBlock)` when the requester set `useBlockhash`, else
`bytes32(0)`. Because `keccak256` is a random oracle over inputs neither party fully
controlled, the output is uniform and unpredictable to both.

## 4. Bounded-gas verification (the anchor trick)

Verifying request `k` naïvely costs `k` hashes — unbounded as the chain is consumed.
Quiver keeps reveal gas **proportional only to the number of outstanding requests**.

Each provider record tracks a moving **anchor**:

- `currentCommitment` — the deepest revealed chain value so far, `chain[N - b]`
- `currentCommitmentSequenceNumber` (`b`) — the sequence number that anchor corresponds to

When a request is assigned sequence number `s`, it **stores its own anchor**:

```
req.providerCommitment = currentCommitment        // chain[N-b] at request time
req.numHashes          = s - b                     // hashes needed to verify
```

At reveal, the coordinator checks `keccak256^(req.numHashes)(providerRevelation) ==
req.providerCommitment`. On success it advances the anchor forward
(`currentCommitment ← providerRevelation`, `b ← s`) **iff** `s` is ahead of the current
anchor. So:

- **Sequential reveals** cost exactly **1 hash** each.
- **Out-of-order reveals** still verify against each request's *stored* anchor, and never
  corrupt the moving anchor (it only ever moves forward).
- The maximum work for any reveal is bounded by `maxNumHashes`, enforced at request time
  (`s - b ≤ maxNumHashes`), which also protects revealers from gas griefing.

> This is why a request stores `numHashes` and a 32-byte anchor rather than re-deriving
> from the tip: it makes every reveal self-contained and order-independent.

### Worked example (`N = 8`)

| Action                         | `sequenceNumber` (next) | anchor `b` | `currentCommitment` |
| ------------------------------ | ----------------------- | ---------- | ------------------- |
| `register(commitment=chain[8])`| 1                       | 0          | `chain[8]`          |
| request → seq 1 (anchor 0, 1 hash) | 2                   | 0          | `chain[8]`          |
| reveal seq 1 → `chain[7]`      | 2                       | 1          | `chain[7]`          |
| request → seq 2 (anchor 1, 1 hash) | 3                   | 1          | `chain[7]`          |
| reveal seq 2 → `chain[6]`      | 3                       | 2          | `chain[6]`          |

Reveal seq 1 checks `keccak(chain[7]) == chain[8]`; reveal seq 2 checks
`keccak(chain[6]) == chain[7]`. Each is one hash.

## 5. Rotation

A chain is finite (`N` values). A provider extends or replaces it with
`rotateCommitment(newCommitment, …, newChainLength, …)`:

- The new chain is anchored just below the next sequence number
  (`anchor = sequenceNumber - 1`), so the very next request verifies in one hash.
- **In-flight requests are unaffected** — they carry their own stored anchors from the
  old chain and still verify and reveal normally.
- Rotation is also the recovery path if a `seed` is suspected compromised: rotate to a
  fresh seed and all *future* values are safe again.

Fletcher tracks chain **segments** (each `{seed, anchorSequenceNumber, length}`) so it
can still reveal old-segment requests after a rotation.

## 6. Two flows: pull and push

### Pull flow — `request` → `reveal`

1. Requester commits `userCommitment = keccak256(userRandom)`, keeping `userRandom` secret.
2. `request(provider, userCommitment, useBlockhash)` reserves sequence `s`.
3. Requester obtains `providerRevelation` for `s` (from the provider's endpoint) and calls
   `reveal(provider, s, userRandom, providerRevelation)`, which returns the randomness.

The requester's value stays secret until it chooses to reveal, so it is a genuine
independent commitment. Optionally fold in `blockhash` for extra provider-unpredictability.

### Push flow — `requestWithCallback` → `revealWithCallback` → `quiverCallback`

1. `requestWithCallback(provider, userRandom)` — the raw `userRandom` is passed and the
   coordinator stores `keccak256(userRandom)`. It is **emitted in the event** so the
   keeper can fulfill.
2. The keeper observes `RandomnessRequested`, computes the provider's reveal, and calls
   `revealWithCallback(provider, s, userRandom, providerRevelation)`.
3. The coordinator produces the randomness and invokes your contract's
   `quiverCallback(s, provider, randomNumber)`.

**Is emitting `userRandom` a problem?** No. Protocol safety rests on the provider's revealed
value being committed *before* the request — the provider cannot re-choose it. The requester's
value contributing entropy does **not** require secrecy in the push flow. The one residual
consideration: because `useBlockhash` is off in the push flow, the provider *can compute*
the outcome once it sees the request, and could choose to withhold the reveal (liveness,
not bias). Mitigations are covered in [security.md](security.md) (reputation/staking,
using the pull flow with `useBlockhash`, and callback buffering so a griefing consumer
can't block a provider).

## 7. Callback safety

`revealWithCallback` performs all state changes (advance anchor, delete the request)
**before** calling the consumer, and wraps the call in `try/catch`:

- **Success** → `CallbackSucceeded`.
- **Revert / out-of-gas** → the randomness is written to a retry buffer and
  `CallbackFailed` is emitted. Anyone can later call `retryCallback(provider, s)` to
  re-deliver it. The provider is paid and unblocked regardless of a misbehaving consumer.

This makes fulfillment robust: a buggy or malicious consumer harms only itself.

## 8. What Quiver guarantees — and what it doesn't

**Guarantees (given an honest coordinator deployment):**

- The output is a fixed function of two pre-committed values; **neither party alone can
  bias it**.
- A provider cannot forge or alter a revealed value (hash-chain verification).
- A reveal cannot be replayed (the request is deleted on reveal).
- Randomness for a failed callback is never lost (retry buffer).

**Non-guarantees / trust assumptions:**

- **Liveness** depends on at least one party revealing. A provider (or, in the pull flow,
  a requester) can refuse — this cannot bias results but can delay them.
- The **push flow without `useBlockhash`** lets the provider predict (not change) the
  outcome, enabling selective withholding. Use provider reputation/staking, or the pull
  flow with `useBlockhash`, where stronger guarantees are needed.
- Security assumes `keccak256` pre-image and collision resistance, and a provider that
  keeps its `seed` secret until each reveal.

See [security.md](security.md) for the full threat model and mitigations.
