# Bitpeople Transaction Specs

Formal definitions for each transaction type. Each block is self-contained: input, signature, conditions, state change.

All transactions include `prev_root` and are signed by the specified key, unless otherwise noted. Signature covers: `sign(prev_root || data)`.

---

## Verify

**Phase:** Event

**Signed message:** `prev_root || "verify"`

**Signer:** `nym_a` or `nym_b`

**Conditions:**
- `prev_root` matches phase start root
- `disputed` is false

**State change:**
- `verified_a` → true (if signer is nym_a)
- `verified_b` → true (if signer is nym_b)

---

## Dispute

**Phase:** Event

**Signed message:** `prev_root || "dispute"`

**Signer:** `nym_a` or `nym_b`

**Conditions:**
- `prev_root` matches phase start root
- Signer's `verified` is false

**State change:**
- `disputed` → true
- `verified_a/b` → false
- `court_judged_a/b` → false

---

## Judge

**Phase:** Event

**Signed message:** `prev_root || "judge"`

**Signer:** `nym_a` or `nym_b` on court leaf

**Conditions:**
- `prev_root` matches phase start root
- `court` is non-zero (invite person exists)

**State change:**
- `court_judged_a` → true (if signer is nym_a)
- `court_judged_b` → true (if signer is nym_b)

---

## JudgeDispute

**Phase:** Event

**Signed message:** `prev_root || "judge_dispute" || target_nym_id`

**Signer:** `nym_a` or `nym_b` on court leaf

**Conditions:**
- `prev_root` matches phase start root
- Target nym has `disputed` true
- `target_nym_id mod pair_count` points to signer's leaf
- Target nym address is non-zero

**State change (on target leaf):**
- If target is nym_a: `judged_a_a` → true (if signer is nym_a on court leaf), `judged_a_b` → true (if signer is nym_b on court leaf)
- If target is nym_b: `judged_b_a` → true (if signer is nym_a on court leaf), `judged_b_b` → true (if signer is nym_b on court leaf)

---

## Register

**Phase:** Register, Mixer

**Signed message:** `prev_root || "register" || address || mixer_hash`

**Signer:** nym_a/b/c for Register phase, or current address for Mixer phases

**Conditions:**
- `prev_root` matches phase start root
- If signer is nym_a or nym_b: pair verified (both `verified_a` and `verified_b`) OR judged (`judged_a_a` && `judged_a_b` for nym_a, `judged_b_a` && `judged_b_b` for nym_b)
- If signer is court: both `court_judged_a` and `court_judged_b` true, and pair verified
- Mixer phases: one registration per slot per phase

**State change:**
- `registration_a/b/c` → address
- `mixer_hash_a/b/c` → mixer_hash

---

## Invite

**Phase:** Register

**Signed message:** `prev_root || "invite" || address`

**Signer:** `nym_a` or `nym_b`

**Conditions:**
- `prev_root` matches phase start root
- Signer is nym_a or nym_b (not court)
- Pair verified or judged (`judged_a_a` && `judged_a_b` for nym_a, `judged_b_a` && `judged_b_b` for nym_b)
- `court` is non-zero on the signer's leaf and leaf index < `population_count / 2`, or signer is nym 0

**State change:**
- `invite_a` → address (if signer is nym_a)
- `invite_b` → address (if signer is nym_b)

---

## Commit

**Phase:** Register

**Signed message:** `prev_root || "commit" || hash`

**Signer:** `nym_a/b/c`

**Conditions:**
- `prev_root` matches phase start root
- Signer is verified (pair verified, judged, or court_judged)

**State change:**
- `new_commit_a/b/c` → hash

---

## Elect

**Phase:** Register

**Signed message:** `prev_root || "elect" || address`

**Signer:** `nym_a/b/c`

**Conditions:**
- `prev_root` matches phase start root
- Signer is verified (pair verified, judged, or court_judged)

**State change:**
- `new_elected_a/b/c` → address

---

## InvalidateMixer

**Phase:** Mixer, RNG

**Message:** `preimage` (no signature — preimage is proof of ownership)

**preimage:** `leaf_id || mixer_idx || new_address || nonce` (leaf_id is 8 bytes, mixer_idx is 1 byte, nonce is 32 bytes)

**Conditions:**
- `hash(preimage)` matches sender's `mixer_hash`
- Partner leaf (from `preimage.leaf_id`) exists

**Validation:**
1. Hash preimage → verify against sender's `mixer_hash`
2. Locate partner leaf from preimage
3. Check partner's `address` equals `preimage.new_address`
4. Compute `hash(sender_leaf_id, sender_mixer_idx, partner_new_address, preimage.nonce)` → compare against partner's `mixer_hash`
5. If step 3 or 4 fails, mixer is invalidated

**Application order:** InvalidateMixer is applied immediately. Register is applied at phase end.

**State change:**
- Sender's address restored to `preimage.new_address` (original address), mixer_hash zeroed

---

## Reveal

**Phase:** RNG

**Message:** `preimage` (256-bit, no signature)

**Conditions:**
- `hash(preimage)` matches the `commit` on the target nym

**State change:**
- Target = `(nym_position + uint(preimage)) mod population_count`
- Target's `seed_hits` incremented
