# Bitpeople

Periodic verification of unique humans. Proofs are valid one period, anonymous, and untraceable between periods.

---

# Ledger

A binary Merkle tree. Leaves are pairs of nyms. Branches aggregate counts upward. The root is the state of the population.

## Data

```
LeafNode {
    prev_root           [32]byte
    nym_a/b             [32]byte
    verified_a/b        bool
    disputed            bool
    judged_a_a/b        bool
    judged_b_a/b        bool
    court               [32]byte
    court_judged_a/b    bool
    registration/mixer_hash_a/b/c  [32]byte
    invite_a/b          [32]byte
    new_elected_a/b/c   [32]byte
    new_commit_a/b/c    [32]byte
    commit_a/b          [32]byte
    seed_hits_a/b       uint32
    elected_a/b         [32]byte
}

BranchNode {
    prev_root           [32]byte
    population_count    uint32
    invite_count        uint32
    seed_hits           uint32
    left_hash           [32]byte
    right_hash          [32]byte
}
```

LeafNode hash: `hash(all fields)`. BranchNode hash: `hash(all fields)`. Root is a BranchNode with no parent.

All hashing: `sha256(fields concatenated, fixed-size, big-endian integers)`.

## Counts

Aggregated upward from leaves to root.

Per leaf:
- `population_count` = pair verified nyms (both verified_a and verified_b) + court judged invite (both court_judged_a and court_judged_b, if pair verified) + judged disputed nyms (judged_a_a && judged_a_b for nym_a, judged_b_a && judged_b_b for nym_b)
- `invite_count` = non-zero invite fields (max 2)
- `seed_hits` = max(seed_hits_a, seed_hits_b), lowest index breaks ties

Per branch: population_count, invite_count = sum of children. seed_hits = max of children, lowest index breaks ties.

## Lookup

`pair_count = (population_count + 1) / 2`. Tree depth = `ceil(log2(pair_count))`. To find a nym by index: follow left/right from root based on index. O(log N) hops. Hashes along the path constitute a Merkle proof.

---

# Phases

Each period consists of Event, Register, one or more Mixer phases, and RNG — each producing a Merkle root. Shuffle is a deterministic computation between periods, not a phase.

```
Event → Register → Mixer 1..N → RNG
```

Each phase collects transactions, then applies them all at once to produce the next root. Transactions within a phase reference the root at the start of the phase. The order of transactions within a phase does not affect the result.

## Shuffle

Deterministic computation from the previous seed. Produces the new tree.

The shuffle uses a Feistel shuffle (Horst Feistel, 1973). A Feistel shuffle takes a sequence of numbers and produces a unique, shuffled output for each input — guaranteed, not probabilistic. It is also reversible: given an output, you can compute the input. Each position can be computed independently without computing the full shuffle. The mechanism is surprisingly simple but counterintuitive — it is not obvious why it always produces unique outputs. The key insight is that a single round always produces unique outputs: the half that is hashed passes through unchanged, so the hash result is the same for inputs sharing that half — and XOR with the same value cannot make two different values equal. The output of one round is indistinguishable from fresh input — it is just another set of unique values. So the next round also preserves uniqueness, and so on through any number of rounds. This makes it a natural fit for Bitpeople: every nym computes its own new position from the seed, and validators only need to verify the positions they are responsible for.

Every nym counted in `population_count` (N) has a nym_id (0 to N-1). All are auto-registered and shuffled — nyms that did not register a new address carry a zero address. Each nym's new position is computed independently:

`new_position = feistel(nym_id, seed, N)`

The Feistel shuffle works as follows: the nym's index is represented as a fixed-width binary number (width = smallest even number of bits ≥ log2(N)) and split into two equal halves (L, R). Three rounds are applied: in each round, the right half is hashed with a round key, XORed with the left half, and the halves are swapped. Round keys are derived from the seed: `round_key_i = sha256(seed || i)`. The round function is `f(R, key) = sha256(R || key)`, truncated to half-width bits.

If the result ≥ N, the Feistel is applied again on the result (cycle-walking) until it falls within [0, N). Since every input produces a unique output on the full domain, each input follows a unique chain of values — no two chains can merge. The first valid value encountered in each chain is therefore unique.

Each nym can compute its own new position independently. No sequential dependency. The permutation carries `registration`, `new_elected`, and `new_commit` from the previous period. Consecutive positions form pairs. Invites are placed directly (not permuted).

**Assign courts** — invites from previous period placed sequentially: invite 0 → leaf 0, invite 1 → leaf 1, etc. Max one court per pair.

A nym may invite if pair verified or judged, and nym_id < 2 * previous invites_used (equivalently, `court != 0` on the leaf) and leaf index < `population_count / 2`, or is nym 0 (min 1).

## Event

Pairs meet over video. Transactions:

- **Verify** — nym confirms its partner. Sets `verified`.
- **Dispute** — nym flags dispute. Sets `disputed`. If disputed, verified and court_judged flags on the same leaf are cleared.
- **Judge** — court pair member confirms the invite person. Sets `court_judged`.
- **JudgeDispute** — court pair member confirms a disputed nym. Sets `judged` on the disputed nym's leaf. Signed by court pair member, applied to target leaf.

Court assignment for disputed nyms: target court leaf = `nym_index mod pair_count`. No limit on courts per pair.

Counts finalized: population_count.

## Register

Verified nyms register for next period and submit governance fields. Transactions:

- **Register** — submit registration address and mixer_hash for registration slot. Signer: nym_a/b/c. Requires pair verified or judged (or court_judged and pair verified for court).
- **Invite** — submit an address for a new participant. Sets `invite`. Signer: nym_a or nym_b. Requires pair verified or judged, `court != 0` on the signer's leaf and leaf index < `population_count / 2`, or nym 0.
- **Commit** — submit new_commit hash.
- **Elect** — submit new_elected address.

Counts finalized: invite_count.

## Mixer

One or more mixer phases per period. Each mixer phase produces a Merkle root. The number of mixer phases is determined by the population.

Register transactions are held pending until phase end, since the signing address may be restored by an invalidation during the phase. Transactions:

- **Register** — submit registration address and mixer_hash for registration slot. Signer: current address in the slot. One registration per slot per phase.
- **InvalidateMixer** — submit preimage (leaf_id, mixer_idx, new_address, nonce). The validator: (1) hashes the preimage and verifies it matches the sender's `mixer_hash`, (2) locates the partner's leaf from the preimage, (3) checks that the partner's `address` equals `new_address` from the preimage, (4) computes `hash(sender_leaf_id, sender_mixer_idx, partner_new_address, nonce)` and compares against the partner's `mixer_hash`. If step 3 or 4 fails, the mixer is invalidated: original address restored, mixer_hash zeroed.

## RNG

Each nym reveals by submitting a 256-bit preimage matching their `commit`. The reveal maps to a target: `(position + uint(reveal)) mod population_count`. Target's `seed_hits` incremented. At stable population (population_count roughly equal to previous period's), seed_hits approximate a Poisson distribution. The target with the most hits wins (lowest index breaks ties). The winner must present all reveals that pointed at it. The new seed is the XOR of those reveals.

Aggregated upward: leaf reports max of its two nyms' seed_hits, branch takes max of children. Lowest index breaks ties. Seed root records the winning target and XOR result.

Transactions:

- **Reveal** — submit preimage matching `commit` (no signature). Target's `seed_hits` incremented.
- **InvalidateMixer** — permitted during RNG for the final mixer round.

---

# Transactions

Every transaction includes `prev_root` and is signed by the nym's address key.

## Token Mixing

Two nyms swap registration addresses to break the link between old nym and new address. Mixing occurs over sequential mixer phases — the number of phases is determined by the population.

Each registration slot has `registration` (the partner's new address, visible on-chain) and `mixer_hash` = `hash(leaf_id, mixer_idx, new_address, nonce)` where leaf_id and mixer_idx identify the partner's leaf and slot, new_address is the owner's new address (visible as `registration` on the partner's leaf), and nonce is a shared 32-byte random value.

1. **Private** — A and B exchange new addresses and agree on a shared nonce off-chain.
2. **Register** — both submit each other's address and mixer_hash.
3. **Invalidate** — submit preimage (leaf_id, mixer_idx, new_address, nonce). The validator verifies the preimage against the sender's `mixer_hash`, locates the partner's leaf, checks that the partner set `new_address`, and computes the expected partner `mixer_hash` from known values. If the partner did not set the correct address or did not commit back to the sender, the swap is rolled back: original address restored, mixer_hash zeroed.

A nym that invalidates can register again in the same phase.

### Breaking Traceability

An attacker who observed a nym during Event must be the nym's mix partner in every subsequent mixer phase to maintain traceability. If the nym mixes with an honest partner in any single round, the link is broken permanently.

Recommended strategy: mix early rounds with trusted contacts — friends, family, community members, or even oneself (self-mix). A single honest round is enough for the attacker to lose track.

## Dispute

Either nym flags dispute during event. Neither is verified. Both retain registrations for next shuffle.

Court assignment for disputed nyms: target court leaf = `nym_index mod pair_count`. Court pair confirms the disputed nym via JudgeDispute.

## Proof-of-Unique-Human

A nym counted in `population_count` has a proof-of-unique-human. A Merkle proof against the root demonstrating inclusion in the count is the proof. Other systems can import the root and verify proofs directly. If anonymity is needed, the external system can run its own mixer.

---

# Consensus

The root referenced by the most transactions is canon — analogous to a majority vote by the users each phase. The population finalizes by building on the root they attest to.

---

# Root Aggregation

The goal is a fully distributed validator hierarchy where every nym participates. But since the aggregation method is not part of the Merkle tree — signatures are transport, not structure — simpler coordination can be used to begin with.

Each nym submits an `elected` address during register phase, pointing to their chosen validator. The elected addresses are shuffled to determine validator positions in the hierarchy.

## Central Server

All nyms send transactions to a single server that builds the roots and publishes Merkle proofs. Simplest starting point.

## Single Aggregator

Nym 0's elected service builds the roots for the period — one aggregator, one period, analogous to one block producer per block. If nym 0's service is slow or offline, nym 1, 2, 3 etc. are next in line. Each other elected service independently verifies the roots and attests correctness to the nyms it represents — not as part of a collective decision, but as an individual audit on behalf of its clients.

## Validator Hierarchy

Heap indexing: nym i = node i. Root = 0. Children: 2i+1, 2i+2. Leaves start at pair_count-1.

Leaf validators verify signatures, state transitions, compute counts, report upward. Branch validators verify children's signatures, aggregate, report upward. Each node does O(1) work.

Heap index modulo total vote space determines responsibility. In proof-of-stake, the vote space is total staked units. In proof-of-unique-human, each people-vote is one slot.

Failed or dishonest node: majority (>50%) of leaves underneath override.

### Replication

Each nym's elected service stores one node per level. On level k, nym i's service stores node `nym_index mod 2^k`. Equal burden. Each node replicated by `population / 2^k` services. Lookups are direct.

### Auditing

Each nym's elected service audits the leaf its nym is assigned to and verifies the chain from leaf to root. Each leaf has 3 independent observers by default (two nyms' elected services + leaf validator). A full verification of the entire tree is a few terabytes at 10 billion population. Trivial for anyone with a computer.

---

# Security

## Self-Protecting

- **Counts invariants** — bounded per leaf, aggregated upward. Fabrication breaks the sum.
- **Self-auditing** — each nym can verify its own Merkle proof against the root. Unauthorized changes detected by the affected party.
- **Fork choice** — prev_root in every transaction. Invalid roots are rejected by the population.
- **Bounded lie surface** — a compromised leaf (two colluding nyms + validator) can verify each other, but this is by design — the attack is that no real person is behind it.

## Requires Auditing

### Court Assignment Correctness

Court assignments for disputed nyms follow a deterministic formula. Each must link a disputed nym to a valid court leaf. Verifiable by anyone.

### Reveal Integrity

The seed determines next period's shuffle. To verify:

- Was commit shuffled correctly (followed the permutation)?
- Was commit unchanged after register phase?
- Can the seed winner present all reveals matching their claimed hits?
- Does each reveal match a commit on the target nym?

Extend to all nyms with seed_hits for full coverage. All deterministic, all verifiable.

## Man-in-the-Middle Attack

An attacker places two fake accounts between two honest people, relaying video in both directions. Both honest people believe they are talking to each other and verify the fake accounts.

The defense is a lock & key mechanism: both nyms in a pair commit an encrypted video before the event. After the event (or after a handshake before it) they reveal the decryption key. The pre-recorded video must match the person seen during the event. An attacker relaying video cannot produce a matching pre-recorded video.

However, if the attacker knows who a nym is — from observing them in a previous Event — they can deepfake a convincing pre-recorded video and defeat the lock & key. This is why anonymity is critical. The mixer breaks the link between Events, preventing the attacker from knowing who they are targeting.

## Collusion Attacks

Bitpeople is vulnerable to collusion — groups of people who agree to verify fake accounts. The success of collusion attacks increases quadratically: a fraction x of the population colluding gains x² influence per period.

Repeated attacks across periods conform to: `a[n] = (x + a[n-1])² / (1 + a[n-1])`, which plateaus at `a[∞] = x² / (1 - 2x)` for x < 0.5. Colluders reach 50% control when x = 1/3. Bitpeople is a 66% majority controlled system.

## 1-on-1 Video Turing Test

Bitpeople's security assumption is that a person can tell they are talking to a real person in a live, unscripted 1-on-1 video conversation. Deepfakes do not break this — they still require a person behind them, and that person can only be in one pair at a time. To break Bitpeople with an AI attack would require fully autonomous AI — no human behind it — that the average person cannot distinguish from a real person in live conversation. If this ever happens, Bitpeople breaks. This is currently science fiction, but it defines a theoretical boundary of the system.

---

# Scheduling

Current period calculated from genesis timestamp and fixed periodicity. Event scheduled on the weekend for all time zones. Hour varies per period for fairness. Between RNG and the next period's Event there is a pause, so that all participants know the seed and their pairing in advance.

Example 28-day period: Event 6, Register 6, Mixer 3, Mixer 3, Mixer 3, RNG 5, Pause 2.

## Growth

Invites are capped at `population_count / 2` per period — at most 50% growth per period. Roughly exponential — from 2 people to billions in a few years.
