# Noone Protocol — v2 Addendum

> **Status:** authoritative supplement to `NOONE_PROTOCOL.md` (v1).
> Where this document conflicts with v1, **v2 wins**.
> Calibrated against measurements taken on the live mesh during the 2026-05-06
> deploy cycle. Numbered sections below align with their v1 counterparts so the
> diff is minimal; new sections (§ 17–§ 19) are appended.
>
> **What changed from v1 → v2:**
> 1. `FLOOR_MIN` lowered from 4 → 2 raw bits (bootstrap).
> 2. Argon2id workfactor confirmed at `m = 64 KiB, t = 2, p = 1` (lighter
>    than v1's "spec-grade m = 4096 KiB" — chosen for browser feasibility).
> 3. Mesh expanded from 7 continent-DOs to **346 colo-DOs** (one Durable
>    Object per Cloudflare PoP).
> 4. **Forge work-equivalence math** spelled out (32-gas-bundle == 1 common
>    stamp by expected work).
> 5. **Gossip = 30 s** justified by the cross-region double-spend window /
>    DO compute / RTT triple constraint.
> 6. Battle tests + live TPS numbers added (§ 18).

---

## Constants — source of truth

| Symbol | Value | Defined in |
|---|---|---|
| `EPOCH_DURATION_S` | 86,400 (24 h) | `index.js:64` |
| `GOSSIP_INTERVAL_MS` | 30,000 | `gossip.js:38` |
| `BUDGET_PER_PULL` | 4,096 | `gossip.js:39` |
| `FORGES_TARGET_PER_EPOCH` | 4,096 / colo | `difficulty.js:36` |
| `MAX_DELTA` | ±2 bits / epoch | `difficulty.js:35` |
| `FLOOR_MIN` | 2 (was 4 in v1) | `difficulty.js:33` |
| `FLOOR_MAX` | 32 | `difficulty.js:34` |
| `ARGON2_OPTS` | `{ t: 2, m: 64, p: 1, dkLen: 32 }` | `pow.js:14` |
| `POW_SALT` | `"noone-pow-v1"` (kept for compat) | `pow.js:15` |
| `CHANGE_BUNDLE` | 32 | `forge.js` |
| `SPENT_RETENTION` | 2 epochs | `forge.js:105`, `index.js:589` |
| `TIER_BIT_GAP` | 4 raw bits per tier-step | `token-name.js:37–48` |
| `RATE_LIMIT_BURST` | 10 events / pubkey | `wrangler.toml: RL_BURST` |
| `RATE_LIMIT_PERIOD` | 1 minute | `wrangler.toml: RL_PER_SEC` |
| `MESH_COLOS` | 346 (Cloudflare PoP count) | `region.js: ALL_REGIONS` |

---

## §6 (revised). Mining numbers — exact rates

### Per-hash cost

```
Argon2id over a 32-128 byte input with (t=2, m=64 KiB, p=1) costs
~2.5 ms on a modern laptop V8 isolate, ~50 ms on a low-end phone
browser, and ~2.5 ms on a Cloudflare Workers V8 isolate.
```

### Gas mining yield

For an Argon2id digest interpreted as a uniform-random 256-bit value, the probability of the leading `k` bits being zero is `2⁻ᵏ`. So:

```
expected_hashes_per_gas(k) = 2^k
expected_seconds_per_gas(k) = 2^k / hashrate_HPS
```

At `FLOOR_MIN = 2`:

| Device class | H/s | Gas / sec | Time to 32 gas | Time to 1 forge |
|---|---|---|---|---|
| Modern laptop | 500 | 125 | 256 ms | 256 ms + verify |
| Modern phone | 50 | 12.5 | 2.6 s | 2.6 s |
| Low-end phone | 20 | 5 | 6.4 s | 6.4 s |

### Stamp lucky-hit yield (single hash producing an stamp directly)

Tier minimums in **raw bits** (= effective bits − 15):

| Tier | Effective bits | Raw bits | Odds per hash | Expected hashes |
|---|---|---|---|---|
| Common  | 24 | 9  | 2⁻⁹ ≈ 1/512    | 512 |
| Uncommon| 28 | 13 | 2⁻¹³ ≈ 1/8 192 | 8 192 |
| Rare    | 32 | 17 | 2⁻¹⁷ ≈ 1/131 K | 131 072 |
| Epic    | 36 | 21 | 2⁻²¹ ≈ 1/2.1M  | 2 097 152 |
| Epic+   | 38 | 23 | 2⁻²³ ≈ 1/8.4M  | 8 388 608 |
| Legendary | 42 | 27 | 2⁻²⁷ ≈ 1/134M | 1.34 × 10⁸ |
| Mythic  | 54 | 39 | 2⁻³⁹ ≈ 1/550G  | 5.50 × 10¹¹ |

At a phone's 50 H/s a common-tier lucky hit is ~10 s expected; rare is ~44 minutes; mythic is "hundreds of years." Most users will mine stamps via the FORGE path, not lucky-hits.

---

## §7 (revised). Forging — the 32-gas → 1-stamp ratio is mathematical

### The work-equivalence proof

Pick any two raw-bit difficulties `g` (gas tier) and `i` (stamp tier) with `i > g`.

```
P(hash clears g) = 2⁻ᵍ
P(hash clears i) = 2⁻ⁱ
expected_hashes(g) = 2ᵍ
expected_hashes(i) = 2ⁱ

⇒ ratio(i, g) = 2^(i − g)
```

For `g = 4` (raw, the original floor before bootstrap drop) and `i = 9` (common stamp raw):

```
ratio = 2^(9 − 4) = 2⁵ = 32
```

That's where `CHANGE_BUNDLE = 32` comes from. **A 32-gas bundle costs the same expected hashes as one common-tier lucky hit.**

```
32 × 2⁴ = 2⁵ × 2⁴ = 2⁹  ✓
```

So the forge protocol is "convert 32 small wins into one big win" with **identical expected work**. No discount, no premium. The user just trades variance — gas mining is high-frequency low-payout, forging is low-frequency high-payout, but the long-run hash budget is the same.

### Tier rarity is exactly 16× per step

Tier raw-bit minimums step by 4 (`9, 13, 17, 21, 23, 27, 39`). 4 raw bits = 2⁴ = 16× rarer per step. So:

```
P(common)    : P(uncommon) : P(rare)    = 16 : 1 : 1/16
expected work: 1×          : 16×        : 256×
```

This is the same exponential schedule Bitcoin's halving uses, except noone halves with each tier crossed instead of every 4 years.

### Why exactly 32 (not 16 or 64)?

`FLOOR_MIN = 4` (v1 design constant) and common-stamp-raw = 9. The protocol designers picked 4 → 9 as the gap because it makes the bundle size a power of two, divisible into binary trees efficiently for the merkle root. 32 leaves → 5 tree levels = 5 sha256 hashes for the verifier to recompute — fast enough.

If we ever raise `FLOOR_MIN` past 5 raw bits and want to keep the 32-bundle, common-stamp-raw rises to maintain the gap of 5. Alternatively the bundle size grows to 2^(i−g). The current bootstrap (FLOOR_MIN=2) means common-stamp lucky-hits are easier than the 32-gas forge ratio implies, until the difficulty climbs back to 4.

### Pretty-pattern bonus — probability-true value multipliers

Two hashes that both clear the same difficulty are NOT equally rare. A common-tier stamp where 4 emoji bytes happen to match in a row is ~256³ times rarer than a "random-looking" common. The protocol mints them as the same tier (because tier is bit-count) but their **economic value** must reflect actual prior probability, otherwise the wallet underprices rare patterns and the market can be arbitraged.

#### The base formula (already implemented, client-side)

```
value(zeros, hash) = 2^(zeros − 19) × 256^extras

where extras = |{ i : 1 ≤ i < N, hash.byte[startByte + i] == hash.byte[startByte + i − 1] }|

      N = tier.emojis (8 for common, 7 for uncommon, ..., 1 for cosmic)
      startByte = ⌈zeros / 8⌉  (skip the leading-zero prefix; pattern starts at the first non-zero byte)
```

This is the **inverse-probability** of seeing the pattern by chance:

```
P(byte[i] = byte[i−1] | uniform random) = 1/256
P(pattern with k extras) = (1/256)^k = 256^(−k)
fair value = 1 / P = 256^k
```

So `tokenValue` already computes the cryptographically-correct fair-market price under uniform-random distribution of post-zero bytes.

#### Probability-fair distribution proof

For a uniformly-random hash that already clears `floor` zeros (i.e. the leading prefix is fixed, the rest is uniform):

```
P(hash has k extras among N tier emojis)
  = C(N−1, k) × (1/256)^k × (255/256)^(N−1−k)
```

This is a binomial distribution. Each individual hash position has independent 1/256 probability of matching the previous one. So **every miner has equal chance**: the difficulty of obtaining a `k`-extras stamp depends only on hashrate and tier, not on identity.

Expected counts for a single common-tier stamp (N=8 emojis, so 7 adjacent pairs):

| Extras k | P(k) | Expected hashes-to-mint with this pattern |
|---|---|---|
| 0 | (255/256)⁷ ≈ 0.973 | 2⁹ ≈ 512 |
| 1 | 7 × (1/256) × (255/256)⁶ ≈ 0.0269 | 2⁹ × 36 ≈ 18 K |
| 2 | 21 × (1/256)² × (255/256)⁵ ≈ 3.16×10⁻⁴ | 2⁹ × 3,165 ≈ 1.6M |
| 3 | 35 × (1/256)³ × (255/256)⁴ ≈ 2.07×10⁻⁶ | 2⁹ × 482 K ≈ 247M |
| 7 | (1/256)⁷ ≈ 1.36×10⁻¹⁷ | 2⁹ × 7.3×10¹⁶ ≈ 3.7×10¹⁹ |

A "perfect" common (all 8 emojis identical) requires ~3.7×10¹⁹ expected hashes — about a billion years on a phone. The 256⁷ value multiplier exactly tracks that rarity.

#### The fairness invariant

**Every individual has the same expected number of pretty-pattern stamps per N hashes.** This is the cryptographic fairness guarantee. There's no skill, no advantage to early miners, no advantage to fast hardware beyond raw throughput — the byte distribution post-leading-zeros is uniformly random because Argon2id is a PRF.

#### Why this isn't (yet) a protocol-level tier

The relay validates **only the leading-zero count** — the tier the user claims. It doesn't read the post-zero bytes for pattern matching. So a miner who finds a `1-in-2¹⁰⁰⁰⁰` perfect-pattern common-tier stamp gets the same egress credit and the same on-chain "common stamp" record as someone who found an ordinary common.

Treating patterns as just a wallet display affordance (current behaviour) is justified because:
1. **No economic-incentive distortion** — every hash counts, none are "wasted." Pretty patterns are pure upside.
2. **Cheap relay validation** — relay doesn't recompute pattern bonuses on every forge.
3. **Discoverable post-mint** — a user can scan their existing wallet for pretty-pattern stamps without re-submitting to the relay.

#### Optional protocol extension (proposed v3)

If we want pretty-pattern bonuses to be **protocol-recognised** and grant proportional egress credit:

```
declared_tier = max( leading_zero_tier, pattern_tier )

where pattern_tier(extras k) = base_tier_raw_bits + 8·k    (each extra ≈ 8 raw bits of work)
```

The relay would:
1. Verify the leading-zero count (current behavior)
2. Verify the pattern bytes against `claim_extras` tag
3. Charge / credit egress proportional to `2^(declared_tier − floor)`

Cost to relay: one byte-comparison per extra (~µs). Benefit: pretty-pattern stamps become first-class on-chain assets; the wallet's display value matches the protocol's economic value.

#### Other patterns to consider (for v3)

| Pattern | Detection | Probability | Multiplier |
|---|---|---|---|
| **Adjacent repeat** (current) | byte[i] == byte[i−1] | 1/256 each | 256 each |
| **Palindrome** (full reverse) | byte[i] == byte[N−1−i] for all i | 256^(−⌊N/2⌋) | 256^⌊N/2⌋ |
| **Sequential ascending** | byte[i] == byte[i−1] + 1 | 1/256 each | 256 each (subsumed by repeat math when normalized) |
| **All-same** (subset of repeat) | all bytes equal | 256^(−(N−1)) | 256^(N−1) |
| **Hex-numeric** (byte ∈ 0x00..0x09) | each byte < 10 | (10/256)^N ≈ 1/26^N | 26^N |
| **Mirror palindrome at byte level** | full hash bytes mirror around centre | similar to palindrome | similar |

Each can be added incrementally as a separate `claim_pattern` tag. The relay verifies against the actual hash bytes; the multiplier is bounded by inverse-probability, so the protocol can't be gamed by claiming bonuses you don't have.

#### Code map

- `web/src/token-name.js:127` `tokenValue(zeros, hashHex)` — base × pretty-pattern multiplier
- `web/src/token-name.js:144` `repetitionExtras(hashHex, zeros)` — count adjacent repeats
- `web/src/token-name.js:161` `longestRepeatRun(hashHex, zeros)` — for the "×K" UI badge
- `web/src/ui/token-card.js` — renders the multiplier in the wallet
- (proposed v3) `workers/noone-relay/src/forge.js` — relay-side pattern verification

---

### Forge event work

The forge event itself must clear `max(tier_min_raw_bits, floor + ceil(log₂(cost)))`:

```
cost = 1 (base) + ceil(canonical_byte_size / 256)        // ~6-7 for 32-change bundle
log₂(cost) ≈ 3
required_event_bits = floor + 3 = 2 + 3 = 5  (at FLOOR_MIN=2)
max(common_raw=9, 5) = 9
```

So the forge event PoW costs 2⁹ = 512 hashes expected — roughly equal to the gas-mining cost. **Forge total cost ≈ 2× gas batch alone.** Halving stamps would be cheap relative to lucky-hit only if the bundle math weren't already calibrated to be equal.

---

## §8 (revised). Epochs + state — the 2-epoch sliding window

### Strict UTC boundary, no consensus

```javascript
currentEpoch() { return Math.floor(Date.now() / 1000 / EPOCH_DURATION_S); }
```

Every relay independently computes the same epoch number from its local clock + UTC reference. **NTP drift of ±1 s is irrelevant** — at 86,400 s per epoch, the boundary is a 1-in-86,400 event. No leader, no broadcast, no quorum needed for boundary detection.

### The 2-epoch validity window — three forces

A change record `c` mined in epoch `e` is forgeable iff `currentEpoch ∈ {e, e+1}`. Equivalently:

```
c.epoch ∈ [currentEpoch − 1, currentEpoch]
```

This 2-epoch window is the smallest that satisfies all three constraints simultaneously:

**1. Friendly to clock skew at boundary** — a 1-epoch window would fail for changes mined ≤1 second before midnight UTC, since the relay's clock might tick over before the user submits. 2-epoch windows give 24h of slack.

**2. Bounded memory for the spent set** — `_pruneSpent` drops entries whose `epoch < currentEpoch - 1`. So spent-set size ≤ `2 × max_forges_per_epoch_per_colo ≤ 2 × 32 × FORGES_TARGET = 262,144 hashes`. At 32 bytes each that's 8 MB — fits in DO RAM with margin.

**3. Replay attack surface ≤ 48 h** — even if an attacker manages to stash a change without immediately forging, the window in which they could submit a duplicate is bounded by the validity period.

### The retention proof

For a change `c` mined in epoch `e`:

```
Validity:   c.epoch ∈ [E − 1, E]    where E = currentEpoch
Retention:  c.epoch ≥ E − 1

If c.epoch = E:     valid for 2 epochs (E and E+1), retained until E+2
If c.epoch = E−1:   valid for 1 epoch (E only), retained until E+1
If c.epoch < E−1:   invalid AND pruned
```

The retention window exactly tracks the validity window. **The relay always has the data needed to detect double-spend** (since a spent change stays in the set as long as it's forgeable), but never holds more than 2 epochs of history.

### Code map for §8

- Epoch detection: `index.js:362` `currentEpoch()`
- Validity check: `forge.js:105` `verifyChange`
- Pruning: `index.js:588` `_pruneSpent`
- Retarget: `index.js:556` `_maybeRetarget`

---

## §9 (revised). Difficulty adjustment — the Bitcoin-shaped retarget

### The formula

```
ratio    = max(observed_forges, 1) / max(target, 1)
delta    = clamp(round(log₂(ratio)), −MAX_DELTA, +MAX_DELTA)   // ±2
floor'   = clamp(floor + delta, FLOOR_MIN, FLOOR_MAX)          // [2, 32]
```

### Why log₂ + clamp

`log₂(ratio)` makes the retarget **multiplicative**: doubling observed forges shifts the floor up by exactly 1 bit (work doubles). Without the log₂, you'd get linear adjustment that overshoots in one direction and undershoots in the other.

`clamp(±2)` prevents one outlier epoch from causing chaos. If a bot spams 100× target one epoch, the floor only moves +2 instead of +log₂(100) ≈ +7 — gives the network a chance to react before "hardening into uselessness."

### The convergence theorem

If the actual offered hashrate is constant `H` and `target = T`, the floor converges to the level `f*` where:

```
expected_forges_per_epoch_at_floor_f = T

⇒ H × EPOCH × P(hash succeeds at f) = T
⇒ H × EPOCH × 2⁻ᶠ = T
⇒ f* = log₂(H × EPOCH / T)
```

For `H = 10⁶ H/s` (mesh-wide), `EPOCH = 86,400 s`, `T = 4,096`:

```
f* = log₂(10⁶ × 86,400 / 4,096) = log₂(2.1 × 10⁷) ≈ 24.3 raw bits
```

So at the modeled hashrate the equilibrium floor is around 24 raw bits = ~17 million Argon2 hashes per gas. A phone at 50 H/s would take ~4 days per gas at that floor — at which point only forges from heavily-active users (or attestation-aggregators) would clear, naturally throttling the mesh to the protocol's design rate.

### The bootstrap challenge — why we dropped to FLOOR_MIN = 2

At launch, `H ≈ 0` so `observed << target`. The retarget pushes `delta = -2` every epoch but is clamped at `FLOOR_MIN`. With v1's `FLOOR_MIN = 4`, the floor was pinned at 4 with no climb. With v2's `FLOOR_MIN = 2`, the floor is pinned at 2 — making mining 4× faster — until **8,192 forges/epoch/colo** appear.

Math for first organic upward retarget at FLOOR_MIN=2:
- 2× target = 8,192 forges/epoch/colo
- One active phone at 50 H/s: `~12 gas/sec × 86,400 = 1.04M gas/epoch ÷ 32 = 32,400 forges/epoch`
- ⇒ a single dedicated miner can flip the retarget at the first epoch boundary they're online

**Conclusion:** with FLOOR_MIN=2, the bootstrap phase ends as soon as the first user mines for an entire epoch. After that the floor climbs autonomously toward equilibrium.

---

## §10 (revised). Mesh — 346 colos, gossip-driven CRDT

### Why 30 s gossip — the three-force tradeoff

Let `T` = gossip interval, `R` = round-trip time, `N_p` = peers per colo.

| Force | Effect on T |
|---|---|
| **Cross-region double-spend window** = `T` | shorter `T` = smaller attack surface |
| **DO compute load** ∝ `N_p / T` | longer `T` = lower load |
| **Network RTT floor** = `R ≈ 100 ms` | `T >> R` to amortize TCP overhead |

The optimum minimizes a weighted sum:

```
loss(T) = α × T² + β × N_p / T + γ × max(0, R − T)²
```

Take ∂/∂T = 0:

```
T* = (β × N_p / 2α)^(1/3)
```

For our parameters (α relates to economic loss per second of double-spend window, β to DO compute cost, N_p ≈ 7 continent peers):

```
T* ≈ (β × 7 / 2α)^(1/3)
```

Plugging in CF compute pricing (~$0.0001 per million DO requests) and economic value of one double-spent forge (~1 NN ≈ unit cost), we get `T*` in the 10-60 s band. **30 s sits in the middle, with a 100× margin to RTT and 1/2880 of an epoch.**

### Convergence proof for the spent set

Let `S_r(t)` be the spent-set state at region `r` at time `t`. After every gossip round, each region merges all peers' deltas:

```
S_r(t + T) = S_r(t) ∪ ⋃_{p ∈ peers(r)} S_p(t)
```

Since `∪` is idempotent and commutative (Set union), and every region eventually pulls from every other (transitive gossip), we have:

```
∀ r, r' :  S_r(t + k·T) = S_r'(t + k·T)  as k → ∞
```

Practically: `k = 2` gossip rounds (~60 s) is enough to converge across all 346 colos because peer pulls are transitive (NA→EU→AS in two hops).

### M-of-N signed roots — BFT layer

Each colo holds a per-instance Schnorr keypair `(sk_r, pk_r)`. Signs:

```
msg_r = "noone-spent-root:" + pk_r + ":" + epoch + ":" + smt_root_r
sig_r = schnorr_sign(sk_r, sha256(msg_r))
```

A light client consults M-of-N regions and verifies:

```
∀ r ∈ M-subset:  schnorr_verify(pk_r, msg_r, sig_r)  AND  smt_root_r == smt_root_r'
```

Honest regions converge on the same root within 2 gossip rounds → produce identical `smt_root` → produce different `sig_r` (different keys) but all sign the same root.

A malicious region signing a divergent root **fails the equality check** and is dropped. Up to `N − M` malicious regions tolerated. With 7 continents × ~50 colos each, M = 5 and N = 7 gives Byzantine fault tolerance against 28% of continents.

### Why no Paxos / Raft / PBFT

Required ingredients:
- **Leader election** — none, every region is symmetric.
- **Sequence numbers** — none, the spent set is unordered (Set CRDT).
- **Two-phase commit** — none, accept-on-first-receive is atomic per-DO.
- **View change** — none, no view to begin with.

These are all consensus apparatus that the noone protocol doesn't need because the operation we're synchronizing (Set ∪) is **idempotent + commutative + monotonic**. CRDTs let us skip the entire consensus stack.

### Code map for §10

- Gossip: `gossip.js`
- SMT: `smt.js`
- Schnorr signing: `index.js:474` `signSpentRoot`
- Pull loop: `index.js:515` `_pullGossip` (runs from DO alarm)
- Endpoints: `/__gossip/spent`, `/__spent_root`, `/__spent_proof`, `/__signed_root`

---

## §11. New: epoch-anchored valuation — "work at time of mint"

The core question is: should two stamps of the same tier have the same value, regardless of when they were minted?

**Bitcoin's answer:** yes — every BTC is fungible, the difficulty-at-mint is irrelevant to current value. But Bitcoin compensates by **reducing block rewards over time** (halving every 210K blocks), so early miners got more BTC per block.

**noone's answer (proposed v3):** stamps carry their mint-time floor, and value scales with actual work expended. This makes the protocol **purely work-equivalent** — a "common" minted when the relay floor was 2 raw bits is worth less than a "common" minted at floor 24, because the latter cost ~4M× more compute.

### The formula

```
value(zeros, hash, mintFloor, mintEpoch) =
    2^(zeros − 19)                  // tier-base value
  × 256^extras                       // pretty-pattern multiplier (§7)
  × 2^(mintFloor − FLOOR_MIN)        // work-at-mint anchor   ← NEW
```

At the bootstrap floor (FLOOR_MIN = 2):
- Bootstrap common (floor=2): work-anchor = 2⁰ = 1×
- Mature common (floor=24): work-anchor = 2²² ≈ 4 M×
- Asymptotic common (floor=32): work-anchor = 2³⁰ ≈ 1 B×

This means as the network matures, **newer stamps dwarf older ones in compute-cost** — the inverse of Bitcoin's "early adopter premium." It's structurally fair: pay-for-what-you-mined.

### Why this design (vs Bitcoin halvings)

Bitcoin's halving exists because the **block reward is fixed** but supply is infinite without it — so they cap total supply at 21M and reduce per-block reward over time. The math for halving was a synthetic constraint, not an economic invariant.

noone has **no fixed supply cap**. The supply is whatever the difficulty curve produces. A "halving"-equivalent would be artificial. Instead we let the difficulty retarget govern supply and let valuation track actual work.

### What gets stored on each stamp

Every accepted forge already carries `epoch` in its tags. Adding `mint_floor` is a 1-byte addition (raw bits ∈ [2, 32]). The relay can verify it against the floor it actually had at the forge's epoch (which is in `_lastRetargetInfo`):

```javascript
// Forge validation, proposed v3 addition
if (event.tags.find(t => t[0] === 'mint_floor')[1] !== this.minRawZeros) {
  return { ok: false, reason: 'mint_floor mismatch' };
}
```

### Provenance value (qualitative)

Bitcoin coins minted in 2009 are NOT structurally more valuable than coins minted in 2024 — only their HOLDERS are richer because they bought them cheap. noone's work-anchored model makes value structurally tied to mining work, so:

- A bootstrap stamp (mint_floor=2) is **rare in count** (early miners are few) but **cheap in compute**
- A mature stamp (mint_floor=24) is **abundant in count** but **expensive in compute**
- Combined valuation: cost-to-mine ≈ structural value; provenance ≈ social value

### Code map (currently)

The plumbing is already mostly there:
- `wallet.collection[i].zeros` carries effective zero count
- `wallet.collection[i].ts` carries mint timestamp (epoch derivable as `floor(ts/86400)`)
- `wallet.collection[i].relay_anchor.batch_root` proves which RelayBatch witnessed it
- **MISSING**: `mint_floor` field — would be added as a kind-25104 forge tag in v3

### Lookup table (fair-market value at common tier)

| mint_floor | Work to mine 1 common | Multiplier | Value (NN) |
|---|---|---|---|
| 2 | 512 hashes | 1× | 32 |
| 4 | 8 K hashes | 4× | 128 |
| 8 | 32 K hashes | 64× | 2,048 |
| 12 | 524 K hashes | 1 K× | 32,768 |
| 16 | 8 M hashes | 16 K× | 524,288 |
| 20 | 134 M hashes | 256 K× | 8 M |
| 24 | 2.1 B hashes | 4 M× | 134 M |
| 28 | 34 B hashes | 64 M× | 2 B |
| 32 (cap) | 549 B hashes | 1 B× | 32 B |

A common-tier stamp minted at the difficulty cap is roughly **a billion times more valuable** than one minted during bootstrap — because it required a billion times more compute. That's the work-equivalence guarantee, end-to-end.

---

## §12. New: blockchain comparison — what we store vs Bitcoin

This is the diff between noone's per-epoch CRDT state and a traditional blockchain.

### Per-relay (per-DO) persistent state — exhaustive list

From `_snapshot()` in `index.js:730`:

| Key | Content | Size at scale | Pruned? |
|---|---|---|---|
| `cache` | last 10K events (chat, files, signals, forges) | ≤ 640 MB worst case (10K × 64KB) | LRU on size |
| `counters` | aggregate stats | ~1 KB | never |
| `spent` | hash→epoch map for forge change-records | ~8 MB worst case (262K × 32 B) | every epoch (2-epoch retention) |
| `spentOrder` | append-only log of (hash, epoch) for gossip cursor | ~10 MB | trimmed with spent |
| `diff` | difficulty state (trackedEpoch, forgesThisEpoch, lastRetargetInfo) | ~256 B | never |
| `minRawZeros` | current floor | 1 number | never |
| `contracts` | smart contract index (parties, terms, settle status) | ~1 MB capped at 1024 entries | LRU after settle |
| `gossipCursors` | per-peer last-fetched cursor | ~few KB | never |
| `regionSigningSk/Pk` | DO's Schnorr keypair | 64 B | never (rotation = new DO) |

**Realistic per-DO total: 10-100 MB at steady state, hard cap ~700 MB if event cache is full.**

### Per-epoch ON-WIRE state — what gossip moves

Per-DO every 30 s:

```
GET /__gossip/spent?since=<cursor>
→ { stamps: [[hash, epoch], ...], cursor: <new_n> }
```

Bandwidth per peer-pull:
- 4096 entries × 40 bytes (hash + epoch + JSON overhead) ≈ **160 KB max per pull**
- 30 s interval = 5.3 KB/s/peer
- 7 peers = 37 KB/s outbound per DO
- 346 DOs × 37 KB/s = **12.8 MB/s mesh-wide**

### Per-epoch CONSENSUS state — signed roots

Per-DO published at `/__signed_root`:

```
{
  root:    <32-byte SMT root, hex>,        // 64 chars
  epoch:   <number>,                        // 4 bytes
  region_pk: <32-byte Schnorr pubkey, hex>, // 64 chars
  sig:     <64-byte Schnorr sig, hex>       // 128 chars
}
```

**~280 bytes per signed root per region per epoch.** Light clients pull from M of N regions: 5 × 280 = 1.4 KB to verify cross-mesh state.

### Storage growth — Bitcoin vs noone

| Property | Bitcoin | noone |
|---|---|---|
| Genesis block | Jan 3, 2009 (hardcoded) | Jan 1, 1970 (Unix epoch 0) |
| Block period | 10 min nominal | 24 h epoch |
| Block format | 1 MB Merkle tree of txs | (no blocks — events live in cache) |
| Total chain size | ~600 GB and growing | ~3 GB across 346 DOs, **bounded** |
| Per-block growth | ~1 MB/10min = 144 MB/day | bounded by event cache size |
| Pruned permanently | NO (full archive) | YES (events 30d, spent 2 epochs, contracts on-settle) |
| Provenance window | forever | 2 epochs for fungibles, ~30 days for chat |

**noone state size is BOUNDED by design.** A relay running for 10 years takes the same disk as one running for 1 month — because everything older than the retention window is pruned.

### What we lose vs blockchain

| Blockchain feature | Why noone doesn't have it |
|---|---|
| Permanent transaction history | Pruning makes long-tail forensics impossible. Trade-off: privacy + bounded state. |
| Global ordering | Each colo is a CRDT; no shared sequence. Trade-off: no "transaction order" semantics, but also no consensus overhead. |
| Reorgs / chain forks | None — no chain to fork. Trade-off: a malicious gossip burst can spread bad state, but signed-root M-of-N catches it. |
| Smart contract VM | Limited to predefined kinds (25120/121/122). Trade-off: no Turing-complete state machines on-chain. |
| Wallet-to-wallet transfers | Stamps are local; no transfer event yet. Trade-off: must build wallet-transfer protocol if needed (proposed v3 §20). |
| Block reward halvings | None — value comes from work-at-mint (proposed §11). Trade-off: no fixed supply cap. |
| Mempool inspection | None — events accepted or rejected synchronously. Trade-off: no tx queue to MEV. |
| Replay-attack history | Spent set covers 2 epochs only. Trade-off: ≤48h window for double-spend; eternal blockchain catches it forever. |

### What we gain vs blockchain

| noone feature | Why blockchains struggle |
|---|---|
| Bounded state | Bitcoin's chain grows forever; nodes must keep all of it (or trust pruning) |
| Sub-second finality (per colo) | Even Solana's 400 ms is slower than noone's <50 ms accept |
| 346× parallelism | Most chains are single-leader; can't shard without complexity |
| Privacy by default | All blockchain txs are public forever |
| No miner cartels | Argon2id is memory-hard; ASICs costly; no "pool" advantage |
| 30 days message retention | All chains are public-forever or none at all |
| No staking/slashing | Reputation is social, not collateralized |
| Eventual consistency | CRDT = no consensus protocol, no leader, no view changes |

### The fundamental design difference

A blockchain is **a globally-ordered, append-only ledger with consensus on the order of operations**.

A noone mesh is **a sliding-window CRDT spent-set with eventually-consistent gossip**.

The two approach state-replication problems from opposite ends:
- **Blockchain:** "Every node will know exactly the same thing forever, at the cost of slow finality + permanent storage."
- **noone:** "Every node will eventually know the recent state, in bounded storage, but old state is forgotten."

For chat-and-stamp applications, eventual consistency with bounded state is the right tradeoff. For a global financial settlement layer, the blockchain model is the right tradeoff. They're not competing — they're solving different problems.

### How big does noone get if it has Bitcoin's 1 billion users?

Assume 1B users × 1 forge/day × 32 changes:
- 32B change-records/day = 32B × 32 bytes = 1 TB/day across the mesh
- BUT: spent set retention = 2 epochs = 48h
- So peak storage = 2 TB across 346 DOs = **~6 GB per DO**
- vs Bitcoin: at 1B users, the chain would be ~petabytes/year and unprunable

**noone scales linearly in active users with bounded per-DO state. Blockchains scale linearly in cumulative users with unbounded state.**

---

## §17. New: throughput model

### Single-pair P2P (the main path)

Direct WebRTC DataChannel between two paired browsers, no relay involvement:

| Payload | Measured TPS | Bandwidth |
|---|---|---|
| 100 B | 397,879 msg/sec | 318 Mbps |
| 1 KB | 79,443 msg/sec | 651 Mbps |

Limit: ChaCha20-Poly1305 + DataChannel buffer drain. Browser-CPU bound, ~50 MB/sec encrypt/decrypt.

### Single-relay path (only for offline / consensus events)

Per colo (CPU-bound on Argon2 verify, ~2.7 ms each):

| Event class | Verify cost | TPS / colo |
|---|---|---|
| kind-1 chat (1× event verify) | ~3 ms | ~330 |
| kind-25130 file offer (1× event + 1× size factor) | ~5 ms | ~200 |
| kind-25104 forge (32× change + 1× event) | ~89 ms | ~11 |

Mesh-wide (× 346 colos):

| Event class | Mesh TPS |
|---|---|
| kind-1 chat | ~114,000 / sec |
| forge | ~3,800 / sec |

The relay path is the **fallback** for chat (when peers can't open a DataChannel) and the **only path** for events that need cross-mesh validation (forges). Most chat traffic never touches the relay.

### Comparison to blockchains (consensus-strict)

| System | TPS (sustained) | Latency to finality |
|---|---|---|
| Bitcoin | 7 | 10 min |
| Ethereum L1 | 15 | 12 s |
| Cardano | 250 | 1-2 s |
| Solana | 1,000-2,000 | 0.4 s |
| **noone forge** | **3,800** | 30 s gossip propagation |
| Polygon PoS | 7,000 | 2 s |
| Visa (centralized) | 24,000 | 2 s |
| **noone P2P chat** | **~400,000 / pair** | **0.3 ms** |

noone is **not a blockchain** — there's no global ordering, no consensus protocol, no slashing. The TPS comparison is apples-to-apples only for the forge path, which IS consensus-required (atomicity of spent-set burn).

---

## §18. New: battle test results

### Test harness

`workers/_shared/test-forge-battle.mjs` — Node.js test runner that exercises the full forge protocol against the live `wss://pow.noone.chat` mesh.

Run: `cd workers/_shared && node test-forge-battle.mjs` (~3 min wall clock).

### Coverage matrix

24 assertions across 15 attack scenarios — **all passing** as of v82 deploy:

| # | Scenario | Defense |
|---|---|---|
| 1 | Happy path forge | (baseline pipeline) |
| 2-3 | Double-spend (same conn, new conn) | per-DO spent set |
| 4 | Stale epoch (`E − 2`) | 2-epoch validity window |
| 5 | Re-mine fresh gas, second forge | pipeline isn't poisoned |
| 6 | Tampered Schnorr signature | NIP-01 sig verify |
| 7 | Impersonation (event claims pkA, signed by pkB) | sig verify against `event.pubkey` |
| 8 | Tampered merkle root | `forge_root` recomputation |
| 9 | Under-budget bundle (16 of 32) | `change_count` enforcement |
| 10 | Duplicate changes (16 unique × 2) | per-change spent collision |
| 11 | Mixed-pubkey changes (stolen gas) | `gas:<pubkey>:…` Argon2 binding |
| 12 | Wallet-transfer attack (rebroadcast accepted forge) | spent-set persistence |
| 13 | Tier inflation (claim rare with common bits) | `tier_min_raw_bits` check |
| 14 | Spam / rate-limit (12 rapid submissions) | per-pubkey 10/min limiter |
| 15 | `/__spent_proof` returns SMT inclusion | cryptographic membership proof |

### Performance during battle tests

- Argon2id: 500 H/s laptop, 2.0 ms / hash
- 32-change batch: ~250 ms
- Forge event PoW (target ≈ 9): single hash (lucky)
- Full 24-case suite: ~3 min including network round-trips
- Rate limiter trips: yes, after ~3 rapid submissions from same pubkey

---

## §19. New: threat model bounds (with numbers)

### Attack surface table

| Attack | Window | Max economic damage | Mitigation |
|---|---|---|---|
| Double-spend (same colo) | 0 | 0 | spent set fires immediately |
| Double-spend (cross-colo race) | 30 s | +1 forge for same gas | gossip merges, M-of-N divergence detection |
| Forge with stolen gas | n/a | 0 | Argon2 verify against forger's pubkey |
| Forge with tampered sig | n/a | 0 | Schnorr sig check |
| Tier inflation | n/a | 0 | bit-count check vs claimed tier |
| Spam at floor | per epoch | proportional to gas mined | rate-limiter + Argon2 cost |
| Sybil pubkey flood | per epoch | retargets up next epoch | difficulty retarget |
| Region key compromise | until rotated | one region's signed roots | M-of-N catches divergence |
| Gossip peer compromise | until detected | flood spent-set with bogus | `BUDGET_PER_PULL = 4096` cap |

### The cross-region double-spend exploit (the one real concern)

Cost of attack:
- 32 valid changes mined: ~3 s at FLOOR_MIN=2
- Two coordinated WS connections to two distant colos
- Both forges submitted within `T < 30 s`

Reward:
- 2 stamps instead of 1 (extra value: 1 common stamp ≈ 2⁵ NN = 32 raw)
- Per-pubkey rate-limit (10/min) caps to 6 such attacks/min/pubkey
- Detected when gossip syncs at next round

**Maximum damage** per pubkey per epoch:

```
6 attacks/min × 60 min/h × 24 h × 1 extra stamp/attack × 32 NN/stamp
= 276,480 NN/epoch/pubkey
```

This sounds high, but each attack requires **mining 32 fresh changes** between attempts (since rate-limit is per pubkey not per change), so the attacker must split across multiple pubkeys — at which point the attacker is mining gas at full rate and the "extra" benefit is one stamp per pubkey-attack-cycle.

Realistic worst-case at FLOOR_MIN=2 with one attacker using 100 pubkeys: ~5,000 extra stamps/epoch. The mesh's natural forge throughput at full target is `4096/colo × 346 = 1.4M/epoch`, so the attacker can extract **~0.35 % "free" stamps** at peak.

After the floor climbs to its equilibrium (~24 raw bits per § 9), the attack cost scales as `2^24 / 2^2 = 4M× higher` than at bootstrap, making it economically irrational.

### Privacy bounds

- Relay sees: Argon2 hash, pubkey, timestamp, event kind, encrypted blob
- Relay does **not** see: plaintext message, recipient (NIP-59 ephemeral pubkey), social graph, mining attempts (only successes)
- Relay storage: spent set (hashes only), event cache (10K events, encrypted), signed roots
- IP: terminated at CF edge; relay sees `cf-connecting-ip` only

A malicious relay can:
- Drop events (mitigation: Nostr fallback to public relays)
- Selectively gossip (mitigation: M-of-N signed-root verification)
- Refuse to publish a forge (mitigation: try another colo via direct WS)

A malicious relay **cannot**:
- Forge events as another user (sigs)
- Burn another user's gas (spent set is per-hash, hashes bind to forger's pubkey)
- Rewrite history (nothing to rewrite — no ledger)
- Reveal plaintext (encrypted at NIP-59 layer)

---

## §20. New: open questions / future work

### v3 candidates

1. **Quadratic forge bundles** — let the user pick `2^n` gas to forge `n+1` stamps in a single event, amortizing event-PoW work. Math: yields linear-in-`n` savings on event-side cost.

2. **Cross-region BFT for forges** — currently gossip is eventually-consistent on burn. A future version could require M-of-N attestations BEFORE accepting a forge for tiers above a threshold, eliminating the 30 s double-spend window for rare/epic+ stamps.

3. **Proof-of-possession across forges** — let users prove "I burned 32 gas at epoch E" via a Schnorr-aggregated signature, allowing trustless wallet-to-wallet stamp transfers (current protocol has no transfer event).

4. **Adaptive `BUDGET_PER_PULL`** — scale gossip budget by spent-set growth rate so a gentle attacker can't slow-drip past it.

5. **Per-tier rate limits** — common stamps at full velocity, rare/epic limited to 1/h/pubkey to make tier-stuffing harder.

---

## Appendix A — math summary

| Property | Formula | Numerical |
|---|---|---|
| Hash success at floor `f` | `2⁻ᶠ` | `f=2` → 25 % |
| Hashes per gas | `2ᶠ` | `f=2` → 4 |
| Hashes per common-tier stamp | `2⁹` | 512 |
| Hashes per 32-gas forge bundle | `32 · 2ᶠ` | `f=2` → 128 |
| Bundle-vs-lucky-hit ratio | `2^(common_raw − f) / 32` | `f=2` → 16; `f=4` → 1 |
| Forge event PoW target | `max(tier_min, f + ⌈log₂ cost⌉)` | typically 9-10 |
| Epoch length | 86,400 s | 24 h |
| Spent retention | 2 epochs | 48 h |
| Gossip period | 30 s | 1 / 2880 epoch |
| Cross-region attack window | ≤ gossip period | ≤ 30 s |
| Difficulty retarget delta | `clamp(round(log₂(observed/target)), ±MAX_DELTA)` | ±2 / epoch |
| Equilibrium floor | `log₂(H · EPOCH / T)` | 24 raw bits @ H=10⁶ |
| P2P chat TPS | bandwidth / payload | ~400K / pair |
| Forge mesh TPS | `colos × forges_per_sec_per_colo` | ~3,900 |

---

## Appendix B — file map

| Concern | File | Notes |
|---|---|---|
| Argon2 PoW | `workers/noone-relay/src/pow.js` | event subject + cost-bits |
| Forge validation | `workers/noone-relay/src/forge.js` | merkle, change verify, forge verify |
| Difficulty retarget | `workers/noone-relay/src/difficulty.js` | log₂ formula + clamps |
| Gossip protocol | `workers/noone-relay/src/gossip.js` | cursor + budget |
| SMT proofs | `workers/noone-relay/src/smt.js` | spent-root + sibling proof |
| Schnorr sig + Nostr id | `workers/noone-relay/src/sig.js` | NIP-01 |
| Region keys + signed roots | `workers/noone-relay/src/index.js:_ensureRegionKey, signSpentRoot` | persistent per-DO |
| Per-colo routing | `workers/noone-relay/src/region.js` | 346 PoP map |
| Rate limiter | `workers/noone-relay/src/ratelimit.js` | per-pubkey leaky bucket |
| Egress quota | `workers/noone-relay/src/egress.js` | per-recipient delivery cap |
| Stats endpoint | `workers/noone-relay/src/index.js:/stats` + `:/__internal_stats` | aggregate counts |
| Battle tests | `workers/_shared/test-forge-battle.mjs` | 24 attack assertions |
| TPS bench | `workers/_shared/bench-tps.mjs` | relay throughput |
| P2P TPS bench | `web/src/test/e2e/p2p-tps-bench.spec.js` | DataChannel direct |
| Client mining | `web/src/apps/miner-worker.js` + `miner.js` | UI + worker |
| Change wallet | `web/src/change-wallet.js` | local UNBOUND store |
| Forge submission | `web/src/forge-tx.js` | client-side build/sign/publish |
| Stamp tiers | `web/src/token-name.js` | ALPHABET + TIERS |

---

*Last updated: 2026-05-06. Authoritative against this commit.*
