Protocol reference · Go · zero dependencies · self-verifying

peoplecoin

Pluggable UTXO + UBI by demurrage, compatible with any population register. One person, one unit of stake. The core provides payments, the universal basic income, and consensus; the register is a module — anything that can answer “who is a verified unique human” plugs in. Two implementations ship: bitpeople (periodic anonymous verification) and hierarchy (hierarchical attestation).

3 packages5 742 lines of Go21 tests2 demos, both replay-verified0 external deps

Architecture

The cut sits where the spec put it: the value layer and the election mechanics are identity-agnostic. Each module answers the interface’s seven points; everything else is shared.

CORE binary Merkle radix trie · delta-maintained counts (uint64) · Merkle proofs · max_seed_hits · sum Mix — anonymizing permutation UTXO · Transfer · Prune · demurrage Election — mix + commit + multi-validator hash onion norm advance = planned era event INTERFACE 1 population_count 2 is_verified(key) 3 randomness 4 election entries 5 UBI minting 6 header fields 7 transactions BITPEOPLE pairs verified on video · mixed each period per-period seed · schedule peeled in place UBI per Vote · anonymous, untraceable HIERARCHY people tree · your superior attested you per-block rand ^= H(nonce) · two tries continuous UBI · maps onto civil registry

The core

Trie — the frozen index

A path-compressed binary Merkle radix trie over 32-byte keys, canonical for its key set. The count aggregate is delta-maintained, never re-derived: Insert, Remove and Touch adjust it along the path; Refresh rehashes after in-place mutation but never recounts. That makes the frozen index a structural property — after the swap nothing issues deltas, so a revealed entry keeps its rank, its leaf count, and its Merkle provability while its commit is consumed.

Mix

Atomic permutation within a trie: inputs consumed, outputs inserted with all fields zeroed — uniform except mix_count. The budget rule Σ out ≥ Σ in + count(outputs) lets counts redistribute freely, so no mapping is visible. One honest participant breaks traceability.

UTXO — value that decays

20% annual decay is the UBI. Rent accrues per second and is settled when the coin moves; it never destroys value — it relocates it from the owner to the validator. Coins whose spendable value reaches zero can be pruned by any producer, who collects the remainder.

spendable(T) = amount × decay^(T−time) / SCALE − rent_owed(T)
rent_owed(T) = rent × (SCALE − decay^(T−time)) / (SCALE − decay)
COIN:
t = 8.0 yrvalue rent owed spendable

Election

Entry created (by the module) → Mix breaks the voter↔validator link → Commit sets the onion top (requires mix_count > 0) → selection by random_value mod count → each block’s reveal peels one layer. The peel is the shared role; what a module derives from the nonce beyond it — hierarchy feeds it into its randomness — is interface point 3 and lives outside the peel. The onion itself is demonstrated below — with real hashes.

Norm advance is a planned event

Renormalization (sum ×= decay^Δ) rewrites every contribution and every coin hash — inherently O(N), impossible inside a 60-second slot at scale. So the node runs its whole era on a fixed epoch (uint128 gives 100+ years of headroom while norms grow), and the advance is a coordinated, precomputable era upgrade — coordination, not consensus computation. UTXOSet.Renormalize is the tool for the event.

The hash onion

Every election commit is the top of a multi-validator hash onion. Producing a block peels one layer: the header carries (validator, nonce), everyone checks H(validator ‖ nonce) = commit, and the commit becomes the nonce’s trailing 32 bytes — the next layer, possibly naming a different validator. DoS protection at every reveal, not just the first. A 32-byte nonce is the deepest layer: the entry is consumed.

This onion is real. The hashes below are actual SHA-256 chains — peel it and check.

The interface, answered twice

Pointbitpeoplehierarchy
population_countpair-verified ∨ judged, per periodroot.tree_count (persons in the tree)
is_verifiedMerkle proof of a verified nym entrytree membership
randomnessper-period seed from reveals; sequence public, onion is the DoS shieldper-block: rand ^= H(nonce) at every reveal
election entriesauto on Commit(n) and Judge promotionVoteClaim, once per person per year
UBI mintinglump per Vote: TOKEN × (1 − decay^period)continuous: TOKEN × (1 − decay^(T − last_ubi))
header fieldssix trie rootspeople_tree, two election roots, rand
transactionsVerify · Judge · JudgeNym · Use · Commit · Vote · RevealAdd · Remove · Move · Leave · Rekey · UBIClaim · VoteClaim · Commit

The two UBI rows share one kernel — core.AccruedUBI(elapsed) = TOKEN × (1 − decay^elapsed); the lump is the kernel over one period. The trigger difference is forced, not stylistic: continuous accrual needs a per-person last_ubi, and any accumulator surviving the Mix would be a tracking tag through it. A public tree can accrue continuously; an anonymous register must pay a uniform, vote-gated lump to fresh keys.

bitpeople — anonymous, periodic

Every 28 days the whole population verifies pairwise on live video, pairs drawn by a Feistel shuffle over the register — no physical reordering, ever. Proofs are valid one period and untraceable between periods, because everyone passes through the mixer.

EVENT · 14dverify · judge · mix · commit · vote RNG · 7dreveals target seed_hits PAUSE · 7dcompute next pairs ↑ commit deadline swap at boundary →
VerifyCommitVoteUBI

No step can be skipped. The commit is the registration act: it adds the entry to the index (one Touch per Commit), the index locks at the swap, and Reveal may then consume the commit as replay protection — count and ordering are structurally unaffected, and the revealed entry stays a valid RNG target. The election schedule is copied down at the boundary and peeled in place, block by block; the live election trie is always next period’s vote under construction.

hierarchy — attested, continuous

Your superior attested your identity; the tree maps onto civil registration. Persons are leaves, tree_count aggregates upward, and the operations carry consent: Add and Move need both signatures, Leave is child-initiated, Remove auto-mints the accrued UBI of everyone detached, Rekey moves the node to a new key with a replay-counted nonce.

root org org S person · 1 person · 1 person · 1 population = root.tree_count claimable(T) = TOKEN × (1 − decay^(T − last_ubi))

Two election tries swap yearly; randomness is per block, rand ^= H(nonce) at every reveal — the onion nonce does double duty as entropy. Reveals peel the active trie live: consumption drops the count, and rand mod count simply adapts.

What it provides — and how a registry plugs in

Payments

A complete UTXO layer: transfers, fees to block producers, pruning of expired dust. Amounts are uint128 fixed-point; supply arithmetic is fraud-provable from the sum aggregate in every header.

Basic income

Funded by demurrage, not taxation: 20% annual decay is the issuance, and each person's holdings converge to one TOKEN. No inflation lever to govern — the money supply follows the population.

Elections & consensus

60-second slots, validator selection from onion-protected commits (DoS shield at every reveal), fork choice by fewest skips, and every block replay-verifiable from genesis.

Proof of person

Membership is provable against any block header: a Merkle proof in bitpeople, the attested tree in hierarchy. External systems verify without trusting a server.

A civil registry as the population module

The hierarchy module is shaped for this: a registry is a people tree — the authority at the root, offices as organization nodes, persons as leaves. The registration acts a registry already performs become signed transactions:

Registry eventTransactionOn-chain effect
New registration (birth, immigration)Add — office + subject consentPerson leaf created; UBI starts accruing immediately
Deregistration (death, emigration)Remove — office signatureSubtree detached; accrued UBI auto-minted to the persons' keys
Relocation between officesMove — subject + receiving officeNode reparented; income and vote state preserved
Subject-initiated exitLeave — subject signatureAs Remove, self-signed
Lost or rotated credentialsRekey — old + new key, replay-countedKey replaced; last_ubi, vote state and children follow
IncomeUBIClaim — anytime, any cadenceTOKEN × (1 − decay^elapsed) minted; claim timing is economically neutral
Validator electionVoteClaim → Mix → Commit — yearlyOne vote per person; the mix unlinks voter from validator

What the registry operates is small: keys for its organization nodes and signatures on the acts above. Money, consensus, elections and audit come with the core — every registration is a signed, replayable transaction, and the tree hash sits in every block header. Where no registry exists, or none is trusted, the same monetary design runs trustlessly under the bitpeople module instead: same coin semantics, a different answer to who counts.

Source — all of it

Every file, grouped by package. Click to expand; syntax coloring is applied on first open. The historical single-file reference bpc (spec v21, pre-onion) ships separately.

built in your browser from the code on this page (zip by JSZip via cdnjs)
core/the shared core1388 lines
amount.go141
package core

// amount.go — hiercoin's fixed-point monetary math, ported verbatim.
//
// All UTXO values decay at 20% per YEAR (the demurrage IS the UBI),
// every UTXO pays trie rent, and all exponentiation uses binary
// exponentiation with floor rounding at every step — deterministic
// and bit-reproducible on every node.

import (
	"math/big"
	"sync"
)

// Fixed-point scale: 1 TOKEN = 10^16 base units.
var (
	Scale = big.NewInt(10_000_000_000_000_000)
	TOKEN = big.NewInt(10_000_000_000_000_000)

	// Per-second decay factor at scale 10^16: the largest integer
	// where power(decay, YEAR) < 0.8 × SCALE. 20% per year. Value
	// fixed by the hiercoin spec.
	DecayPerSecond = big.NewInt(9_999_999_929_290_076)

	// RentPerSecond is the trie rent: 1000 base units per second per
	// UTXO. Value fixed by the hiercoin spec.
	RentPerSecond = big.NewInt(1000)

	// rentDenom = SCALE − decay, the per-second fixed-point loss.
	rentDenom = new(big.Int).Sub(Scale, DecayPerSecond)
)

const SecondsPerYear = uint64(31_557_600) // Julian year

// All periodic operations use intervals that are multiples or
// divisors of YEAR, aligned to Unix time 0. norm_time sits on
// multiples of 4 × YEAR from Unix 0 (1970, 1974, ..., 2026, ...).
const NormPeriod = 4 * SecondsPerYear

// normTimeFor returns the norm_time boundary for a genesis at t:
// the largest multiple of 4 × YEAR from Unix 0 that is ≤ t.
func NormTimeFor(t uint64) uint64 { return t - t%NormPeriod }

// mulScale computes floor(a*b / Scale) for non-negative a, b.
// This is THE rounding rule of the system: floor at every step.
func MulScale(a, b *big.Int) *big.Int {
	r := new(big.Int).Mul(a, b)
	return r.Quo(r, Scale)
}

var (
	powMu    sync.Mutex
	powCache = map[uint64]*big.Int{}
)

// DecayPow returns decay^dt at scale 10^16, computed by binary
// exponentiation with floor rounding at every step.
func DecayPow(dt uint64) *big.Int {
	powMu.Lock()
	if c, ok := powCache[dt]; ok {
		r := new(big.Int).Set(c)
		powMu.Unlock()
		return r
	}
	powMu.Unlock()

	res := new(big.Int).Set(Scale)
	base := new(big.Int).Set(DecayPerSecond)
	for e := dt; e > 0; e >>= 1 {
		if e&1 == 1 {
			res = MulScale(res, base)
		}
		if e > 1 {
			base = MulScale(base, base)
		}
	}

	powMu.Lock()
	powCache[dt] = new(big.Int).Set(res)
	powMu.Unlock()
	return res
}

// PowScaled is DecayPow generalized to any base (used by tests to
// check the spec constant is the largest valid decay).
func PowScaled(base *big.Int, dt uint64) *big.Int {
	res := new(big.Int).Set(Scale)
	b := new(big.Int).Set(base)
	for e := dt; e > 0; e >>= 1 {
		if e&1 == 1 {
			res = MulScale(res, b)
		}
		if e > 1 {
			b = MulScale(b, b)
		}
	}
	return res
}

// Normalize converts a real amount at time t to its normalized value
// at reference time norm: normalized = floor(amount * Scale / decay^(t-norm)).
func Normalize(amount *big.Int, t, norm uint64) *big.Int {
	if t < norm {
		panic("Normalize: time before norm_time")
	}
	p := DecayPow(t - norm)
	r := new(big.Int).Mul(amount, Scale)
	return r.Quo(r, p)
}

// ValueAt converts a normalized value back to its real value at time T.
func ValueAt(normVal *big.Int, T, normTime uint64) *big.Int {
	if T < normTime {
		panic("ValueAt: time before norm_time")
	}
	return MulScale(normVal, DecayPow(T-normTime))
}

// RentOwed is the accumulated rent on a UTXO created dt seconds ago:
//
//	rent_owed = rent × (SCALE − decay^dt) / (SCALE − decay)
//
// Rent and demurrage are coupled — each unit of rent accrues
// demurrage from the moment it is deducted, which sums to the
// geometric series above.
func RentOwed(dt uint64) *big.Int {
	if dt == 0 {
		return new(big.Int)
	}
	n := new(big.Int).Sub(Scale, DecayPow(dt))
	n.Mul(n, RentPerSecond)
	return n.Quo(n, rentDenom)
}

// tokens returns n * TOKEN as a fresh big.Int.
func tokens(n uint64) *big.Int {
	return new(big.Int).Mul(new(big.Int).SetUint64(n), TOKEN)
}

// SlotSeconds is the shared block slot.
const SlotSeconds = uint64(60)
coin.go126
package core

import (
	"errors"
	"math/big"
)

// ============================================================== utxo

// CoinEntry — the payment layer, keyed by hash(tx || idx).
type CoinEntry struct {
	Owner  PubKey
	Amount *big.Int
	Norm   *big.Int // amount normalized to norm_time
	Time   uint64
}

func (e *CoinEntry) Value(T uint64) *big.Int {
	if T < e.Time {
		panic("value query before creation")
	}
	return MulScale(e.Amount, DecayPow(T-e.Time))
}

func (e *CoinEntry) Rent(T uint64) *big.Int { return RentOwed(T - e.Time) }

func (e *CoinEntry) Spendable(T uint64) *big.Int {
	return new(big.Int).Sub(e.Value(T), e.Rent(T))
}

func (e *CoinEntry) Expired(T uint64) bool { return e.Spendable(T).Sign() <= 0 }

func (e *CoinEntry) LeafHash(k [32]byte) [32]byte {
	var w Buf
	w.U8(0x16)
	w.Bytes(k[:])
	w.Bytes(e.Owner[:])
	w.U128(e.Amount)
	w.U64(e.Time)
	return H(w.B)
}

// Outpoint → key: hash(tx || idx), per the spec.
func CoinKey(tx [32]byte, index uint32) [32]byte {
	var w Buf
	w.Bytes(tx[:])
	w.U32(index)
	return H(w.B)
}

// CoinEntry implements Entry so coins live in the same trie
// structure; CoinBranch carries sum and no count.
func (e *CoinEntry) Counted() bool    { return true }
func (e *CoinEntry) HitsVal() uint32  { return 0 }
func (e *CoinEntry) MixCt() uint32    { return 0 }
func (e *CoinEntry) SumVal() *big.Int { return e.Norm }
func (e *CoinEntry) CloneEntry() Entry {
	c := *e
	c.Amount = new(big.Int).Set(e.Amount)
	c.Norm = new(big.Int).Set(e.Norm)
	return &c
}

// UTXOSet is the value trie: CoinBranch {sum, left, right}.
type UTXOSet struct{ t *Trie }

func NewUTXOSet() *UTXOSet {
	return &UTXOSet{t: NewTrie(0x17, nil, false, true)}
}

func (u *UTXOSet) Insert(k [32]byte, e *CoinEntry) error { return u.t.Insert(k, e) }

func (u *UTXOSet) Get(k [32]byte) *CoinEntry {
	if e := u.t.Get(k); e != nil {
		return e.(*CoinEntry)
	}
	return nil
}

func (u *UTXOSet) Spend(k [32]byte) (*CoinEntry, error) {
	e, err := u.t.Remove(k)
	if err != nil {
		return nil, errors.New("utxo: missing or already spent")
	}
	return e.(*CoinEntry), nil
}

func (u *UTXOSet) Sum() *big.Int   { return u.t.Sum() }
func (u *UTXOSet) Len() int        { return u.t.Len() }
func (u *UTXOSet) Root() [32]byte  { return u.t.Root() }
func (u *UTXOSet) Clone() *UTXOSet { return &UTXOSet{t: u.t.Clone()} }

func (u *UTXOSet) ForEach(f func(k [32]byte, e *CoinEntry)) {
	u.t.ForEach(func(k [32]byte, e Entry) { f(k, e.(*CoinEntry)) })
}

// Renormalize advances the norm_time epoch: "When norm_time
// advances by Δ: sum ×= decay^Δ". Applied per contribution (floor)
// with branch sums rebuilt exactly, so parent = left + right holds
// and sum-Merkle proofs stay sound; the root lands within one base
// unit per leaf of the ideal rescale. Entries themselves store
// {amount, time} and are never touched — which is also why the
// naive alternative (recompute norms from raw amounts) is wrong:
// after an advance every existing entry has time < norm_time, a
// negative exponent the fixed-point math has no business seeing.
// Advancement is rare by design: uint128 leaves 100+ years of
// headroom at planetary scale.
func (u *UTXOSet) Renormalize(oldNorm, newNorm uint64) {
	factor := DecayPow(newNorm - oldNorm)
	var walk func(n *node) *big.Int
	walk = func(n *node) *big.Int {
		if n == nil {
			return new(big.Int)
		}
		n.dirty = true
		if n.isLeaf() {
			ce := n.entry.(*CoinEntry)
			ce.Norm = MulScale(ce.Norm, factor)
			n.sum = new(big.Int).Set(ce.Norm)
			return n.sum
		}
		n.sum = new(big.Int).Add(walk(n.left), walk(n.right))
		return n.sum
	}
	walk(u.t.root)
}
core_test.go117
package core

import (
	"math/big"
	"testing"
)

// TestOnion — the multi-validator hash onion: peels through
// different validators in order, rejects wrong material, consumes
// at the deepest (32-byte) layer.
func TestOnion(t *testing.T) {
	var vals []PubKey
	var privs [][32]byte
	for i := 0; i < 4; i++ {
		_, p := GenKey()
		vals = append(vals, p)
		privs = append(privs, [32]byte(p))
	}
	_ = privs
	ctr := byte(0)
	det := func(p []byte) {
		for i := range p {
			p[i] = ctr
			ctr++
		}
	}
	commit, layers := BuildOnion(vals, det)
	if len(layers) != 4 {
		t.Fatal("layer count")
	}
	cur := commit
	for i, l := range layers {
		// Wrong validator fails.
		wrong := vals[(i+1)%4]
		if _, ok := VerifyPeel(cur, wrong, l.Nonce); ok && wrong != l.Validator {
			t.Fatalf("layer %d peeled by the wrong validator", i)
		}
		// Wrong nonce fails.
		bad := append([]byte{}, l.Nonce...)
		bad[0] ^= 1
		if _, ok := VerifyPeel(cur, l.Validator, bad); ok {
			t.Fatalf("layer %d peeled with a tampered nonce", i)
		}
		next, ok := VerifyPeel(cur, l.Validator, l.Nonce)
		if !ok {
			t.Fatalf("layer %d rejected valid reveal", i)
		}
		if i < 3 {
			if len(l.Nonce) != 64 || next == Zero32 {
				t.Fatalf("layer %d: expected 64-byte nonce with successor", i)
			}
		} else {
			if len(l.Nonce) != 32 || next != Zero32 {
				t.Fatal("deepest layer must consume (32-byte nonce, zero successor)")
			}
		}
		cur = next
	}
}

// TestNormAdvance — the planned-event tool: norm ×= decay^Δ per
// contribution, exact branch sums, real values untouched.
func TestNormAdvance(t *testing.T) {
	old := uint64(14) * NormPeriod
	newN := old + NormPeriod
	u := NewUTXOSet()
	var keys [][32]byte
	var oldNorms []*big.Int
	for i := 0; i < 7; i++ {
		_, p := GenKey()
		amt := new(big.Int).Mul(big.NewInt(int64(i+1)), big.NewInt(1_000_000_000))
		tm := old + uint64(i)*100000
		k := CoinKey(H([]byte{byte(i)}, []byte("norm")), 0)
		nv := Normalize(amt, tm, old)
		if err := u.Insert(k, &CoinEntry{Owner: p, Amount: amt, Norm: nv, Time: tm}); err != nil {
			t.Fatal(err)
		}
		keys = append(keys, k)
		oldNorms = append(oldNorms, new(big.Int).Set(nv))
	}
	T := newN + 1000*SlotSeconds
	valBefore := u.Get(keys[3]).Value(T)
	sumBefore := u.Sum()
	u.Renormalize(old, newN)
	factor := DecayPow(NormPeriod)
	total := new(big.Int)
	for i, k := range keys {
		want := MulScale(oldNorms[i], factor)
		if u.Get(k).Norm.Cmp(want) != 0 {
			t.Fatalf("leaf %d rescale wrong", i)
		}
		total.Add(total, want)
	}
	if u.Sum().Cmp(total) != 0 {
		t.Fatal("branch sums not exact over rescaled leaves")
	}
	ideal := MulScale(sumBefore, factor)
	diff := new(big.Int).Sub(ideal, u.Sum())
	if diff.Sign() < 0 || diff.Cmp(big.NewInt(int64(len(keys)))) > 0 {
		t.Fatalf("sum ×= decay^Δ off by %s", diff)
	}
	if u.Get(keys[3]).Value(T).Cmp(valBefore) != 0 {
		t.Fatal("rescale changed a real value")
	}
}

// TestDecayConstant — decay^YEAR ≈ 0.8: 20% annual demurrage.
func TestDecayConstant(t *testing.T) {
	p := DecayPow(SecondsPerYear)
	want := new(big.Int).Quo(new(big.Int).Mul(Scale, big.NewInt(8)), big.NewInt(10))
	diff := new(big.Int).Sub(p, want)
	diff.Abs(diff)
	lim := new(big.Int).Quo(Scale, big.NewInt(1_000_000))
	if diff.Cmp(lim) > 0 {
		t.Fatalf("decay^YEAR = %s, want ≈ %s", p, want)
	}
}
election.go97
package core

import "math/big"

// =========================================================== election

// ElectionEntry — commit = hash(validator || nonce).
type ElectionEntry struct {
	MixCount uint32
	Commit   [32]byte
}

func (e *ElectionEntry) LeafHash(k [32]byte) [32]byte {
	var w Buf
	w.U8(0x14)
	w.Bytes(k[:])
	w.U32(e.MixCount)
	w.Bytes(e.Commit[:])
	return H(w.B)
}

func (e *ElectionEntry) Counted() bool     { return e.Commit != [32]byte{} }
func (e *ElectionEntry) HitsVal() uint32   { return 0 }
func (e *ElectionEntry) SumVal() *big.Int  { return nil }
func (e *ElectionEntry) MixCt() uint32     { return e.MixCount }
func (e *ElectionEntry) CloneEntry() Entry { c := *e; return &c }

func FreshElection(mc uint32) Entry { return &ElectionEntry{MixCount: mc} }

func NewElectionTrie() *Trie { return NewTrie(0x15, FreshElection, false, false) }

// ======================================================== hash onion
//
// Multi-validator hash onion: each layer can name a different
// validator — DoS protection at every reveal, not just the first.
//
//	layer_0 = H(val_Z ‖ nonce_Z)            (deepest: 32-byte nonce)
//	layer_1 = H(val_Y ‖ nonce_Y ‖ layer_0)
//	commit  = H(val_X ‖ nonce_X ‖ layer_1)
//
// The voter constructs the onion and gives each validator their
// (nonce ‖ next_layer) privately — 64 bytes, or 32 at the deepest
// layer. The block header carries (validator, nonce); verification
// is H(validator ‖ nonce) == commit, after which commit becomes the
// last 32 bytes of the nonce. A 32-byte nonce consumes the entry.

// OnionLayer is one validator's private reveal material.
type OnionLayer struct {
	Validator PubKey
	Nonce     []byte // 64 bytes, or 32 at the deepest layer
}

// BuildOnion constructs an onion naming the given validators from
// the outermost layer inward, returning the commit (the top) and
// each layer's reveal material.
func BuildOnion(validators []PubKey, rand func([]byte)) (commit [32]byte, layers []OnionLayer) {
	n := len(validators)
	layers = make([]OnionLayer, n)
	below := [32]byte{}
	for i := n - 1; i >= 0; i-- {
		var r [32]byte
		rand(r[:])
		var nonce []byte
		if i == n-1 {
			nonce = append([]byte{}, r[:]...) // deepest: 32 bytes
		} else {
			nonce = append(append([]byte{}, r[:]...), below[:]...) // 64
		}
		layers[i] = OnionLayer{Validator: validators[i], Nonce: nonce}
		below = H(validators[i][:], nonce)
	}
	return below, layers
}

// VerifyPeel checks a reveal against a commit and returns the
// commit's successor: the nonce's trailing 32 bytes, or zero when
// the deepest (32-byte) nonce consumed the onion. A zero commit is
// "consumed" in every module's semantics — uncounted, unprovable.
//
// Peeling is the election's shared role. Anything else a module
// derives from the nonce — hierarchy feeds it into its per-block
// randomness — is module RNG (interface point 3) and lives outside
// the peel.
func VerifyPeel(commit [32]byte, validator PubKey, nonce []byte) ([32]byte, bool) {
	if len(nonce) != 32 && len(nonce) != 64 {
		return Zero32, false
	}
	if H(validator[:], nonce) != commit {
		return Zero32, false
	}
	if len(nonce) == 32 {
		return Zero32, true // onion spent
	}
	var next [32]byte
	copy(next[:], nonce[32:])
	return next, true
}
enc.go81
package core

// enc.go — canonical encoding + hashing primitives.
//
// Buf is a tiny canonical encoder: fixed-width big-endian fields.
// Every hash and signature in the system is computed over encodings
// produced by this type, so the byte layout here IS the consensus
// format. Ported from hiercoin.

import (
	"crypto/sha256"
	"encoding/binary"
	"math/big"
)

type Buf struct{ B []byte }

func (w *Buf) U8(x byte) { w.B = append(w.B, x) }

func (w *Buf) U32(x uint32) {
	var t [4]byte
	binary.BigEndian.PutUint32(t[:], x)
	w.B = append(w.B, t[:]...)
}

func (w *Buf) U64(x uint64) {
	var t [8]byte
	binary.BigEndian.PutUint64(t[:], x)
	w.B = append(w.B, t[:]...)
}

// u128 writes a non-negative big.Int as 16 bytes big-endian.
// Panics on overflow: amounts are defined as unsigned 128-bit.
func (w *Buf) U128(x *big.Int) { w.B = append(w.B, U128Bytes(x)...) }

func (w *Buf) Bytes(p []byte) { w.B = append(w.B, p...) }

func (w *Buf) Boolb(v bool) {
	if v {
		w.U8(1)
	} else {
		w.U8(0)
	}
}

// U128Bytes encodes x as exactly 16 big-endian bytes.
func U128Bytes(x *big.Int) []byte {
	if x.Sign() < 0 || x.BitLen() > 128 {
		panic("amount out of u128 range")
	}
	var out [16]byte
	x.FillBytes(out[:])
	return out[:]
}

// H is SHA-256 over the concatenation of parts. SHA-256 is the only
// hash in the system (both parent specs).
func H(parts ...[]byte) [32]byte {
	h := sha256.New()
	for _, p := range parts {
		h.Write(p)
	}
	var o [32]byte
	copy(o[:], h.Sum(nil))
	return o
}

var Zero32 [32]byte

func Xor32(a, b [32]byte) (o [32]byte) {
	for i := range o {
		o[i] = a[i] ^ b[i]
	}
	return
}

func U64be(x uint64) []byte {
	var t [8]byte
	binary.BigEndian.PutUint64(t[:], x)
	return t[:]
}
keys.go48
package core

// keys.go — Ed25519, the signature scheme of both parent specs.
// Every signature in the system covers an encoding that begins with
// an opcode ("all signatures include an opcode" — hiercoin).

import (
	"crypto/ed25519"
	"crypto/rand"
	"encoding/hex"
)

// PubKey is a raw Ed25519 public key. In bitpeople terms this is a
// nym address; in hiercoin terms a node/owner key. Addresses and
// keys are the same 32-byte type throughout.
type PubKey [32]byte

// Sig is a raw Ed25519 signature.
type Sig [64]byte

func GenKey() (ed25519.PrivateKey, PubKey) {
	pub, priv, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		panic(err)
	}
	var p PubKey
	copy(p[:], pub)
	return priv, p
}

func Pub(priv ed25519.PrivateKey) PubKey {
	var p PubKey
	copy(p[:], priv.Public().(ed25519.PublicKey))
	return p
}

func Sign(priv ed25519.PrivateKey, msg []byte) Sig {
	var s Sig
	copy(s[:], ed25519.Sign(priv, msg))
	return s
}

func VerifySig(pub PubKey, msg []byte, sig Sig) bool {
	return ed25519.Verify(pub[:], msg, sig[:])
}

func (k PubKey) Short() string    { return hex.EncodeToString(k[:4]) }
func ShortHash(h [32]byte) string { return hex.EncodeToString(h[:8]) }
mix.go121
package core

// Mix — the shared generic primitive: atomic permutation within a
// trie. Phase gating and trie routing belong to the population
// module; the core validates and applies.

import (
	"errors"
	"fmt"
)

const (
	OpTransfer byte = 0x01
	OpPrune    byte = 0x02
	OpMix      byte = 0x20

	MaxInputs  = 1024
	MaxOutputs = 1024
)

type Tx interface {
	Body() []byte
	TxID() [32]byte
}

// =============================================================== mix

// Mix — the generic primitive: atomic permutation within a trie.
// Inputs consumed (uncommitted/unused only — commit and use end an
// entry's mixable life), outputs recreated under fresh keys with all
// fields zeroed except the declared mix_count.
type Mix struct {
	TrieID  byte
	Inputs  [][32]byte
	Outputs []MixOutput
	Sigs    []Sig
}

type MixOutput struct {
	Key      [32]byte
	MixCount uint32
}

func (t *Mix) Body() []byte {
	var w Buf
	w.U8(OpMix)
	w.U8(t.TrieID)
	w.U32(uint32(len(t.Inputs)))
	for i := range t.Inputs {
		w.Bytes(t.Inputs[i][:])
	}
	w.U32(uint32(len(t.Outputs)))
	for i := range t.Outputs {
		w.Bytes(t.Outputs[i].Key[:])
		w.U32(t.Outputs[i].MixCount)
	}
	return w.B
}
func (t *Mix) TxID() [32]byte { return H(t.Body()) }

// ApplyMix validates and applies a Mix against its routed trie —
// the population module resolves TrieID and gates the phase.
func ApplyMix(trie *Trie, t *Mix) error {
	n := len(t.Inputs)
	if n < 2 || n > MaxInputs {
		return errors.New("mix: need at least 2 inputs")
	}
	if len(t.Outputs) != n || len(t.Sigs) != n {
		return errors.New("mix: inputs, outputs and sigs must match in count")
	}
	id := t.TxID()
	inSum := uint64(0)
	seen := map[[32]byte]struct{}{}
	for i, k := range t.Inputs {
		if _, dup := seen[k]; dup {
			return errors.New("mix: duplicate input")
		}
		seen[k] = struct{}{}
		e := trie.Get(k)
		if e == nil {
			return errors.New("mix: input missing or consumed")
		}
		if e.Counted() {
			return errors.New("mix: input already committed/used")
		}
		if !VerifySig(PubKey(k), id[:], t.Sigs[i]) {
			return fmt.Errorf("mix: bad signature for input %d", i)
		}
		inSum += uint64(e.MixCt())
	}
	outSum := uint64(0)
	oseen := map[[32]byte]struct{}{}
	for i := range t.Outputs {
		o := &t.Outputs[i]
		if _, dup := oseen[o.Key]; dup {
			return errors.New("mix: duplicate output key")
		}
		oseen[o.Key] = struct{}{}
		if _, wasIn := seen[o.Key]; wasIn {
			return errors.New("mix: output key reuses an input key")
		}
		if trie.Get(o.Key) != nil {
			return errors.New("mix: output key not fresh")
		}
		outSum += uint64(o.MixCount)
	}
	if outSum < inSum+uint64(n) {
		return fmt.Errorf("mix: mix_count budget %d < %d required", outSum, inSum+uint64(n))
	}
	for _, k := range t.Inputs {
		if _, err := trie.Remove(k); err != nil {
			return err
		}
	}
	for i := range t.Outputs {
		if err := trie.Insert(t.Outputs[i].Key, trie.Fresh(t.Outputs[i].MixCount)); err != nil {
			return err
		}
	}
	return nil
}
transfer.go129
package core

// Transfer and coin pruning — standard UTXO semantics. The
// difference between input value and outputs is the fee, and the
// rent is part of it: rent does not destroy value, it relocates it
// from the UTXO owner to the validator upon collection.

import (
	"errors"
	"fmt"
	"math/big"
)

// ========================================================= utxo layer

type Outpoint struct {
	Tx    [32]byte
	Index uint32
}

type Output struct {
	Amount *big.Int
	Owner  PubKey
}

// Transfer — standard UTXO semantics; the difference is the fee.
type Transfer struct {
	Inputs  []Outpoint
	Outputs []Output
	Sigs    []Sig
}

func (t *Transfer) Body() []byte {
	var w Buf
	w.U8(OpTransfer)
	w.U32(uint32(len(t.Inputs)))
	for i := range t.Inputs {
		w.Bytes(t.Inputs[i].Tx[:])
		w.U32(t.Inputs[i].Index)
	}
	w.U32(uint32(len(t.Outputs)))
	for i := range t.Outputs {
		w.U128(t.Outputs[i].Amount)
		w.Bytes(t.Outputs[i].Owner[:])
	}
	return w.B
}
func (t *Transfer) TxID() [32]byte { return H(t.Body()) }

// ApplyTransfer spends and creates; returns the fee (value minus
// outputs — the rent relocates to the validator here).
func ApplyTransfer(u *UTXOSet, t *Transfer, T, normTime uint64) (*big.Int, error) {
	if len(t.Inputs) == 0 || len(t.Inputs) > MaxInputs {
		return nil, errors.New("transfer: bad input count")
	}
	if len(t.Outputs) == 0 || len(t.Outputs) > MaxOutputs {
		return nil, errors.New("transfer: bad output count")
	}
	if len(t.Sigs) != len(t.Inputs) {
		return nil, errors.New("transfer: need one signature per input")
	}
	id := t.TxID()
	outSum := new(big.Int)
	for i := range t.Outputs {
		if t.Outputs[i].Amount == nil || t.Outputs[i].Amount.Sign() <= 0 ||
			t.Outputs[i].Amount.BitLen() > 128 {
			return nil, errors.New("transfer: bad output amount")
		}
		outSum.Add(outSum, t.Outputs[i].Amount)
	}
	inVal, inSpend := new(big.Int), new(big.Int)
	seen := map[[32]byte]struct{}{}
	for i, op := range t.Inputs {
		k := CoinKey(op.Tx, op.Index)
		if _, dup := seen[k]; dup {
			return nil, errors.New("transfer: duplicate input")
		}
		seen[k] = struct{}{}
		e := u.Get(k)
		if e == nil {
			return nil, errors.New("transfer: input missing or spent")
		}
		if !VerifySig(e.Owner, id[:], t.Sigs[i]) {
			return nil, fmt.Errorf("transfer: bad signature for input %d", i)
		}
		inVal.Add(inVal, e.Value(T))
		inSpend.Add(inSpend, e.Spendable(T))
	}
	if outSum.Cmp(inSpend) > 0 {
		return nil, fmt.Errorf("transfer: outputs %s exceed spendable %s", outSum, inSpend)
	}
	for _, op := range t.Inputs {
		if _, err := u.Spend(CoinKey(op.Tx, op.Index)); err != nil {
			return nil, err
		}
	}
	for i := range t.Outputs {
		if err := u.Insert(CoinKey(id, uint32(i)), &CoinEntry{
			Owner: t.Outputs[i].Owner, Amount: t.Outputs[i].Amount,
			Norm: Normalize(t.Outputs[i].Amount, T, normTime), Time: T,
		}); err != nil {
			return nil, err
		}
	}
	return new(big.Int).Sub(inVal, outSum), nil
}

// PruneCoins removes expired UTXOs; the validator collects the
// remaining value — economically incentivized cleanup.
func PruneCoins(u *UTXOSet, keys [][32]byte, T uint64) (*big.Int, error) {
	if len(keys) == 0 || len(keys) > MaxInputs {
		return nil, errors.New("prune: bad key count")
	}
	fee := new(big.Int)
	for _, k := range keys {
		e := u.Get(k)
		if e == nil {
			return nil, errors.New("prune: utxo missing")
		}
		if !e.Expired(T) {
			return nil, errors.New("prune: utxo not expired")
		}
		fee.Add(fee, e.Value(T))
		if _, err := u.Spend(k); err != nil {
			return nil, err
		}
	}
	return fee, nil
}
trie.go528
package core

// trie.go — the spec's binary Merkle trie, structurally: a
// path-compressed binary radix trie over 32-byte keys.
//
//   - Canonical for a given key set: every branch sits at the first
//     bit position where its subtree's keys differ, so the shape is
//     independent of insertion order.
//   - O(log N) expected insert/remove/lookup for hash keys; Merkle
//     proofs are the path hashes — proof-of-unique-human per spec.
//   - Branch aggregates are type-specific: count (+ max_seed_hits
//     for nym tries) on identity tries, sum on the UTXO trie.
//
// The count aggregate is delta-maintained, never re-derived: Insert,
// Remove and Touch adjust it along the path, while Refresh (in-place
// mutation of flags, seed_hits, or commit consumption by Reveal)
// recomputes hashes and max_seed_hits but not counts. That is the
// locked index as structure: after the swap nothing issues count
// deltas on an active trie, so "count and ordering are unaffected".

import (
	"errors"
	"math/big"
)

// Entry is a trie payload.
type Entry interface {
	LeafHash(key [32]byte) [32]byte
	Counted() bool    // the registration act: counts toward the index
	HitsVal() uint32  // seed_hits (nym entries; 0 elsewhere)
	SumVal() *big.Int // normalized value (coin entries; nil elsewhere)
	MixCt() uint32
	CloneEntry() Entry
}

func bitAt(k [32]byte, i int) int { return int(k[i>>3]>>(7-uint(i&7))) & 1 }

func firstDiffBit(a, b [32]byte) int {
	for i := 0; i < 32; i++ {
		if x := a[i] ^ b[i]; x != 0 {
			d := i * 8
			for x&0x80 == 0 {
				x <<= 1
				d++
			}
			return d
		}
	}
	return -1
}

type node struct {
	// leaf fields (entry != nil)
	key   [32]byte
	entry Entry
	// branch fields
	bit         int
	left, right *node
	// aggregates and cached hash
	count uint64 // delta-maintained index membership (uint64: v21,
	// the register must cover 10+ billion people)
	hits  uint32   // max seed_hits in subtree (lazy, with hash)
	sum   *big.Int // value tries only (delta-maintained)
	hash  [32]byte
	dirty bool
}

func (n *node) isLeaf() bool { return n.entry != nil }

// Trie configuration mirrors the spec's per-type branch structs.
type Trie struct {
	tag      byte // branch hash domain
	fresh    func(mixCount uint32) Entry
	withHits bool // NymBranch carries max_seed_hits
	withSum  bool // CoinBranch carries sum (and no count)
	root     *node
	n        int
}

// NewTrie configures a trie per its spec branch struct: withHits
// adds max_seed_hits to the branch hash, withSum switches to the
// value-trie form (sum, no count).
func NewTrie(tag byte, fresh func(uint32) Entry, withHits, withSum bool) *Trie {
	return &Trie{tag: tag, fresh: fresh, withHits: withHits, withSum: withSum}
}

// Fresh builds a zeroed entry with the given mix_count (Mix outputs).
func (t *Trie) Fresh(mc uint32) Entry { return t.fresh(mc) }

func c01(e Entry) uint64 {
	if e.Counted() {
		return 1
	}
	return 0
}

func (t *Trie) sumOf(e Entry) *big.Int {
	if !t.withSum {
		return nil
	}
	if v := e.SumVal(); v != nil {
		return new(big.Int).Set(v)
	}
	return new(big.Int)
}

// findLeaf walks by key bits to the leaf the key would occupy.
func (t *Trie) findLeaf(k [32]byte) *node {
	n := t.root
	for n != nil && !n.isLeaf() {
		if bitAt(k, n.bit) == 0 {
			n = n.left
		} else {
			n = n.right
		}
	}
	return n
}

func (t *Trie) Get(k [32]byte) Entry {
	if l := t.findLeaf(k); l != nil && l.key == k {
		return l.entry
	}
	return nil
}

// Insert adds a fresh entry — the key must not exist.
func (t *Trie) Insert(k [32]byte, e Entry) error {
	nl := &node{key: k, entry: e, count: c01(e), sum: t.sumOf(e), dirty: true}
	if t.root == nil {
		t.root = nl
		t.n = 1
		return nil
	}
	probe := t.findLeaf(k)
	if probe.key == k {
		return errors.New("trie: key already exists")
	}
	d := firstDiffBit(k, probe.key)
	// Splice a branch at bit d: descend while branches sit above d.
	link := &t.root
	for {
		n := *link
		if n.isLeaf() || n.bit > d {
			break
		}
		if bitAt(k, n.bit) == 0 {
			link = &n.left
		} else {
			link = &n.right
		}
	}
	old := *link
	br := &node{bit: d, count: old.count, dirty: true}
	if t.withSum {
		br.sum = new(big.Int)
		if old.sum != nil {
			br.sum.Set(old.sum)
		}
	}
	if bitAt(k, d) == 0 {
		br.left, br.right = nl, old
	} else {
		br.left, br.right = old, nl
	}
	*link = br
	// Path deltas from the root down to (and excluding) the new
	// leaf — the leaf was created with its own aggregates.
	dc, ds := nl.count, nl.sum
	n := t.root
	for n != nl {
		n.count += dc
		if t.withSum && ds != nil {
			n.sum.Add(n.sum, ds)
		}
		n.dirty = true
		if n.isLeaf() {
			break
		}
		if bitAt(k, n.bit) == 0 {
			n = n.left
		} else {
			n = n.right
		}
	}
	t.n++
	return nil
}

// Remove consumes an entry (Mix inputs, Prune, UTXO spends).
func (t *Trie) Remove(k [32]byte) (Entry, error) {
	var removed Entry
	var dc uint64
	var ds *big.Int
	var rec func(n *node) (*node, bool)
	rec = func(n *node) (*node, bool) {
		if n == nil {
			return nil, false
		}
		if n.isLeaf() {
			if n.key != k {
				return n, false
			}
			removed, dc, ds = n.entry, n.count, n.sum
			return nil, true
		}
		child, sib := &n.left, n.right
		if bitAt(k, n.bit) == 1 {
			child, sib = &n.right, n.left
		}
		nc, found := rec(*child)
		if !found {
			return n, false
		}
		if nc == nil {
			return sib, true // collapse the branch
		}
		*child = nc
		n.count -= dc
		if t.withSum && ds != nil {
			n.sum.Sub(n.sum, ds)
		}
		n.dirty = true
		return n, true
	}
	nr, found := rec(t.root)
	if !found {
		return nil, errors.New("trie: key missing")
	}
	t.root = nr
	t.n--
	return removed, nil
}

// Touch re-indexes a key after its registration act (Commit / Use):
// the count delta propagates along the path. This is how the index
// is established, one act at a time; active tries never Touch.
func (t *Trie) Touch(k [32]byte) {
	l := t.findLeaf(k)
	if l == nil || l.key != k {
		return
	}
	newC := c01(l.entry)
	delta := int64(newC) - int64(l.count)
	n := t.root
	for {
		n.count = uint64(int64(n.count) + delta)
		n.dirty = true
		if n.isLeaf() {
			break
		}
		if bitAt(k, n.bit) == 0 {
			n = n.left
		} else {
			n = n.right
		}
	}
}

// Refresh marks a key's path for rehash after an in-place mutation
// that must not move the index: flags, seed_hits, commit consumption.
func (t *Trie) Refresh(k [32]byte) {
	n := t.root
	for n != nil {
		n.dirty = true
		if n.isLeaf() {
			return
		}
		if bitAt(k, n.bit) == 0 {
			n = n.left
		} else {
			n = n.right
		}
	}
}

func (t *Trie) Count() uint64 {
	if t.root == nil {
		return 0
	}
	return t.root.count
}

func (t *Trie) Len() int { return t.n }

func (t *Trie) Sum() *big.Int {
	if t.root == nil || t.root.sum == nil {
		return new(big.Int)
	}
	return new(big.Int).Set(t.root.sum)
}

// Rank: a counted key's position in key order — the in-order
// traversal via the count aggregates, O(depth).
func (t *Trie) Rank(k [32]byte) (uint64, bool) {
	rank := uint64(0)
	n := t.root
	for n != nil {
		if n.isLeaf() {
			if n.key == k && n.count == 1 {
				return rank, true
			}
			return 0, false
		}
		if bitAt(k, n.bit) == 0 {
			n = n.left
		} else {
			rank += n.left.count
			n = n.right
		}
	}
	return 0, false
}

// Select: the counted key at a given rank — the inverse walk.
func (t *Trie) Select(rank uint64) ([32]byte, bool) {
	if t.root == nil || rank >= t.root.count {
		return [32]byte{}, false
	}
	n := t.root
	for !n.isLeaf() {
		if rank < n.left.count {
			n = n.left
		} else {
			rank -= n.left.count
			n = n.right
		}
	}
	return n.key, true
}

// CommittedKeys: the counted set in key order (in-order walk).
func (t *Trie) CommittedKeys() [][32]byte {
	out := make([][32]byte, 0, t.Count())
	var walk func(n *node)
	walk = func(n *node) {
		if n == nil {
			return
		}
		if n.isLeaf() {
			if n.count == 1 {
				out = append(out, n.key)
			}
			return
		}
		walk(n.left)
		walk(n.right)
	}
	walk(t.root)
	return out
}

func (t *Trie) ForEach(f func(k [32]byte, e Entry)) {
	var walk func(n *node)
	walk = func(n *node) {
		if n == nil {
			return
		}
		if n.isLeaf() {
			f(n.key, n.entry)
			return
		}
		walk(n.left)
		walk(n.right)
	}
	walk(t.root)
}

// rehash recomputes dirty hashes and max_seed_hits bottom-up.
func (t *Trie) rehash(n *node) {
	if n == nil || !n.dirty {
		return
	}
	if n.isLeaf() {
		n.hits = n.entry.HitsVal()
		n.hash = n.entry.LeafHash(n.key)
		n.dirty = false
		return
	}
	t.rehash(n.left)
	t.rehash(n.right)
	n.hits = n.left.hits
	if n.right.hits > n.hits {
		n.hits = n.right.hits
	}
	n.hash = t.branchHash(n.count, n.hits, n.sum, n.left.hash, n.right.hash)
	n.dirty = false
}

// branchHash encodes exactly the spec's per-type branch struct —
// count is uint64 (v21).
func (t *Trie) branchHash(count uint64, hits uint32, sum *big.Int, l, r [32]byte) [32]byte {
	var w Buf
	w.U8(t.tag)
	if t.withSum {
		w.U128(sum)
	} else {
		w.U64(count)
		if t.withHits {
			w.U32(hits)
		}
	}
	w.Bytes(l[:])
	w.Bytes(r[:])
	return H(w.B)
}

// SelectMaxHits walks the max_seed_hits aggregate to the winner:
// highest seed_hits, leftmost (= lowest key = lowest rank) on ties.
// This is what NymBranch carries the field for — O(log N) instead
// of scanning the population.
func (t *Trie) SelectMaxHits() ([32]byte, uint32, bool) {
	if t.root == nil {
		return [32]byte{}, 0, false
	}
	t.rehash(t.root) // hits are maintained lazily with the hashes
	if t.root.hits == 0 {
		return [32]byte{}, 0, false
	}
	n := t.root
	for !n.isLeaf() {
		if n.left.hits >= n.right.hits {
			n = n.left
		} else {
			n = n.right
		}
	}
	return n.key, n.hits, true
}

func (t *Trie) Root() [32]byte {
	if t.root == nil {
		return Zero32
	}
	t.rehash(t.root)
	return t.root.hash
}

func (t *Trie) Clone() *Trie {
	c := &Trie{tag: t.tag, fresh: t.fresh, withHits: t.withHits, withSum: t.withSum, n: t.n}
	var cp func(n *node) *node
	cp = func(n *node) *node {
		if n == nil {
			return nil
		}
		m := *n
		if n.entry != nil {
			m.entry = n.entry.CloneEntry()
		}
		if n.sum != nil {
			m.sum = new(big.Int).Set(n.sum)
		}
		m.left, m.right = cp(n.left), cp(n.right)
		return &m
	}
	c.root = cp(t.root)
	return c
}

// ============================================================ proofs

// ProofStep is one branch on the path from a leaf to the root, seen
// from the proven key's side.
type ProofStep struct {
	Bit      int
	SibHash  [32]byte
	SibCount uint64
	SibHits  uint32
	SibSum   *big.Int
}

// MerkleProof returns the leaf's frozen count and the sibling chain,
// leaf-to-root. With the trie root from a header, the pair proves
// the entry — a counted, verified nym entry is a
// proof-of-unique-human.
func (t *Trie) MerkleProof(k [32]byte) (leafCount uint64, steps []ProofStep, err error) {
	t.rehash(t.root)
	var path []*node
	n := t.root
	for n != nil && !n.isLeaf() {
		path = append(path, n)
		if bitAt(k, n.bit) == 0 {
			n = n.left
		} else {
			n = n.right
		}
	}
	if n == nil || n.key != k {
		return 0, nil, errors.New("trie: key missing")
	}
	for i := len(path) - 1; i >= 0; i-- {
		br := path[i]
		sib := br.right
		if bitAt(k, br.bit) == 1 {
			sib = br.left
		}
		steps = append(steps, ProofStep{
			Bit: br.bit, SibHash: sib.hash, SibCount: sib.count,
			SibHits: sib.hits, SibSum: sib.sum,
		})
	}
	return n.count, steps, nil
}

// VerifyProof recomputes the root from an entry, its frozen leaf
// count, and the sibling chain.
func (t *Trie) VerifyProof(root [32]byte, k [32]byte, e Entry, leafCount uint64, steps []ProofStep) bool {
	h := e.LeafHash(k)
	count, hits := leafCount, e.HitsVal()
	sum := t.sumOf(e)
	for _, s := range steps {
		var l, r [32]byte
		if bitAt(k, s.Bit) == 0 {
			l, r = h, s.SibHash
		} else {
			l, r = s.SibHash, h
		}
		count += s.SibCount
		if s.SibHits > hits {
			hits = s.SibHits
		}
		if t.withSum && s.SibSum != nil {
			sum.Add(sum, s.SibSum)
		}
		h = t.branchHash(count, hits, sum, l, r)
	}
	return h == root
}
bitpeople/population module: periodic anonymous verification3020 lines
bpc_test.go1026
package bitpeople

import (
	"bytes"
	"crypto/ed25519"
	"math/big"
	mrand "math/rand"
	"sort"
	"testing"

	"peoplecoin/core"
)

// fixture builds a live state mid-Event with N committed nyms.
func fixture(t *testing.T, n int) (*State, []ed25519.PrivateKey, [][32]byte) {
	t.Helper()
	s := &State{
		NormTime: core.NormTimeFor(14 * core.NormPeriod),
		Time:     14 * core.NormPeriod, Genesis: 14 * core.NormPeriod,
		Nym: NewNymTrie(), NextNym: NewNymTrie(),
		Invite: NewInviteTrie(), NextInvite: NewInviteTrie(),
		Election: core.NewElectionTrie(), UTXO: core.NewUTXOSet(),
		Seed:   core.H([]byte("fixture seed")),
		XorAcc: map[[32]byte][32]byte{},
	}
	privs := make([]ed25519.PrivateKey, n)
	pres := make([][32]byte, n)
	for i := 0; i < n; i++ {
		priv, pub := core.GenKey()
		privs[i] = priv
		pres[i] = core.H([]byte{byte(i)}, []byte("pre"))
		if err := s.Nym.Insert([32]byte(pub), &NymEntry{Commit: core.H(pres[i][:])}); err != nil {
			t.Fatal(err)
		}
	}
	return s, privs, pres
}

func eventT(s *State) uint64 { return s.Genesis + 2*core.SlotSeconds }
func rngT(s *State) uint64   { return s.Genesis + EventSeconds + core.SlotSeconds }

func TestDecayConstant(t *testing.T) {
	y := core.DecayPow(core.SecondsPerYear)
	target := new(big.Int).Div(new(big.Int).Mul(core.Scale, big.NewInt(8)), big.NewInt(10))
	if y.Cmp(target) >= 0 {
		t.Fatalf("decay^YEAR = %s, want < 0.8×Scale", y)
	}
	lo := new(big.Int).Div(new(big.Int).Mul(core.Scale, big.NewInt(79)), big.NewInt(100))
	if y.Cmp(lo) < 0 {
		t.Fatalf("decay^YEAR = %s, way below 0.79×Scale", y)
	}
}

// TestMixRules: the generic Mix constraints.
func TestMixRules(t *testing.T) {
	s, privs, _ := fixture(t, 2)
	T := eventT(s)
	// Two next_nym entries via Verify pair completion is overkill —
	// insert directly.
	k1, k2 := [32]byte(core.Pub(privs[0])), [32]byte(core.Pub(privs[1]))
	s.NextNym.Insert(k1, freshNym(0))
	s.NextNym.Insert(k2, freshNym(0))

	mk := func(outMC1, outMC2 uint32) (*core.Mix, []ed25519.PrivateKey) {
		o1p, o1 := core.GenKey()
		o2p, o2 := core.GenKey()
		m := &core.Mix{TrieID: TrieNextNym,
			Inputs: [][32]byte{k1, k2},
			Outputs: []core.MixOutput{
				{Key: [32]byte(o1), MixCount: outMC1},
				{Key: [32]byte(o2), MixCount: outMC2},
			}}
		id := m.TxID()
		m.Sigs = []core.Sig{core.Sign(privs[0], id[:]), core.Sign(privs[1], id[:])}
		return m, []ed25519.PrivateKey{o1p, o2p}
	}

	// Budget too small: 0+0 inputs need ≥ 2 out.
	bad, _ := mk(1, 0)
	if err := s.applyMix(bad, T); err == nil {
		t.Fatal("under-budget mix accepted")
	}
	// Single input.
	single := &core.Mix{TrieID: TrieNextNym, Inputs: [][32]byte{k1},
		Outputs: []core.MixOutput{{Key: [32]byte(core.Pub(privs[0])), MixCount: 5}}}
	id := single.TxID()
	single.Sigs = []core.Sig{core.Sign(privs[0], id[:])}
	if err := s.applyMix(single, T); err == nil {
		t.Fatal("1-input mix accepted (minimum is 2)")
	}
	// Legal.
	good, outs := mk(1, 1)
	if err := s.applyMix(good, T); err != nil {
		t.Fatalf("legal mix rejected: %v", err)
	}
	// Output key not fresh (reuse a live key).
	dup := &core.Mix{TrieID: TrieNextNym,
		Inputs: [][32]byte{[32]byte(core.Pub(outs[0])), [32]byte(core.Pub(outs[1]))},
		Outputs: []core.MixOutput{
			{Key: [32]byte(core.Pub(outs[0])), MixCount: 2},
			{Key: [32]byte(core.Pub(privs[0])), MixCount: 2},
		}}
	id = dup.TxID()
	dup.Sigs = []core.Sig{core.Sign(outs[0], id[:]), core.Sign(outs[1], id[:])}
	if err := s.applyMix(dup, T); err == nil {
		t.Fatal("output reusing an input key accepted")
	}
	// Committed inputs are locked.
	ce, _ := s.NextNym.Get([32]byte(core.Pub(outs[0]))).(*NymEntry)
	ce.Commit = core.H([]byte("x"))
	s.NextNym.Touch([32]byte(core.Pub(outs[0])))
	locked, _ := mk(2, 2)
	locked.Inputs = [][32]byte{[32]byte(core.Pub(outs[0])), [32]byte(core.Pub(outs[1]))}
	id = locked.TxID()
	locked.Sigs = []core.Sig{core.Sign(outs[0], id[:]), core.Sign(outs[1], id[:])}
	if err := s.applyMix(locked, T); err == nil {
		t.Fatal("committed input accepted into a mix")
	}
}

// TestIncentiveChain: each step requires the previous.
func TestIncentiveChain(t *testing.T) {
	s, privs, _ := fixture(t, 2)
	T := eventT(s)
	k1 := [32]byte(core.Pub(privs[0]))
	s.NextNym.Insert(k1, freshNym(0))

	// Commit(n) without mixing.
	c := mkCommitN(privs[0], core.H([]byte("c")))
	if err := s.applyCommitN(c, T); err == nil {
		t.Fatal("commit_n with mix_count 0 accepted")
	}
	// Mix, then commit.
	e, _ := s.NextNym.Get(k1).(*NymEntry)
	e.MixCount = 1 // stand-in for a mix
	if err := s.applyCommitN(c, T); err != nil {
		t.Fatalf("commit_n rejected: %v", err)
	}
	// Election entry auto-created, mix_count 0 → Commit(e) blocked.
	ce := mkCommitE(privs[0], core.H([]byte("v")))
	if err := s.applyCommitE(ce, T); err == nil {
		t.Fatal("commit_e with mix_count 0 accepted")
	}
	ee, _ := s.Election.Get(k1).(*core.ElectionEntry)
	if ee == nil {
		t.Fatal("commit_n did not create the election entry")
	}
	// Vote before Commit(e).
	v := mkVote(privs[0])
	if err := s.applyVote(v, T); err == nil {
		t.Fatal("vote on uncommitted election entry accepted")
	}
	ee.MixCount = 1
	if err := s.applyCommitE(ce, T); err != nil {
		t.Fatalf("commit_e rejected: %v", err)
	}
	if err := s.applyVote(v, T); err != nil {
		t.Fatalf("vote rejected: %v", err)
	}
	if s.UTXO.Len() != 1 {
		t.Fatal("vote did not mint UBI")
	}
	// Replay: same Vote → same txid → UTXO key collision.
	if err := s.applyVote(mkVote(privs[0]), T); err == nil {
		t.Fatal("vote replay accepted")
	}
	got := s.UTXO.Get(core.CoinKey(v.TxID(), 0))
	if got == nil || got.Amount.Cmp(UBIPerPeriod()) != 0 {
		t.Fatal("UBI amount wrong")
	}
}

// TestRevealTargeting: snapshot ranks, commit consumption, xor fold.
func TestRevealTargeting(t *testing.T) {
	s, _, pres := fixture(t, 2)
	T := rngT(s)
	k0, _ := s.Nym.Select(0)
	k1, _ := s.Nym.Select(1)
	rootBefore := s.Nym.Root()
	countBefore := s.Nym.Count()
	// Match fixture preimages to snapshot order by commit.
	var pre0, pre1 [32]byte
	for _, p := range pres {
		if core.H(p[:]) == s.Nym.Get(k0).(*NymEntry).Commit {
			pre0 = p
		}
		if core.H(p[:]) == s.Nym.Get(k1).(*NymEntry).Commit {
			pre1 = p
		}
	}

	// Reveal outside RNG.
	if err := s.applyReveal(&Reveal{Key: k0, Preimage: pre0}, eventT(s)); err == nil {
		t.Fatal("reveal accepted in event phase")
	}
	// Wrong preimage.
	if err := s.applyReveal(&Reveal{Key: k0, Preimage: core.H([]byte("no"))}, T); err == nil {
		t.Fatal("wrong preimage accepted")
	}
	// Valid: target = (0 + uint(pre)) mod 2.
	if err := s.applyReveal(&Reveal{Key: k0, Preimage: pre0}, T); err != nil {
		t.Fatalf("reveal rejected: %v", err)
	}
	tr := new(big.Int).SetBytes(pre0[:])
	tr.Mod(tr, big.NewInt(2))
	want, _ := s.Nym.Select(tr.Uint64())
	we := s.Nym.Get(want).(*NymEntry)
	if we.SeedHits != 1 {
		t.Fatal("hit did not land on the computed target")
	}
	if s.XorAcc[want] != pre0 {
		t.Fatal("xor accumulator did not fold the preimage")
	}
	// The reveal consumed the commit — that IS the replay
	// protection — while the frozen index is untouched: count and
	// ordering are the swap-time comm list, so the revealed entry
	// keeps its rank and remains a valid RNG target. N never
	// shrinks during the RNG phase.
	if s.Nym.Get(k0).(*NymEntry).Commit != ([32]byte{}) {
		t.Fatal("reveal did not consume the commit")
	}
	if err := s.applyReveal(&Reveal{Key: k0, Preimage: pre0}, T); err == nil {
		t.Fatal("double reveal accepted")
	}
	if s.Nym.Count() != countBefore {
		t.Fatal("consuming the commit altered the frozen count")
	}
	if r0, ok := s.Nym.Rank(k0); !ok || r0 != 0 {
		t.Fatal("revealed entry lost its rank — index not frozen")
	}
	if s.Nym.Root() == rootBefore {
		t.Fatal("consumption and seed_hits should change leaf hashes (root)")
	}
	// Second revealer, then the winner check.
	if err := s.applyReveal(&Reveal{Key: k1, Preimage: pre1}, T); err != nil {
		t.Fatalf("second reveal rejected: %v", err)
	}
	seed := computeSeed(s)
	if seed == ([32]byte{}) {
		t.Fatal("seed empty")
	}
	// Winner = most hits, lowest rank ties; recompute expectation.
	h0 := s.Nym.Get(k0).(*NymEntry).SeedHits
	h1 := s.Nym.Get(k1).(*NymEntry).SeedHits
	var expect [32]byte
	if h1 > h0 {
		expect = s.XorAcc[k1]
	} else {
		expect = s.XorAcc[k0]
	}
	if seed != expect {
		t.Fatal("computeSeed did not pick max-hits / lowest-rank winner")
	}
}

// TestJudgePromotion: court routing and the atomic promote.
func TestJudgePromotion(t *testing.T) {
	s, privs, _ := fixture(t, 2)
	T := eventT(s)
	_, invitee := core.GenKey()
	s.Invite.Insert([32]byte(invitee), &InviteEntry{Used: true})

	// A non-member of the court pair (fresh key) is rejected.
	stranger, _ := core.GenKey()
	if err := s.applyJudge(mkJudge(stranger, [32]byte(invitee)), T); err == nil {
		t.Fatal("stranger judge accepted")
	}
	if err := s.applyJudge(mkJudge(privs[0], [32]byte(invitee)), T); err != nil {
		t.Fatalf("first judge rejected: %v", err)
	}
	// Same side twice.
	if err := s.applyJudge(mkJudge(privs[0], [32]byte(invitee)), T); err == nil {
		t.Fatal("same court member judging twice accepted")
	}
	if s.NextNym.Get([32]byte(invitee)) != nil {
		t.Fatal("promotion fired on a single judge")
	}
	if err := s.applyJudge(mkJudge(privs[1], [32]byte(invitee)), T); err != nil {
		t.Fatalf("second judge rejected: %v", err)
	}
	if s.NextNym.Get([32]byte(invitee)) == nil {
		t.Fatal("no next_nym entry after promotion")
	}
	if s.Election.Get([32]byte(invitee)) == nil {
		t.Fatal("no election entry after promotion")
	}
	// v15: the entry stays, fully judged — no operation alters the
	// invite index during the period.
	inv, _ := s.Invite.Get([32]byte(invitee)).(*InviteEntry)
	if inv == nil || !inv.CourtJudgedA || !inv.CourtJudgedB {
		t.Fatal("promoted invite entry missing or flags not set")
	}
	if r, ok := s.inviteRank([32]byte(invitee)); !ok || r != 0 {
		t.Fatal("promoted invite lost its rank — index not frozen")
	}
	// A third judge bounces on the flags: no double promotion.
	if err := s.applyJudge(mkJudge(privs[0], [32]byte(invitee)), T); err == nil {
		t.Fatal("third judge accepted — double promotion possible")
	}
	if s.UTXO.Len() != 1 {
		t.Fatal("onboarding UBI not minted exactly once")
	}
	if s.Pop != 1 {
		t.Fatalf("population_count = %d, want 1", s.Pop)
	}
}

// TestForkChoice: fewest skips wins; worse branches rejected.
func TestForkChoice(t *testing.T) {
	v1, v2 := newVal("a"), newVal("b")
	_, pa := core.GenKey()
	_, pb := core.GenKey()
	c1 := mkOnion([]*valSvc{v1}, 12)
	c2 := mkOnion([]*valSvc{v2}, 12)
	mkChain := func() *Chain {
		ch, err := NewChain(GenesisConfig{
			T0: 14 * core.NormPeriod,
			Nyms: []GenesisNym{
				{Key: pa, Commit: core.H([]byte("ca"))},
				{Key: pb, Commit: core.H([]byte("cb"))},
			},
			Seed:     core.H([]byte("fork seed")),
			Schedule: [][32]byte{c1, c2},
		})
		if err != nil {
			t.Fatal(err)
		}
		return ch
	}
	// Owner of each slot alternates pseudo-randomly; find slots.
	main := mkChain()
	produceAt := func(c *Chain, slot uint64) *Block {
		T := c.State.Genesis + slot*core.SlotSeconds
		b, _, err := produceAny(c, []*valSvc{v1, v2}, T, nil)
		if err != nil {
			t.Fatal(err)
		}
		return b
	}
	produceAt(main, 4) // sparse: skips 1-3
	fork := mkChain()
	b1 := produceAt(fork, 1)
	b2 := produceAt(fork, 2)

	// Denser branch (fewer skips at same-or-later tip) adopts.
	ok, err := main.TryAdopt([]*Block{b1, b2})
	if err != nil || !ok {
		t.Fatalf("denser branch not adopted: %v", err)
	}
	if main.State.Seq != 2 || main.skips() != 0 {
		t.Fatalf("post-adopt tip seq=%d skips=%d", main.State.Seq, main.skips())
	}
	// A sparser branch is refused.
	sparse := mkChain()
	sb := produceAt(sparse, 9)
	ok, err = main.TryAdopt([]*Block{sb})
	if err != nil || ok {
		t.Fatal("sparser branch adopted")
	}
	// Tampered block rejected.
	tampered := *b2
	evil := core.H([]byte("evil"))
	tampered.Header.Nonce = evil[:]
	if _, err := main.TryAdopt([]*Block{b1, &tampered}); err == nil {
		t.Fatal("tampered branch verified")
	}
}

// TestFullDemo: the whole scenario replays and detects tampering.
func TestFullDemo(t *testing.T) {
	chain, err := RunDemo()
	if err != nil {
		t.Fatalf("demo: %v", err)
	}
	s := chain.genesisState.Clone()
	for i, b := range chain.Blocks {
		ns, err := VerifyBlock(s, b)
		if err != nil {
			t.Fatalf("replay block %d: %v", i, err)
		}
		s = ns
	}
	if s.LastHash != chain.State.LastHash {
		t.Fatal("replay tip differs")
	}
	// The tip sits just past the period-3 boundary: population_count
	// has reset, the copied-down snapshots carry the result.
	if chain.State.Nym.Count() != 4 {
		t.Fatalf("final N = %d, want 4", chain.State.Nym.Count())
	}
	if len(chain.State.Schedule) != 5 {
		t.Fatalf("final schedule = %d commits, want 5", len(chain.State.Schedule))
	}
	if chain.State.Pop != 0 {
		t.Fatalf("population_count = %d, want 0 right after activation", chain.State.Pop)
	}
	// Tamper with a mid-chain tx.
	s = chain.genesisState.Clone()
	for i, b := range chain.Blocks {
		if len(b.Txs) > 0 {
			if v, ok := b.Txs[0].(*Verify); ok {
				bad := *v
				bad.Key[0] ^= 1
				tb := *b
				tb.Txs = append([]core.Tx{&bad}, b.Txs[1:]...)
				if _, err := VerifyBlock(s, &tb); err == nil {
					t.Fatal("tampered tx verified")
				}
				break
			}
		}
		ns, err := VerifyBlock(s, b)
		if err != nil {
			t.Fatalf("replay block %d: %v", i, err)
		}
		s = ns
	}
}

// TestCommitlessDoesNotExist — the commit is the registration act:
// indexing is established by commits in next_nym and locked at the
// swap. An entry carried through the swap with commit == 0 was
// never registered — no rank, not in N, cannot verify, cannot be
// judged, cannot reveal. The protocol cannot reference it; it
// self-cleans at the next swap.
func TestCommitlessDoesNotExist(t *testing.T) {
	s, privs, _ := fixture(t, 2)
	T := eventT(s)
	lazyPriv, lazyPub := core.GenKey()
	if err := s.Nym.Insert([32]byte(lazyPub), &NymEntry{MixCount: 1}); err != nil {
		t.Fatal(err)
	}

	// Not indexed: N unchanged, no rank.
	if s.Nym.Count() != 2 {
		t.Fatalf("N = %d, want 2 — commitless entry entered the index", s.Nym.Count())
	}
	if _, ok := s.nymRank([32]byte(lazyPub)); ok {
		t.Fatal("commitless entry has a rank")
	}

	// Cannot verify (no rank → no pair).
	if err := s.applyVerify(mkVerify(lazyPriv), T); err == nil {
		t.Fatal("commitless entry verified")
	}
	// Cannot be judged in (no rank → no court routing).
	if err := s.applyJudgeNym(mkJudgeNym(privs[0], [32]byte(lazyPub)), T); err == nil {
		t.Fatal("commitless entry judged")
	}
	// Cannot reveal (nothing committed).
	if err := s.applyReveal(&Reveal{Key: [32]byte(lazyPub)}, rngT(s)); err == nil {
		t.Fatal("commitless entry revealed")
	}
	// The register is untouched by its presence: the real pair
	// completes exactly as with N = 2.
	if err := s.applyVerify(mkVerify(privs[0]), T); err != nil {
		t.Fatal(err)
	}
	if err := s.applyVerify(mkVerify(privs[1]), T); err != nil {
		t.Fatal(err)
	}
	if s.Pop != 2 {
		t.Fatalf("population_count = %d, want 2", s.Pop)
	}
}

// TestTrieCanonical: the trie is canonical for a key set — shape and
// root independent of insertion order, and remove restores exactly
// the never-inserted state.
func TestTrieCanonical(t *testing.T) {
	keys := make([][32]byte, 40)
	for i := range keys {
		keys[i] = core.H([]byte{byte(i)}, []byte("canon"))
	}
	build := func(order []int) *core.Trie {
		tr := NewNymTrie()
		for _, i := range order {
			e := &NymEntry{}
			if i%3 != 0 {
				e.Commit = core.H(keys[i][:]) // mixed committed/uncommitted
			}
			if err := tr.Insert(keys[i], e); err != nil {
				t.Fatal(err)
			}
		}
		return tr
	}
	fwd := make([]int, 40)
	rev := make([]int, 40)
	for i := range fwd {
		fwd[i], rev[i] = i, 39-i
	}
	a, b := build(fwd), build(rev)
	if a.Root() != b.Root() {
		t.Fatal("root depends on insertion order")
	}
	if a.Count() != b.Count() {
		t.Fatal("count depends on insertion order")
	}
	// Rank/Select agree and invert.
	for r := uint64(0); r < a.Count(); r++ {
		ka, _ := a.Select(r)
		kb, _ := b.Select(r)
		if ka != kb {
			t.Fatal("select depends on insertion order")
		}
		if got, ok := a.Rank(ka); !ok || got != r {
			t.Fatal("rank does not invert select")
		}
	}
	// Insert + remove ≡ never inserted.
	extra := core.H([]byte("extra"))
	before := a.Root()
	if err := a.Insert(extra, &NymEntry{Commit: core.H([]byte("x"))}); err != nil {
		t.Fatal(err)
	}
	if a.Root() == before {
		t.Fatal("insert did not change the root")
	}
	if _, err := a.Remove(extra); err != nil {
		t.Fatal(err)
	}
	if a.Root() != before {
		t.Fatal("remove did not restore the canonical root")
	}
}

// TestMerkleProof: proof-of-unique-human — a counted, verified nym
// entry proven against the header's nym_trie root, surviving reveal
// (frozen leaf count), failing on tampering.
func TestMerkleProof(t *testing.T) {
	s, privs, pres := fixture(t, 5)
	T := eventT(s)
	// Verify a full pair so one entry is verified.
	var target [32]byte
	for r := uint64(0); r < 5; r++ {
		k, _ := s.Nym.Select(r)
		pr, ok := s.partnerRank(r)
		if !ok {
			continue
		}
		pk, _ := s.Nym.Select(pr)
		var kp, pp ed25519.PrivateKey
		for _, p := range privs {
			if [32]byte(core.Pub(p)) == k {
				kp = p
			}
			if [32]byte(core.Pub(p)) == pk {
				pp = p
			}
		}
		if err := s.applyVerify(mkVerify(kp), T); err != nil {
			t.Fatal(err)
		}
		if err := s.applyVerify(mkVerify(pp), T); err != nil {
			t.Fatal(err)
		}
		target = k
		break
	}
	root := s.Nym.Root()
	lc, steps, err := s.Nym.MerkleProof(target)
	if err != nil {
		t.Fatal(err)
	}
	entry := s.Nym.Get(target).(*NymEntry).CloneEntry().(*NymEntry)
	if !s.Nym.VerifyProof(root, target, entry, lc, steps) {
		t.Fatal("valid proof rejected")
	}
	// Tampered entry (claiming verified where root says otherwise).
	bad := entry.CloneEntry().(*NymEntry)
	bad.SeedHits = 99
	if s.Nym.VerifyProof(root, target, bad, lc, steps) {
		t.Fatal("tampered entry proven")
	}
	// Wrong root.
	if s.Nym.VerifyProof(core.H([]byte("other")), target, entry, lc, steps) {
		t.Fatal("proof verified against a foreign root")
	}
	// After the target reveals, the commit is consumed but the leaf
	// count is frozen: a fresh proof against the new root carries
	// leafCount 1 for an entry whose Counted() is false.
	var pre [32]byte
	for _, p := range pres {
		if core.H(p[:]) == entry.Commit {
			pre = p
		}
	}
	if err := s.applyReveal(&Reveal{Key: target, Preimage: pre}, rngT(s)); err != nil {
		t.Fatalf("reveal: %v", err)
	}
	root2 := s.Nym.Root()
	lc2, steps2, err := s.Nym.MerkleProof(target)
	if err != nil {
		t.Fatal(err)
	}
	e2 := s.Nym.Get(target).(*NymEntry)
	if e2.Counted() || lc2 != 1 {
		t.Fatalf("expected consumed commit with frozen leaf count 1, got committed=%v count=%d",
			e2.Counted(), lc2)
	}
	if !s.Nym.VerifyProof(root2, target, e2.CloneEntry(), lc2, steps2) {
		t.Fatal("post-reveal proof rejected")
	}
}

// TestNormAdvance — the v19 rule: when norm_time advances by Δ,
// sum ×= decay^Δ. Applied per contribution with exact branch sums;
// real values (amount × decay^(T−time)) are untouched by the
// bookkeeping rescale.
func TestNormAdvance(t *testing.T) {
	old := uint64(14) * core.NormPeriod
	newN := old + core.NormPeriod // Δ = 4 years
	u := core.NewUTXOSet()
	var keys [][32]byte
	var oldNorms []*big.Int
	for i := 0; i < 7; i++ {
		_, p := core.GenKey()
		amt := new(big.Int).Mul(big.NewInt(int64(i+1)), big.NewInt(1_000_000_000))
		tm := old + uint64(i)*PeriodSeconds
		k := core.CoinKey(core.H([]byte{byte(i)}, []byte("norm")), 0)
		nv := core.Normalize(amt, tm, old)
		if err := u.Insert(k, &core.CoinEntry{Owner: p, Amount: amt, Norm: nv, Time: tm}); err != nil {
			t.Fatal(err)
		}
		keys = append(keys, k)
		oldNorms = append(oldNorms, new(big.Int).Set(nv))
	}
	T := newN + 1000*core.SlotSeconds
	supplyBefore := core.ValueAt(u.Sum(), T, old)
	sumBefore := u.Sum()
	valBefore := u.Get(keys[3]).Value(T)

	det := u.Clone()
	u.Renormalize(old, newN)
	det.Renormalize(old, newN)
	if u.Root() != det.Root() {
		t.Fatal("renormalization is not deterministic")
	}

	// Per contribution: norm ×= decay^Δ, floored.
	factor := core.DecayPow(core.NormPeriod)
	total := new(big.Int)
	for i, k := range keys {
		want := core.MulScale(oldNorms[i], factor)
		got := u.Get(k).Norm
		if got.Cmp(want) != 0 {
			t.Fatalf("leaf %d: norm %s, want %s", i, got, want)
		}
		total.Add(total, got)
	}
	// Branch sums exact: root == Σ leaves.
	if u.Sum().Cmp(total) != 0 {
		t.Fatal("root sum is not the exact sum of rescaled contributions")
	}
	// Root within one unit per leaf of the ideal rescale.
	ideal := core.MulScale(sumBefore, factor)
	diff := new(big.Int).Sub(ideal, u.Sum())
	if diff.Sign() < 0 || diff.Cmp(big.NewInt(int64(len(keys)))) > 0 {
		t.Fatalf("sum ×= decay^Δ off by %s (allow 0..%d)", diff, len(keys))
	}
	// Real values untouched; supply-at-T continuous within rounding.
	if u.Get(keys[3]).Value(T).Cmp(valBefore) != 0 {
		t.Fatal("renormalization changed a real value")
	}
	supplyAfter := core.ValueAt(u.Sum(), T, newN)
	d2 := new(big.Int).Sub(supplyBefore, supplyAfter)
	if d2.Sign() < 0 || d2.Cmp(big.NewInt(int64(len(keys)))) > 0 {
		t.Fatalf("supply jumped by %s across the advance", d2)
	}
}

// TestFixedEpochAcrossBoundary — the node never renormalizes in the
// transition function: a 4×YEAR boundary passes mid-period, the era
// epoch stays fixed, coins minted after the boundary normalize
// against it (norms grow — that is the uint128 headroom being
// consumed), values stay coherent and the chain replays. The
// planned-event tool (Renormalize) is tested separately in
// TestNormAdvance.
func TestFixedEpochAcrossBoundary(t *testing.T) {
	B := uint64(15) * core.NormPeriod
	T0 := B - PeriodSeconds/2 // Event = [T0, B): boundary at RNG start
	era := core.NormTimeFor(T0)
	v := newVal("v")
	a := &person{name: "a"}
	b := &person{name: "b"}
	for _, p := range []*person{a, b} {
		p.fresh()
		p.rollover()
		p.fresh()
	}
	chain, err := NewChain(GenesisConfig{
		T0: T0,
		Nyms: []GenesisNym{
			{Key: a.pub, Commit: core.H(a.pre[:])},
			{Key: b.pub, Commit: core.H(b.pre[:])},
		},
		Seed:     core.H([]byte("fixed epoch")),
		Schedule: [][32]byte{mkOnion([]*valSvc{v}, 12)},
	})
	if err != nil {
		t.Fatal(err)
	}
	at := func(slot uint64, txs []core.Tx) {
		t.Helper()
		if _, err := chain.Produce(txs, T0+slot*core.SlotSeconds, v.priv, v.nonces); err != nil {
			t.Fatal(err)
		}
	}
	at(1, []core.Tx{mkVerify(a.priv), mkVerify(b.priv)})
	mixN, outs := mkMix(TrieNextNym, chain.State, []ed25519.PrivateKey{a.priv, b.priv})
	at(2, []core.Tx{mixN})
	at(3, []core.Tx{mkCommitN(outs[0], core.H([]byte("c0"))), mkCommitN(outs[1], core.H([]byte("c1")))})
	mixE, eouts := mkMix(TrieElection, chain.State, outs)
	at(4, []core.Tx{mixE})
	at(5, []core.Tx{mkCommitE(eouts[0], mkOnion([]*valSvc{v}, 12)), mkCommitE(eouts[1], mkOnion([]*valSvc{v}, 12))})
	at(6, []core.Tx{mkVote(eouts[0]), mkVote(eouts[1])})

	// Cross the 4×YEAR boundary (still period 0, RNG phase): the
	// epoch must NOT move, and the utxo trie must be untouched.
	rootPre := chain.State.UTXO.Root()
	past := (B-T0)/core.SlotSeconds + 1
	at(past, nil)
	if chain.State.NormTime != era {
		t.Fatalf("norm_time moved to %d; the era epoch is fixed at %d", chain.State.NormTime, era)
	}
	if chain.State.UTXO.Root() != rootPre {
		t.Fatal("crossing the boundary rewrote the utxo trie")
	}

	// A coin created after the boundary normalizes against the old
	// era epoch: time − norm_time > 4 years, norm > amount (headroom
	// consumption), value at T exact.
	voteID := mkVote(eouts[0]).TxID()
	spendT := T0 + (past+2)*core.SlotSeconds
	src := chain.State.UTXO.Get(core.CoinKey(voteID, 0))
	half := new(big.Int).Rsh(src.Spendable(spendT), 1)
	_, dst := core.GenKey()
	tr := &core.Transfer{
		Inputs:  []core.Outpoint{{Tx: voteID, Index: 0}},
		Outputs: []core.Output{{Amount: half, Owner: dst}},
	}
	trID := tr.TxID()
	tr.Sigs = []core.Sig{core.Sign(eouts[0], trID[:])}
	at(past+2, []core.Tx{tr})
	ne := chain.State.UTXO.Get(core.CoinKey(trID, 0))
	if ne.Time <= era+core.NormPeriod {
		t.Fatal("test setup: new coin not past the boundary")
	}
	if ne.Norm.Cmp(ne.Amount) <= 0 {
		t.Fatal("post-boundary norm should exceed amount (growing norms)")
	}
	if ne.Value(ne.Time).Cmp(ne.Amount) != 0 {
		t.Fatal("value at creation time must equal amount")
	}

	// Full replay: fixed-epoch behavior is consensus-deterministic.
	s := chain.genesisState.Clone()
	for i, blk := range chain.Blocks {
		ns, err := VerifyBlock(s, blk)
		if err != nil {
			t.Fatalf("replay block %d: %v", i, err)
		}
		s = ns
	}
	if s.LastHash != chain.State.LastHash {
		t.Fatal("replay tip differs")
	}
}

// TestMaxHitsAggregate — the aggregate walk agrees with a linear
// scan (max hits, lowest key on ties) across random patterns, and
// hits landing only on counted targets means the winner is counted.
func TestMaxHitsAggregate(t *testing.T) {
	tr := NewNymTrie()
	keys := make([][32]byte, 33)
	for i := range keys {
		keys[i] = core.H([]byte{byte(i)}, []byte("hits"))
		e := &NymEntry{Commit: core.H(keys[i][:])}
		if err := tr.Insert(keys[i], e); err != nil {
			t.Fatal(err)
		}
	}
	scan := func() ([32]byte, uint32, bool) {
		best, bh, found := [32]byte{}, uint32(0), false
		tr.ForEach(func(k [32]byte, e core.Entry) {
			if h := e.HitsVal(); h > bh {
				best, bh, found = k, h, true
			}
		})
		return best, bh, found
	}
	// All zero: no winner.
	if _, _, ok := tr.SelectMaxHits(); ok {
		t.Fatal("winner with zero hits")
	}
	// Random patterns with deliberate ties.
	pat := []struct {
		idx  int
		hits uint32
	}{{5, 3}, {12, 7}, {30, 7}, {2, 1}, {19, 7}, {0, 2}}
	for _, p := range pat {
		tr.Get(keys[p.idx]).(*NymEntry).SeedHits = p.hits
		tr.Refresh(keys[p.idx])
		wk, wh, wok := tr.SelectMaxHits()
		sk, sh, sok := scan()
		if wok != sok || wh != sh || wk != sk {
			t.Fatalf("aggregate walk disagrees with scan after setting %d=%d", p.idx, p.hits)
		}
	}
	// The tie (12, 30, 19 all at 7) must resolve to the lowest KEY.
	wk, _, _ := tr.SelectMaxHits()
	low := keys[12]
	for _, i := range []int{19, 30} {
		if string(keys[i][:]) < string(low[:]) {
			low = keys[i]
		}
	}
	if wk != low {
		t.Fatal("tie not broken by lowest key (lowest rank)")
	}
}

// TestTrieDifferential — randomized differential test of the radix
// trie against a plain reference model. Index membership is modeled
// exactly per the frozen-index semantics: set at Insert and Touch,
// UNTOUCHED by in-place mutation + Refresh (including commit
// consumption). Checks count, rank/select, key order, root
// determinism, Merkle proofs and the max-hits walk along the way.
func TestTrieDifferential(t *testing.T) {
	rng := mrand.New(mrand.NewSource(7))
	for round := 0; round < 3; round++ {
		tr := NewNymTrie()
		entries := map[[32]byte]*NymEntry{}
		indexed := map[[32]byte]bool{}
		pool := make([][32]byte, 300)
		for i := range pool {
			pool[i] = core.H([]byte{byte(round), byte(i), byte(i >> 8)}, []byte("diff"))
		}
		sortedIndexed := func() [][32]byte {
			var out [][32]byte
			for k, ok := range indexed {
				if ok {
					out = append(out, k)
				}
			}
			sort.Slice(out, func(i, j int) bool {
				return bytes.Compare(out[i][:], out[j][:]) < 0
			})
			return out
		}
		check := func() {
			t.Helper()
			want := sortedIndexed()
			if tr.Count() != uint64(len(want)) {
				t.Fatalf("count %d, model %d", tr.Count(), len(want))
			}
			got := tr.CommittedKeys()
			for i := range want {
				if got[i] != want[i] {
					t.Fatal("committed keys diverge from model")
				}
				if r, ok := tr.Rank(want[i]); !ok || r != uint64(i) {
					t.Fatal("rank diverges from model")
				}
				if k, ok := tr.Select(uint64(i)); !ok || k != want[i] {
					t.Fatal("select diverges from model")
				}
			}
			if tr.Clone().Root() != tr.Root() {
				t.Fatal("root not deterministic under clone")
			}
			// A random present key must prove against the root.
			if len(entries) > 0 {
				var some [32]byte
				for k := range entries {
					some = k
					break
				}
				lc, steps, err := tr.MerkleProof(some)
				if err != nil {
					t.Fatal(err)
				}
				if !tr.VerifyProof(tr.Root(), some, entries[some].CloneEntry(), lc, steps) {
					t.Fatal("proof of present key rejected")
				}
			}
			// Max-hits walk vs scan over live entries.
			var bk [32]byte
			bh := uint32(0)
			for _, k := range append([][32]byte{}, poolSorted(pool, entries)...) {
				if e := entries[k]; e != nil && e.SeedHits > bh {
					bk, bh = k, e.SeedHits
				}
			}
			wk, wh, wok := tr.SelectMaxHits()
			if (bh > 0) != wok || (wok && (wh != bh || wk != bk)) {
				t.Fatal("max-hits walk diverges from scan")
			}
		}
		for op := 0; op < 2500; op++ {
			k := pool[rng.Intn(len(pool))]
			e := entries[k]
			switch rng.Intn(6) {
			case 0, 1: // insert
				ne := &NymEntry{}
				if rng.Intn(2) == 0 {
					ne.Commit = core.H(k[:], []byte{byte(op)})
				}
				err := tr.Insert(k, ne)
				if e != nil {
					if err == nil {
						t.Fatal("duplicate insert accepted")
					}
				} else {
					if err != nil {
						t.Fatal(err)
					}
					entries[k] = ne
					indexed[k] = ne.Counted()
				}
			case 2: // remove
				_, err := tr.Remove(k)
				if e == nil {
					if err == nil {
						t.Fatal("remove of absent key accepted")
					}
				} else {
					if err != nil {
						t.Fatal(err)
					}
					delete(entries, k)
					delete(indexed, k)
				}
			case 3: // commit + touch (index established)
				if e != nil && e.Commit == ([32]byte{}) {
					e.Commit = core.H(k[:], []byte{0xCC, byte(op)})
					tr.Touch(k)
					indexed[k] = true
				}
			case 4: // in-place mutation + refresh: index frozen
				if e != nil {
					e.SeedHits += uint32(rng.Intn(3))
					e.Verified = !e.Verified
					tr.Refresh(k)
				}
			case 5: // consume commit + refresh: index STILL frozen
				if e != nil && e.Commit != ([32]byte{}) {
					e.Commit = [32]byte{}
					tr.Refresh(k)
					// model: indexed[k] unchanged — the whole point
				}
			}
			if op%250 == 0 {
				check()
			}
		}
		check()
		// Tear down: remove everything → canonical empty root.
		for k := range entries {
			if _, err := tr.Remove(k); err != nil {
				t.Fatal(err)
			}
		}
		if tr.Root() != core.Zero32 || tr.Count() != 0 || tr.Len() != 0 {
			t.Fatal("teardown did not restore the empty trie")
		}
	}
}

// poolSorted returns the pool keys present in entries, key order —
// scan order for the max-hits cross-check (lowest key on ties).
func poolSorted(pool [][32]byte, entries map[[32]byte]*NymEntry) [][32]byte {
	var out [][32]byte
	for _, k := range pool {
		if entries[k] != nil {
			out = append(out, k)
		}
	}
	sort.Slice(out, func(i, j int) bool { return bytes.Compare(out[i][:], out[j][:]) < 0 })
	return out
}

// TestGenesisValidation — config errors are rejected at genesis, not
// discovered as dead state later.
func TestGenesisValidation(t *testing.T) {
	_, pa := core.GenKey()
	_, pb := core.GenKey()
	ok := GenesisConfig{
		T0: 14 * core.NormPeriod,
		Nyms: []GenesisNym{
			{Key: pa, Commit: core.H([]byte("a"))},
			{Key: pb, Commit: core.H([]byte("b"))},
		},
		Seed:     core.H([]byte("g")),
		Schedule: [][32]byte{core.H([]byte("s"))},
	}
	if _, err := NewChain(ok); err != nil {
		t.Fatalf("valid genesis rejected: %v", err)
	}
	bad := ok
	bad.Nyms = []GenesisNym{{Key: pa, Commit: [32]byte{}}}
	if _, err := NewChain(bad); err == nil {
		t.Fatal("zero-commit genesis nym accepted (unindexed person)")
	}
	bad = ok
	bad.Nyms = []GenesisNym{{Key: core.PubKey{}, Commit: core.H([]byte("a"))}}
	if _, err := NewChain(bad); err == nil {
		t.Fatal("zero-key genesis nym accepted")
	}
	bad = ok
	bad.Schedule = [][32]byte{{}}
	if _, err := NewChain(bad); err == nil {
		t.Fatal("zero schedule commit accepted (permanent skips)")
	}
	bad = ok
	bad.Schedule = nil
	if _, err := NewChain(bad); err == nil {
		t.Fatal("empty schedule accepted (chain halted at birth)")
	}
	bad = ok
	bad.T0 = ok.T0 + 1
	if _, err := NewChain(bad); err == nil {
		t.Fatal("off-grid genesis time accepted")
	}
}
consensus.go540
package bitpeople

// consensus.go — phases, the period boundary, and block production.
//
//	Event (14 d) → RNG (7 d) → Pause (7 d), derived from time.
//
// At each boundary, everything derivable is computed once and copied
// down — never materialized as extra tries:
//
//	seed      := bitpeople RNG result of the closing period
//	schedule  := the election trie's committed values in key order —
//	             hash(seed ‖ seq) mod count indexes straight into it
//	nym snap  := the new nym trie's committed keys in key order —
//	             ranks for virtual pairs, courts and RNG targeting
//	invite snap := the used invites in key order — court assignment
//
// then next_nym → nym, next_invite → invite (root swaps), the
// election trie resets to empty, and fresh next tries open. The live
// tries only track consumption during the period; all rank math
// anchors to the snapshots.

import (
	"crypto/ed25519"
	"errors"
	"fmt"
	"math/big"

	"peoplecoin/core"
)

const (
	// PeriodSeconds is the bitpeople period: 28 days.
	PeriodSeconds = uint64(28 * 24 * 3600)
	EventSeconds  = uint64(14 * 24 * 3600)
	RNGSeconds    = uint64(7 * 24 * 3600) // then Pause fills the rest
)

// UBIPerPeriod: TOKEN × (1 − decay^period_duration) — minted on Vote.
func UBIPerPeriod() *big.Int {
	p := core.DecayPow(PeriodSeconds)
	return new(big.Int).Quo(new(big.Int).Mul(core.TOKEN,
		new(big.Int).Sub(core.Scale, p)), core.Scale)
}

func offsetOf(s *State, T uint64) uint64 { return (T - s.Genesis) % PeriodSeconds }

func inEvent(s *State, T uint64) bool { return offsetOf(s, T) < EventSeconds }
func inRNG(s *State, T uint64) bool {
	o := offsetOf(s, T)
	return o >= EventSeconds && o < EventSeconds+RNGSeconds
}
func phaseName(s *State, T uint64) string {
	switch {
	case inEvent(s, T):
		return "event"
	case inRNG(s, T):
		return "rng"
	default:
		return "pause"
	}
}

func (s *State) periodStart(T uint64) uint64 { return T - offsetOf(s, T) }

// State — six tries plus the copied-down derived data.
type State struct {
	NormTime uint64 // the era's norm epoch — fixed for the deployment
	Time     uint64
	Seq      uint64
	Genesis  uint64
	LastHash [32]byte

	Nym        *core.Trie
	NextNym    *core.Trie
	Invite     *core.Trie
	NextInvite *core.Trie
	Election   *core.Trie
	UTXO       *core.UTXOSet

	// Copied down at the boundary. Only the election schedule needs
	// copying — that trie resets. Every other index is the live
	// trie: established in the mixer by the registration act
	// (commit / use), locked at the swap, and untouched by every
	// period operation — Verify/JudgeNym/Judge set flags, Reveal
	// consumes the commit payload, nothing inserts, removes or
	// re-indexes (v15).
	Seed     [32]byte
	Schedule [][32]byte // committed election values, key order

	// Derived, maintained incrementally during the period —
	// recomputable from the chain, never part of a root.
	Pop           uint64 // population_count
	InvitesIssued uint64
	XorAcc        map[[32]byte][32]byte // reveal XOR per target key
}

func (s *State) Clone() *State {
	c := *s
	c.Nym = s.Nym.Clone()
	c.NextNym = s.NextNym.Clone()
	c.Invite = s.Invite.Clone()
	c.NextInvite = s.NextInvite.Clone()
	c.Election = s.Election.Clone()
	c.UTXO = s.UTXO.Clone()
	c.Schedule = append([][32]byte(nil), s.Schedule...)
	c.XorAcc = make(map[[32]byte][32]byte, len(s.XorAcc))
	for k, v := range s.XorAcc {
		c.XorAcc[k] = v
	}
	return &c
}

func (s *State) seqAt(T uint64) uint64 { return (T - s.Genesis) / core.SlotSeconds }

// SupplyAt: sum × decay^(T − norm_time).
func (s *State) SupplyAt(T uint64) *big.Int {
	return core.ValueAt(s.UTXO.Sum(), T, s.NormTime)
}

// ====================================================== rank helpers

func (s *State) nymRank(k [32]byte) (uint64, bool)    { return s.Nym.Rank(k) }
func (s *State) inviteRank(k [32]byte) (uint64, bool) { return s.Invite.Rank(k) }

func (s *State) nymN() uint64 { return s.Nym.Count() }

func (s *State) posOfRank(r uint64) uint64 {
	return FeistelPos(r, s.Seed, s.nymN())
}

// partnerRank: the rank at the adjacent virtual position, if any.
func (s *State) partnerRank(r uint64) (uint64, bool) {
	N := s.nymN()
	pp := s.posOfRank(r) ^ 1
	if pp >= N {
		return 0, false // odd N: the last position is lone
	}
	return FeistelPosInv(pp, s.Seed, N), true
}

// courtSide checks that member sits in the court pair assigned to a
// target rank (target mod pair_count), returning the member's side:
// 0 = a (even virtual position), 1 = b.
func (s *State) courtSide(member [32]byte, targetRank uint64) (int, error) {
	pc := s.nymN() / 2
	if pc == 0 {
		return 0, errors.New("no full pairs to serve as court")
	}
	c := targetRank % pc
	mr, ok := s.nymRank(member)
	if !ok {
		return 0, errors.New("court member not in the boundary snapshot")
	}
	mp := s.posOfRank(mr)
	if mp/2 != c {
		return 0, errors.New("signer is not a member of the assigned court pair")
	}
	return int(mp % 2), nil
}

// ==================================================== period boundary

// computeSeed: the counted... the committed nym with the most
// seed_hits wins (lowest rank breaks ties); the seed is the XOR of
// the reveals that targeted it. Deterministic fallback if none.
func computeSeed(s *State) [32]byte {
	winner, _, ok := s.Nym.SelectMaxHits()
	if !ok {
		return core.H([]byte{0xE0}, s.Seed[:]) // no reveals: deterministic fallback
	}
	return s.XorAcc[winner]
}

// activate runs one boundary: copy down, swap, reset.
func activate(s *State) {
	seed := computeSeed(s)

	// Schedule: the election trie's committed values in key order.
	sched := make([][32]byte, 0, s.Election.Count())
	for _, k := range s.Election.CommittedKeys() {
		e := s.Election.Get(k).(*core.ElectionEntry)
		sched = append(sched, e.Commit)
	}

	s.Nym = s.NextNym
	s.NextNym = NewNymTrie()
	s.Invite = s.NextInvite
	s.NextInvite = NewInviteTrie()
	s.Election = core.NewElectionTrie()

	s.Seed = seed
	s.Schedule = sched
	s.Pop = 0
	s.InvitesIssued = 0
	s.XorAcc = map[[32]byte][32]byte{}
}

func advancePeriods(s *State, from, to uint64) {
	for b := s.periodStart(from) + PeriodSeconds; b <= to; b += PeriodSeconds {
		activate(s)
	}
}

// ============================================================= blocks

type Header struct {
	Seq       uint64
	Time      uint64
	NymT      [32]byte
	NextNymT  [32]byte
	InviteT   [32]byte
	NextInvT  [32]byte
	ElectionT [32]byte
	UTXOT     [32]byte
	Prev      [32]byte
	Validator core.PubKey
	Nonce     []byte // onion reveal: 64 bytes, 32 at the deepest layer
	Sig       core.Sig
}

func (h *Header) encode(withSig bool) []byte {
	var w core.Buf
	w.U8(OpHeader)
	w.U64(h.Seq)
	w.U64(h.Time)
	w.Bytes(h.NymT[:])
	w.Bytes(h.NextNymT[:])
	w.Bytes(h.InviteT[:])
	w.Bytes(h.NextInvT[:])
	w.Bytes(h.ElectionT[:])
	w.Bytes(h.UTXOT[:])
	w.Bytes(h.Prev[:])
	w.Bytes(h.Validator[:])
	w.U8(byte(len(h.Nonce)))
	w.Bytes(h.Nonce)
	if withSig {
		w.Bytes(h.Sig[:])
	}
	return w.B
}

func (h *Header) SigHash() [32]byte { return core.H(h.encode(false)) }
func (h *Header) Hash() [32]byte    { return core.H(h.encode(true)) }

type Block struct {
	Header Header
	Txs    []core.Tx
}

// selectedCommit: hash(seed || seq) mod count into the schedule.
// The whole sequence is public at the boundary; DoS protection comes
// from the onion, not from seed unpredictability. Returns the index
// because the reveal peels the schedule entry in place.
func selectedCommit(s *State, seq uint64) (int, [32]byte, error) {
	n := uint64(len(s.Schedule))
	if n == 0 {
		return 0, [32]byte{}, errors.New("empty schedule: chain halted")
	}
	r := core.H(s.Seed[:], core.U64be(seq))
	i := new(big.Int).SetBytes(r[:])
	i.Mod(i, new(big.Int).SetUint64(n))
	return int(i.Uint64()), s.Schedule[i.Uint64()], nil
}

func preProcess(prev *State, T uint64) (*State, int, [32]byte, error) {
	if T%core.SlotSeconds != 0 {
		return nil, 0, core.Zero32, errors.New("block time not on the slot grid")
	}
	if T <= prev.Time {
		return nil, 0, core.Zero32, errors.New("block time not after previous block")
	}
	ns := prev.Clone()
	advancePeriods(ns, prev.Time, T)
	// Note: norm_time does NOT advance here. Renormalization is an
	// inherently O(N) full-state rewrite (every contribution and
	// every coin hash changes) — it cannot fit a 60-second slot at
	// scale, and every verifier would have to pay it to accept the
	// block. It is a planned era event instead: the rescale is a
	// pure function of state, so everyone precomputes and verifies
	// the post-advance root at leisure and switches at an agreed
	// point — coordination, not consensus computation. The node
	// runs its whole era on one fixed epoch; uint128 gives 100+
	// years of headroom while norms grow. UTXOSet.Renormalize is
	// the tool for the event.
	idx, sel, err := selectedCommit(ns, prev.seqAt(T))
	if err != nil {
		return nil, 0, core.Zero32, err
	}
	return ns, idx, sel, nil
}

func finish(ns *State, txs []core.Tx, T uint64, validator core.PubKey) error {
	fees, err := ns.ApplyTxs(txs, T)
	if err != nil {
		return err
	}
	if fees.Sign() > 0 {
		feeID := core.H([]byte{0xFE}, core.U64be(ns.seqAt(T)))
		if err := ns.UTXO.Insert(core.CoinKey(feeID, 0), &core.CoinEntry{
			Owner: validator, Amount: fees,
			Norm: core.Normalize(fees, T, ns.NormTime), Time: T,
		}); err != nil {
			return fmt.Errorf("fee mint: %w", err)
		}
	}
	return nil
}

// BuildBlock produces a block if the validator holds the reveal
// material for the selected commit: the header carries (validator,
// nonce) with H(validator ‖ nonce) == commit, and the reveal peels
// the schedule entry to the onion's next layer — or consumes it
// when the deepest (32-byte) nonce is spent.
func BuildBlock(prev *State, txs []core.Tx, T uint64, valPriv ed25519.PrivateKey,
	nonces map[[32]byte][]byte) (*Block, *State, error) {
	ns, idx, sel, err := preProcess(prev, T)
	if err != nil {
		return nil, nil, err
	}
	nonce, ok := nonces[sel]
	if !ok {
		return nil, nil, errors.New("build: selected commit is not ours (or consumed)")
	}
	pub := core.Pub(valPriv)
	next, ok := core.VerifyPeel(sel, pub, nonce)
	if !ok {
		return nil, nil, errors.New("build: reveal material does not match the commit")
	}
	ns.Schedule[idx] = next // zero = onion spent: the slot skips from here
	if err := finish(ns, txs, T, pub); err != nil {
		return nil, nil, err
	}
	h := Header{
		Seq: prev.seqAt(T), Time: T,
		NymT: ns.Nym.Root(), NextNymT: ns.NextNym.Root(),
		InviteT: ns.Invite.Root(), NextInvT: ns.NextInvite.Root(),
		ElectionT: ns.Election.Root(), UTXOT: ns.UTXO.Root(),
		Prev: prev.LastHash, Validator: pub, Nonce: nonce,
	}
	sh := h.SigHash()
	h.Sig = core.Sign(valPriv, sh[:])
	ns.Seq, ns.Time, ns.LastHash = h.Seq, T, h.Hash()
	return &Block{Header: h, Txs: txs}, ns, nil
}

// VerifyBlock re-executes and checks the selection proof and all six
// roots.
func VerifyBlock(prev *State, b *Block) (*State, error) {
	h := &b.Header
	if h.Seq != prev.seqAt(h.Time) {
		return nil, errors.New("verify: seq is not slots-since-genesis")
	}
	if h.Prev != prev.LastHash {
		return nil, errors.New("verify: prev hash mismatch")
	}
	ns, idx, sel, err := preProcess(prev, h.Time)
	if err != nil {
		return nil, fmt.Errorf("verify: %w", err)
	}
	next, ok := core.VerifyPeel(sel, h.Validator, h.Nonce)
	if !ok {
		return nil, errors.New("verify: reveal does not peel the selected commit")
	}
	ns.Schedule[idx] = next
	if err := finish(ns, b.Txs, h.Time, h.Validator); err != nil {
		return nil, fmt.Errorf("verify: %w", err)
	}
	sh := h.SigHash()
	if !core.VerifySig(h.Validator, sh[:], h.Sig) {
		return nil, errors.New("verify: bad validator signature")
	}
	roots := [][2][32]byte{
		{ns.Nym.Root(), h.NymT}, {ns.NextNym.Root(), h.NextNymT},
		{ns.Invite.Root(), h.InviteT}, {ns.NextInvite.Root(), h.NextInvT},
		{ns.Election.Root(), h.ElectionT}, {ns.UTXO.Root(), h.UTXOT},
	}
	for i, p := range roots {
		if p[0] != p[1] {
			return nil, fmt.Errorf("verify: root %d mismatch", i)
		}
	}
	ns.Seq, ns.Time, ns.LastHash = h.Seq, h.Time, h.Hash()
	return ns, nil
}

// ============================================================== chain

type Chain struct {
	State  *State
	Blocks []*Block

	Final    *State
	finalIdx int

	genesisState *State
}

type GenesisNym struct {
	Key    core.PubKey
	Commit [32]byte
}

type GenesisConfig struct {
	T0       uint64
	Nyms     []GenesisNym
	Seed     [32]byte
	Schedule [][32]byte // initial validator commits: hash(validator || nonce)
}

func NewChain(cfg GenesisConfig) (*Chain, error) {
	if cfg.T0%core.SlotSeconds != 0 {
		return nil, errors.New("genesis: time not on the slot grid")
	}
	if len(cfg.Schedule) == 0 {
		return nil, errors.New("genesis: empty schedule")
	}
	for _, sc := range cfg.Schedule {
		if sc == ([32]byte{}) {
			return nil, errors.New("genesis: zero schedule commit is unprovable (permanent skips)")
		}
	}
	s := &State{
		NormTime: core.NormTimeFor(cfg.T0),
		Time:     cfg.T0, Genesis: cfg.T0,
		Nym: NewNymTrie(), NextNym: NewNymTrie(),
		Invite: NewInviteTrie(), NextInvite: NewInviteTrie(),
		Election: core.NewElectionTrie(), UTXO: core.NewUTXOSet(),
		Seed: cfg.Seed, Schedule: append([][32]byte(nil), cfg.Schedule...),
		XorAcc: map[[32]byte][32]byte{},
	}
	for _, n := range cfg.Nyms {
		if n.Key == (core.PubKey{}) {
			return nil, errors.New("genesis: zero nym key")
		}
		if n.Commit == ([32]byte{}) {
			return nil, errors.New("genesis: nym without commit would be unindexed")
		}
		if err := s.Nym.Insert([32]byte(n.Key), &NymEntry{Commit: n.Commit}); err != nil {
			return nil, err
		}
	}
	gh := Header{
		Seq: 0, Time: cfg.T0,
		NymT: s.Nym.Root(), NextNymT: s.NextNym.Root(),
		InviteT: s.Invite.Root(), NextInvT: s.NextInvite.Root(),
		ElectionT: s.Election.Root(), UTXOT: s.UTXO.Root(),
		Nonce: cfg.Seed[:],
	}
	s.LastHash = gh.Hash()
	return &Chain{State: s, Final: s.Clone(), finalIdx: -1, genesisState: s.Clone()}, nil
}

func (c *Chain) Produce(txs []core.Tx, T uint64, valPriv ed25519.PrivateKey, nonces map[[32]byte][]byte) (*Block, error) {
	b, ns, err := BuildBlock(c.State, txs, T, valPriv, nonces)
	if err != nil {
		return nil, err
	}
	c.State = ns
	c.Blocks = append(c.Blocks, b)
	c.advanceFinality()
	return b, nil
}

// horizon: finality at the previous period boundary.
func (c *Chain) horizon() uint64 {
	ps := c.State.periodStart(c.State.Time)
	if ps < PeriodSeconds+c.State.Genesis {
		return c.State.Genesis
	}
	return ps - PeriodSeconds
}

func (c *Chain) advanceFinality() {
	hz := c.horizon()
	for c.finalIdx+1 < len(c.Blocks) && c.Blocks[c.finalIdx+1].Header.Time < hz {
		ns, err := VerifyBlock(c.Final, c.Blocks[c.finalIdx+1])
		if err != nil {
			panic("finality: own chain does not verify: " + err.Error())
		}
		c.Final = ns
		c.finalIdx++
	}
}

func (c *Chain) stateAt(i int) (*State, error) {
	s := c.Final.Clone()
	for j := c.finalIdx + 1; j <= i; j++ {
		ns, err := VerifyBlock(s, c.Blocks[j])
		if err != nil {
			return nil, err
		}
		s = ns
	}
	return s, nil
}

func (c *Chain) skips() uint64 { return c.State.Seq - uint64(len(c.Blocks)) }

// TryAdopt: fewest skipped slots wins; ties favor the longer chain.
func (c *Chain) TryAdopt(branch []*Block) (bool, error) {
	if len(branch) == 0 {
		return false, errors.New("adopt: empty branch")
	}
	attach := -2
	if branch[0].Header.Prev == c.Final.LastHash {
		attach = c.finalIdx
	} else {
		for j := c.finalIdx + 1; j < len(c.Blocks); j++ {
			if branch[0].Header.Prev == c.Blocks[j].Header.Hash() {
				attach = j
				break
			}
		}
	}
	if attach == -2 {
		return false, errors.New("adopt: branch does not attach above the finality horizon")
	}
	s, err := c.stateAt(attach)
	if err != nil {
		return false, err
	}
	for _, b := range branch {
		ns, err := VerifyBlock(s, b)
		if err != nil {
			return false, fmt.Errorf("adopt: %w", err)
		}
		s = ns
	}
	candBlocks := uint64(attach+1) + uint64(len(branch))
	candSkips := s.Seq - candBlocks
	better := candSkips < c.skips() ||
		(candSkips == c.skips() && s.Seq > c.State.Seq)
	if !better {
		return false, nil
	}
	c.Blocks = append(c.Blocks[:attach+1], branch...)
	c.State = s
	c.advanceFinality()
	return true, nil
}
demo.go608
package bitpeople

// demo.go — three periods of the combined spec, end to end:
//
//	period 0: genesis pair verifies (auto next_nym + auto invite
//	          token), Use hands the token to carol, the mixers run,
//	          Commit(n) → election → Commit(e) → Vote → UBI, a dust
//	          transfer, reveals
//	period 1: pair verifies, court judges carol in (atomic promote
//	          with onboarding UBI), the chain runs for three, dave
//	          invited, reveals
//	period 2: odd population — the lone nym is judged in by its
//	          court (JudgeNym), dave judged in, an unused invite
//	          token pruned, the expired dust pruned, reveals
//
// Blocks are sparse; every unproduced slot is a skipped slot.

import (
	"crypto/ed25519"
	"crypto/rand"
	"errors"
	"fmt"
	"math/big"
	"strings"

	"peoplecoin/core"
)

type person struct {
	name string
	// current period's nym identity
	priv ed25519.PrivateKey
	pub  core.PubKey
	pre  [32]byte // preimage of the current nym commit
	// pending identity, chosen during the current period's mixing
	nextPriv ed25519.PrivateKey
	nextPub  core.PubKey
	nextPre  [32]byte
}

func (p *person) rollover() {
	p.priv, p.pub, p.pre = p.nextPriv, p.nextPub, p.nextPre
	p.nextPriv, p.nextPub = nil, core.PubKey{}
	p.nextPre = [32]byte{}
}

func (p *person) fresh() {
	p.nextPriv, p.nextPub = core.GenKey()
	if _, err := rand.Read(p.nextPre[:]); err != nil {
		panic(err)
	}
}

type demoWorld struct{ names map[core.PubKey]string }

func (w *demoWorld) tag(pub core.PubKey, label string) { w.names[pub] = label }
func (w *demoWorld) who(k [32]byte) string {
	if n, ok := w.names[core.PubKey(k)]; ok {
		return n
	}
	return core.PubKey(k).Short()
}

type valSvc struct {
	name   string
	priv   ed25519.PrivateKey
	pub    core.PubKey
	nonces map[[32]byte][]byte // schedule commit → onion layer nonce
}

func newVal(name string) *valSvc {
	priv, pub := core.GenKey()
	return &valSvc{name: name, priv: priv, pub: pub, nonces: map[[32]byte][]byte{}}
}

// register stores one onion layer's reveal material under the commit
// value the schedule will show at this validator's turn:
// H(validator ‖ nonce).
func (v *valSvc) register(l core.OnionLayer) {
	v.nonces[core.H(v.pub[:], l.Nonce)] = l.Nonce
}

// mkOnion builds a depth-layer multi-validator onion cycling the
// given validators, hands each their layer privately, and returns
// the top — an election commit or a genesis schedule entry.
func mkOnion(vals []*valSvc, depth int) [32]byte {
	seq := make([]core.PubKey, depth)
	for i := range seq {
		seq[i] = vals[i%len(vals)].pub
	}
	commit, layers := core.BuildOnion(seq, func(p []byte) {
		if _, err := rand.Read(p); err != nil {
			panic(err)
		}
	})
	for _, l := range layers {
		for _, v := range vals {
			if v.pub == l.Validator {
				v.register(l)
			}
		}
	}
	return commit
}

func produceAny(c *Chain, vals []*valSvc, T uint64, txs []core.Tx) (*Block, *valSvc, error) {
	for _, v := range vals {
		b, err := c.Produce(txs, T, v.priv, v.nonces)
		if err == nil {
			return b, v, nil
		}
		if !strings.Contains(err.Error(), "not ours") {
			return nil, nil, err
		}
	}
	return nil, nil, errors.New("no validator service owns this slot")
}

// ------------------------------------------------------------ makers

func signed[T core.Tx](t T, priv ed25519.PrivateKey, set func(core.Sig)) T {
	id := t.TxID()
	set(core.Sign(priv, id[:]))
	return t
}

func mkVerify(priv ed25519.PrivateKey) *Verify {
	t := &Verify{Key: core.Pub(priv)}
	return signed(t, priv, func(s core.Sig) { t.Sig = s })
}

func mkJudge(priv ed25519.PrivateKey, target [32]byte) *Judge {
	t := &Judge{Key: core.Pub(priv), Target: target}
	return signed(t, priv, func(s core.Sig) { t.Sig = s })
}

func mkJudgeNym(priv ed25519.PrivateKey, target [32]byte) *JudgeNym {
	t := &JudgeNym{Key: core.Pub(priv), Target: target}
	return signed(t, priv, func(s core.Sig) { t.Sig = s })
}

func mkUse(priv ed25519.PrivateKey, invitee core.PubKey) *Use {
	t := &Use{Key: [32]byte(core.Pub(priv)), NewKey: invitee}
	return signed(t, priv, func(s core.Sig) { t.Sig = s })
}

func mkCommitN(priv ed25519.PrivateKey, commit [32]byte) *CommitN {
	t := &CommitN{Key: core.Pub(priv), Commit: commit}
	return signed(t, priv, func(s core.Sig) { t.Sig = s })
}

func mkCommitE(priv ed25519.PrivateKey, commit [32]byte) *CommitE {
	t := &CommitE{Key: core.Pub(priv), Commit: commit}
	return signed(t, priv, func(s core.Sig) { t.Sig = s })
}

func mkVote(priv ed25519.PrivateKey) *Vote {
	t := &Vote{Key: core.Pub(priv)}
	return signed(t, priv, func(s core.Sig) { t.Sig = s })
}

// mkMix builds an n→n mix: inputs signed by their keys, outputs are
// fresh keys with mix_count = input mix_count sum / n + 1 spread.
func mkMix(trieID byte, s *State, privs []ed25519.PrivateKey) (*core.Mix, []ed25519.PrivateKey) {
	trie := s.mixTrie(trieID)
	n := len(privs)
	t := &core.Mix{TrieID: trieID}
	inSum := uint64(0)
	for _, p := range privs {
		k := [32]byte(core.Pub(p))
		t.Inputs = append(t.Inputs, k)
		inSum += uint64(trie.Get(k).MixCt())
	}
	outPrivs := make([]ed25519.PrivateKey, n)
	// Spread the budget: total = inSum + n, split as evenly as possible.
	total := inSum + uint64(n)
	for i := 0; i < n; i++ {
		mc := total / uint64(n)
		if uint64(i) < total%uint64(n) {
			mc++
		}
		priv, pub := core.GenKey()
		outPrivs[i] = priv
		t.Outputs = append(t.Outputs, core.MixOutput{Key: [32]byte(pub), MixCount: uint32(mc)})
	}
	id := t.TxID()
	for _, p := range privs {
		t.Sigs = append(t.Sigs, core.Sign(p, id[:]))
	}
	return t, outPrivs
}

// ------------------------------------------------------------ report

func printBoundary(w *demoWorld, s *State, period uint64) {
	fmt.Printf("│ period %d  seed=%s  N=%d  schedule=%d commits  invites to judge=%d\n",
		period, core.ShortHash(s.Seed), s.nymN(), len(s.Schedule), s.Invite.Count())
	N := s.nymN()
	for p := uint64(0); p < (N+1)/2; p++ {
		a, b := "—", "—"
		for r := uint64(0); r < N; r++ {
			switch s.posOfRank(r) {
			case 2 * p:
				k, _ := s.Nym.Select(r)
				a = w.who(k)
			case 2*p + 1:
				k, _ := s.Nym.Select(r)
				b = w.who(k)
			}
		}
		fmt.Printf("│   virtual pair %d: a=%-12s b=%-12s\n", p, a, b)
	}
	pc := N / 2
	for i, k := range s.Invite.CommittedKeys() {
		fmt.Printf("│   used invite: %s (court = pair %d)\n", w.who(k), uint64(i)%maxU64(pc, 1))
	}
}

func fmtTok(v *big.Int) string {
	f := new(big.Float).Quo(new(big.Float).SetInt(v), new(big.Float).SetInt(core.TOKEN))
	return f.Text('f', 6)
}

// RunDemo drives the scenario and returns the chain for replay.
func RunDemo() (*Chain, error) {
	w := &demoWorld{names: map[core.PubKey]string{}}
	t0 := uint64(14) * core.NormPeriod // 2026-01-01, slot-aligned
	slotT := func(period, slot uint64) uint64 { return t0 + period*PeriodSeconds + slot*core.SlotSeconds }
	eventEnd := EventSeconds / core.SlotSeconds // slot index of RNG start

	alice := &person{name: "alice"}
	bob := &person{name: "bob"}
	carol := &person{name: "carol"}
	dave := &person{name: "dave"}
	for _, p := range []*person{alice, bob, carol, dave} {
		p.fresh()
		p.rollover() // current identity for their first period
		p.fresh()    // pending
	}
	w.tag(alice.pub, "alice/p0")
	w.tag(bob.pub, "bob/p0")

	v1 := newVal("val-1")
	v2 := newVal("val-2")
	vals := []*valSvc{v1, v2}

	seed0 := core.H([]byte("bpc demo genesis seed"))
	chain, err := NewChain(GenesisConfig{
		T0: t0,
		Nyms: []GenesisNym{
			{Key: alice.pub, Commit: core.H(alice.pre[:])},
			{Key: bob.pub, Commit: core.H(bob.pre[:])},
		},
		Seed:     seed0,
		Schedule: [][32]byte{mkOnion([]*valSvc{v1, v2}, 16)},
	})
	if err != nil {
		return nil, err
	}

	produce := func(period, slot uint64, txs []core.Tx, label string) error {
		T := slotT(period, slot)
		b, v, err := produceAny(chain, vals, T, txs)
		if err != nil {
			return fmt.Errorf("%s: %w", label, err)
		}
		fmt.Printf("  block seq=%-6d p%d/%-6s validator=%-6s txs=%-2d  %s\n",
			b.Header.Seq, period, phaseName(chain.State, T), v.name, len(txs), label)
		return nil
	}
	pop := func() { fmt.Printf("  » population_count = %d\n", chain.State.Pop) }

	fmt.Println("┌─ genesis")
	printBoundary(w, chain.State, 0)
	fmt.Println("└─")

	// ======================================================= period 0
	if err := produce(0, 1, []core.Tx{mkVerify(alice.priv), mkVerify(bob.priv)},
		"event: pair verifies → auto next_nym ×2 + auto invite token"); err != nil {
		return nil, err
	}
	pop()

	// The auto invite token sits at the lower-position member's key.
	s := chain.State
	var inviter *person
	for _, p := range []*person{alice, bob} {
		if s.NextInvite.Get([32]byte(p.pub)) != nil {
			inviter = p
		}
	}
	w.tag(carol.pub, "carol/p1")
	if err := produce(0, 3, []core.Tx{mkUse(inviter.priv, carol.pub)},
		fmt.Sprintf("event: %s hands the invite token to carol (Use = rekey)", inviter.name)); err != nil {
		return nil, err
	}

	// Mix next_nym; the mix output keys become next period's nyms.
	mixN, outs := mkMix(TrieNextNym, chain.State, []ed25519.PrivateKey{alice.priv, bob.priv})
	alice.nextPriv, alice.nextPub = outs[0], core.Pub(outs[0])
	bob.nextPriv, bob.nextPub = outs[1], core.Pub(outs[1])
	w.tag(alice.nextPub, "alice/p1")
	w.tag(bob.nextPub, "bob/p1")
	if err := produce(0, 5, []core.Tx{mixN}, "event: Mix(next_nym) — fresh keys, link broken"); err != nil {
		return nil, err
	}

	// Commit(n) with next period's RNG commits → election entries.
	if err := produce(0, 7, []core.Tx{
		mkCommitN(alice.nextPriv, core.H(alice.nextPre[:])),
		mkCommitN(bob.nextPriv, core.H(bob.nextPre[:])),
	}, "event: Commit(n) ×2 → auto election entries"); err != nil {
		return nil, err
	}

	// Mix the election trie, then Commit(e) with validator commits.
	mixE, eouts := mkMix(TrieElection, chain.State, []ed25519.PrivateKey{alice.nextPriv, bob.nextPriv})
	if err := produce(0, 9, []core.Tx{mixE}, "event: Mix(election) — voter↔validator link broken"); err != nil {
		return nil, err
	}
	if err := produce(0, 11, []core.Tx{
		mkCommitE(eouts[0], mkOnion([]*valSvc{v1}, 16)),
		mkCommitE(eouts[1], mkOnion([]*valSvc{v2}, 16)),
	}, "event: Commit(e) ×2 = hash(validator ‖ nonce)"); err != nil {
		return nil, err
	}

	// Vote → UBI, minted to the post-mix election keys.
	if err := produce(0, 13, []core.Tx{mkVote(eouts[0]), mkVote(eouts[1])},
		"event: Vote ×2 → UBI minted"); err != nil {
		return nil, err
	}

	// A dust transfer for the later prune.
	s = chain.State
	voteID := mkVote(eouts[0]).TxID()
	ubiKey := core.CoinKey(voteID, 0)
	_, throwP := core.GenKey()
	w.tag(throwP, "dust")
	spendT := slotT(0, 15)
	dustAmt := big.NewInt(50_000_000)
	change := new(big.Int).Sub(s.UTXO.Get(ubiKey).Spendable(spendT), dustAmt)
	tr := &core.Transfer{
		Inputs:  []core.Outpoint{{Tx: voteID, Index: 0}},
		Outputs: []core.Output{{Amount: dustAmt, Owner: throwP}, {Amount: change, Owner: core.Pub(eouts[0])}},
	}
	trID := tr.TxID()
	tr.Sigs = []core.Sig{core.Sign(eouts[0], trID[:])}
	if err := produce(0, 15, []core.Tx{tr}, "event: transfer (dust output, expires in ~14 h)"); err != nil {
		return nil, err
	}

	// RNG: reveals.
	if err := produce(0, eventEnd+1, []core.Tx{
		&Reveal{Key: [32]byte(alice.pub), Preimage: alice.pre},
		&Reveal{Key: [32]byte(bob.pub), Preimage: bob.pre},
	}, "rng: reveals ×2"); err != nil {
		return nil, err
	}

	// ======================================================= period 1
	alice.rollover()
	alice.fresh()
	bob.rollover()
	bob.fresh()
	if err := produce(1, 1, []core.Tx{mkVerify(alice.priv), mkVerify(bob.priv)},
		"boundary + event: pair verifies"); err != nil {
		return nil, err
	}
	fmt.Println("┌─ activation")
	printBoundary(w, chain.State, 1)
	fmt.Println("└─")
	pop()

	// Court (the only pair) judges carol in: atomic promote.
	if err := produce(1, 3, []core.Tx{
		mkJudge(alice.priv, [32]byte(carol.pub)),
		mkJudge(bob.priv, [32]byte(carol.pub)),
	}, "event: court judges carol → next_nym + election + UBI, atomically"); err != nil {
		return nil, err
	}
	pop()

	// This period's auto invite token → dave.
	s = chain.State
	inviter = nil
	for _, p := range []*person{alice, bob} {
		if s.NextInvite.Get([32]byte(p.pub)) != nil {
			inviter = p
		}
	}
	w.tag(dave.pub, "dave/p2")
	if err := produce(1, 5, []core.Tx{mkUse(inviter.priv, dave.pub)},
		fmt.Sprintf("event: %s hands this period's token to dave", inviter.name)); err != nil {
		return nil, err
	}

	// Mix next_nym ×3 (carol's judged entry joins the mixer).
	mixN1, outs1 := mkMix(TrieNextNym, chain.State,
		[]ed25519.PrivateKey{alice.priv, bob.priv, carol.priv})
	alice.nextPriv, alice.nextPub = outs1[0], core.Pub(outs1[0])
	bob.nextPriv, bob.nextPub = outs1[1], core.Pub(outs1[1])
	carol.nextPriv, carol.nextPub = outs1[2], core.Pub(outs1[2])
	carol.fresh2()
	w.tag(alice.nextPub, "alice/p2")
	w.tag(bob.nextPub, "bob/p2")
	w.tag(carol.nextPub, "carol/p2")
	if err := produce(1, 7, []core.Tx{mixN1}, "event: Mix(next_nym) ×3"); err != nil {
		return nil, err
	}
	if err := produce(1, 9, []core.Tx{
		mkCommitN(alice.nextPriv, core.H(alice.nextPre[:])),
		mkCommitN(bob.nextPriv, core.H(bob.nextPre[:])),
		mkCommitN(carol.nextPriv, core.H(carol.nextPre[:])),
	}, "event: Commit(n) ×3"); err != nil {
		return nil, err
	}

	// Election: three Commit(n) entries + carol's onboarding entry.
	mixE1, eouts1 := mkMix(TrieElection, chain.State,
		[]ed25519.PrivateKey{alice.nextPriv, bob.nextPriv, carol.nextPriv, carol.priv})
	if err := produce(1, 11, []core.Tx{mixE1}, "event: Mix(election) ×4"); err != nil {
		return nil, err
	}
	votes := []core.Tx{}
	commits := []core.Tx{}
	for i, ep := range eouts1 {
		v := vals[i%2]
		commits = append(commits, mkCommitE(ep, mkOnion([]*valSvc{v}, 16)))
		votes = append(votes, mkVote(ep))
	}
	if err := produce(1, 13, commits, "event: Commit(e) ×4"); err != nil {
		return nil, err
	}
	if err := produce(1, 15, votes, "event: Vote ×4 → UBI (carol's onboarding entry included)"); err != nil {
		return nil, err
	}

	if err := produce(1, eventEnd+1, []core.Tx{
		&Reveal{Key: [32]byte(alice.pub), Preimage: alice.pre},
		&Reveal{Key: [32]byte(bob.pub), Preimage: bob.pre},
	}, "rng: reveals ×2 (carol has no nym commit her first period)"); err != nil {
		return nil, err
	}

	// ======================================================= period 2
	alice.rollover()
	alice.fresh()
	bob.rollover()
	bob.fresh()
	carol.rollover()
	carol.fresh()
	if err := produce(2, 1, nil, "boundary: activation (N=3, odd)"); err != nil {
		return nil, err
	}
	fmt.Println("┌─ activation")
	printBoundary(w, chain.State, 2)
	fmt.Println("└─")

	// Identify the full pair and the lone position.
	s = chain.State
	N := s.nymN()
	var pairA, pairB, lone *person
	for _, p := range []*person{alice, bob, carol} {
		r, _ := s.nymRank([32]byte(p.pub))
		switch s.posOfRank(r) {
		case 0:
			pairA = p
		case 1:
			pairB = p
		default:
			lone = p
		}
	}
	_ = N
	if err := produce(2, 1+1, []core.Tx{
		mkVerify(pairA.priv), mkVerify(pairB.priv),
	}, fmt.Sprintf("event: pair 0 (%s, %s) verifies", pairA.name, pairB.name)); err != nil {
		return nil, err
	}
	pop()
	if err := produce(2, 3, []core.Tx{
		mkJudgeNym(pairA.priv, [32]byte(lone.pub)),
		mkJudgeNym(pairB.priv, [32]byte(lone.pub)),
	}, fmt.Sprintf("event: court (pair 0) judges lone %s → next_nym", lone.name)); err != nil {
		return nil, err
	}
	pop()
	if err := produce(2, 5, []core.Tx{
		mkJudge(pairA.priv, [32]byte(dave.pub)),
		mkJudge(pairB.priv, [32]byte(dave.pub)),
	}, "event: court judges dave in"); err != nil {
		return nil, err
	}
	pop()

	// Prune the expired dust.
	s = chain.State
	var dustKey [32]byte
	s.UTXO.ForEach(func(k [32]byte, e *core.CoinEntry) {
		if w.who([32]byte(e.Owner)) == "dust" {
			dustKey = k
		}
	})
	if err := produce(2, 7, []core.Tx{&Prune{TrieID: TrieUTXO, Keys: [][32]byte{dustKey}}},
		"event: prune expired dust (producer collects)"); err != nil {
		return nil, err
	}

	// Mix + commit ×4 (dave joins), election ×5.
	mixN2, outs2 := mkMix(TrieNextNym, chain.State,
		[]ed25519.PrivateKey{pairA.priv, pairB.priv, lone.priv, dave.priv})
	people2 := []*person{pairA, pairB, lone, dave}
	for i, p := range people2 {
		p.nextPriv, p.nextPub = outs2[i], core.Pub(outs2[i])
		p.fresh2()
		w.tag(p.nextPub, p.name+"/p3")
	}
	if err := produce(2, 9, []core.Tx{mixN2}, "event: Mix(next_nym) ×4"); err != nil {
		return nil, err
	}
	var cN []core.Tx
	for _, p := range people2 {
		cN = append(cN, mkCommitN(p.nextPriv, core.H(p.nextPre[:])))
	}
	if err := produce(2, 11, cN, "event: Commit(n) ×4"); err != nil {
		return nil, err
	}
	elecPrivs := []ed25519.PrivateKey{dave.priv}
	for _, p := range people2 {
		elecPrivs = append(elecPrivs, p.nextPriv)
	}
	mixE2, eouts2 := mkMix(TrieElection, chain.State, elecPrivs)
	if err := produce(2, 13, []core.Tx{mixE2}, "event: Mix(election) ×5"); err != nil {
		return nil, err
	}
	commits2, votes2 := []core.Tx{}, []core.Tx{}
	for i, ep := range eouts2 {
		commits2 = append(commits2, mkCommitE(ep, mkOnion([]*valSvc{vals[i%2]}, 16)))
		votes2 = append(votes2, mkVote(ep))
	}
	if err := produce(2, 15, commits2, "event: Commit(e) ×5"); err != nil {
		return nil, err
	}
	if err := produce(2, 17, votes2, "event: Vote ×5 → UBI"); err != nil {
		return nil, err
	}

	// This period's invite token stays unused → prune it in Pause.
	var reveals []core.Tx
	for _, p := range []*person{pairA, pairB, lone} {
		reveals = append(reveals, &Reveal{Key: [32]byte(p.pub), Preimage: p.pre})
	}
	if err := produce(2, eventEnd+1, reveals, "rng: reveals ×3"); err != nil {
		return nil, err
	}
	s = chain.State
	var staleTok [][32]byte
	s.NextInvite.ForEach(func(k [32]byte, e core.Entry) {
		if !e.Counted() {
			staleTok = append(staleTok, k)
		}
	})
	if len(staleTok) > 0 {
		if err := produce(2, (EventSeconds+RNGSeconds)/core.SlotSeconds+1,
			[]core.Tx{&Prune{TrieID: TrieNextInvite, Keys: staleTok}},
			"pause: prune the unused invite token"); err != nil {
			return nil, err
		}
	}

	// ======================================================= period 3
	if err := produce(3, 1, nil, "boundary: activation"); err != nil {
		return nil, err
	}
	fmt.Println("┌─ activation")
	printBoundary(w, chain.State, 3)
	fmt.Println("└─")

	T := chain.State.Time
	fmt.Printf("supply(T)   = %s TOKEN (Vote-minted UBI + fees to validators)\n", fmtTok(chain.State.SupplyAt(T)))
	fmt.Printf("ubi/period  = %s TOKEN\n", fmtTok(UBIPerPeriod()))
	fmt.Printf("blocks=%d  tip seq=%d  skipped slots=%d (sparse demo)  final idx=%d\n",
		len(chain.Blocks), chain.State.Seq, chain.skips(), chain.finalIdx)
	// Full replay through VerifyBlock — the demo self-verifies.
	fmt.Print("replaying full chain through VerifyBlock… ")
	rs := chain.genesisState.Clone()
	for i, b := range chain.Blocks {
		ns, err := VerifyBlock(rs, b)
		if err != nil {
			return nil, fmt.Errorf("replay block %d: %w", i, err)
		}
		rs = ns
	}
	if rs.LastHash != chain.State.LastHash {
		return nil, errors.New("replay tip differs from build tip")
	}
	fmt.Printf("ok (%d blocks, tip %s)\n", len(chain.Blocks), core.ShortHash(rs.LastHash))
	return chain, nil
}

// fresh2 regenerates only the pending preimage/keys when the pending
// key was assigned externally (from a mix output).
func (p *person) fresh2() {
	if _, err := rand.Read(p.nextPre[:]); err != nil {
		panic(err)
	}
}
entries.go84
package bitpeople

import (
	"math/big"

	"peoplecoin/core"
)

// ============================================================== nym

// NymEntry — nym_trie and next_nym_trie share the shape. In the
// active trie: verified/judged/seed_hits live, commit is the RNG
// commit. In the mixer: mix_count and commit (set by Commit(n)).
type NymEntry struct {
	MixCount uint32
	Verified bool
	JudgedA  bool
	JudgedB  bool
	Commit   [32]byte
	SeedHits uint32
}

func (e *NymEntry) LeafHash(k [32]byte) [32]byte {
	var w core.Buf
	w.U8(0x10)
	w.Bytes(k[:])
	w.U32(e.MixCount)
	w.Boolb(e.Verified)
	w.Boolb(e.JudgedA)
	w.Boolb(e.JudgedB)
	w.Bytes(e.Commit[:])
	w.U32(e.SeedHits)
	return core.H(w.B)
}

// committed: the nym tries' own count definition (v14: aggregates
// are type-specific), commit != 0 per the nym_trie section. The
// commit IS the registration act — indexing is established by commits, so
// an entry that never commits has no rank, no pair, no court, no
// RNG: the protocol cannot even reference it. It self-cleans (no
// verify → no next_nym entry → gone at the next swap) or is pruned.
func (e *NymEntry) Counted() bool          { return e.Commit != [32]byte{} }
func (e *NymEntry) HitsVal() uint32        { return e.SeedHits }
func (e *NymEntry) SumVal() *big.Int       { return nil }
func (e *NymEntry) MixCt() uint32          { return e.MixCount }
func (e *NymEntry) CloneEntry() core.Entry { c := *e; return &c }

func freshNym(mc uint32) core.Entry { return &NymEntry{MixCount: mc} }

func NewNymTrie() *core.Trie { return core.NewTrie(0x11, freshNym, true, false) }

// ============================================================ invite

// InviteEntry — invite tokens. `used` doubles as the count aggregate
// (court ranks run over used invites in key order).
type InviteEntry struct {
	MixCount     uint32
	Used         bool
	CourtJudgedA bool
	CourtJudgedB bool
}

func (e *InviteEntry) LeafHash(k [32]byte) [32]byte {
	var w core.Buf
	w.U8(0x12)
	w.Bytes(k[:])
	w.U32(e.MixCount)
	w.Boolb(e.Used)
	w.Boolb(e.CourtJudgedA)
	w.Boolb(e.CourtJudgedB)
	return core.H(w.B)
}

// committed: used — the invite analog of commit != 0 (the entry has
// no commit field); court ranks run over real invites.
func (e *InviteEntry) Counted() bool          { return e.Used }
func (e *InviteEntry) HitsVal() uint32        { return 0 }
func (e *InviteEntry) SumVal() *big.Int       { return nil }
func (e *InviteEntry) MixCt() uint32          { return e.MixCount }
func (e *InviteEntry) CloneEntry() core.Entry { c := *e; return &c }

func freshInvite(mc uint32) core.Entry { return &InviteEntry{MixCount: mc} }

func NewInviteTrie() *core.Trie { return core.NewTrie(0x13, freshInvite, false, false) }
feistel.go120
package bitpeople

// feistel.go — bitpeople's Feistel shuffle (Horst Feistel, 1973).
//
// A guaranteed (not probabilistic) permutation: every position can be
// computed independently from the seed, without computing the full
// shuffle. 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. Each round's output is
// indistinguishable from fresh input, so any number of rounds
// preserves uniqueness.
//
// Per the bitpeople spec:
//   - width = smallest even number of bits ≥ log2(N), split in halves
//   - 3 rounds: L', R' = R, L XOR f(R, key_i)
//   - round_key_i = sha256(seed || i)
//   - f(R, key) = sha256(R || key), truncated to half-width bits
//   - cycle-walking: if result ≥ N, apply again until < N. Since the
//     permutation is a bijection on the full 2^width domain, no two
//     chains can merge, so the first in-range value is unique.
//
// Encoding choices fixed by this implementation (the spec leaves them
// open): the round index i is a single byte 0..2, and R is encoded as
// 8 bytes big-endian before hashing; the truncation takes the first
// 8 bytes of the digest as a big-endian uint64 masked to half-width.

import (
	"peoplecoin/core"

	"encoding/binary"
	"math/bits"
)

// feistelWidth returns (halfWidth, halfMask) for a domain of size N ≥ 2.
func feistelWidth(N uint64) (uint, uint64) {
	w := uint(bits.Len64(N - 1)) // ceil(log2(N)) for N ≥ 2
	if w == 0 {
		w = 1
	}
	if w%2 == 1 {
		w++
	}
	hw := w / 2
	return hw, (uint64(1) << hw) - 1
}

// feistel3 runs the three Feistel rounds over the 2^(2·hw) domain.
func feistel3(x uint64, hw uint, mask uint64, seed [32]byte) uint64 {
	L := x >> hw
	R := x & mask
	for i := byte(0); i < 3; i++ {
		key := core.H(seed[:], []byte{i}) // round_key_i = sha256(seed || i)
		var rb [8]byte
		binary.BigEndian.PutUint64(rb[:], R)
		fh := core.H(rb[:], key[:]) // f(R, key) = sha256(R || key)
		f := binary.BigEndian.Uint64(fh[:8]) & mask
		L, R = R, L^f
	}
	return (L << hw) | R
}

// FeistelPos is the shuffle: new_position = feistel(nym_id, seed, N),
// with cycle-walking to stay inside [0, N).
func FeistelPos(id uint64, seed [32]byte, N uint64) uint64 {
	if N <= 1 {
		return 0
	}
	if id >= N {
		panic("FeistelPos: id out of range")
	}
	hw, mask := feistelWidth(N)
	x := id
	for {
		x = feistel3(x, hw, mask, seed)
		if x < N {
			return x
		}
	}
}

// feistel3Inv runs the rounds backwards — the inverse permutation.
// Forward round: L', R' = R, L ^ f(R). Backward: R = L', L = R' ^ f(L').
func feistel3Inv(x uint64, hw uint, mask uint64, seed [32]byte) uint64 {
	L := x >> hw
	R := x & mask
	for i := byte(2); ; i-- {
		key := core.H(seed[:], []byte{i})
		var rb [8]byte
		binary.BigEndian.PutUint64(rb[:], L) // forward's R is backward's L
		fh := core.H(rb[:], key[:])
		f := binary.BigEndian.Uint64(fh[:8]) & mask
		L, R = R^f, L
		if i == 0 {
			break
		}
	}
	return (L << hw) | R
}

// FeistelPosInv is the inverse of FeistelPos, with the same
// cycle-walking: repeatedly invert until the value falls in [0, N).
// Because the permutation is a bijection on the padded domain, this
// terminates and agrees with the forward walk.
func FeistelPosInv(pos uint64, seed [32]byte, N uint64) uint64 {
	if N <= 1 {
		return 0
	}
	if pos >= N {
		panic("feistel: position out of range")
	}
	hw, mask := feistelWidth(N)
	x := pos
	for {
		x = feistel3Inv(x, hw, mask, seed)
		if x < N {
			return x
		}
	}
}
feistel_inv_test.go19
package bitpeople

import (
	"crypto/rand"
	"testing"
)

func TestFeistelInverse(t *testing.T) {
	for _, n := range []uint64{2, 3, 5, 8, 31, 100, 257} {
		var seed [32]byte
		rand.Read(seed[:])
		for i := uint64(0); i < n; i++ {
			p := FeistelPos(i, seed, n)
			if FeistelPosInv(p, seed, n) != i {
				t.Fatalf("N=%d: inv(pos(%d)) != %d", n, i, i)
			}
		}
	}
}
tx.go623
package bitpeople

// tx.go — the transaction set of the combined spec, with the
// automatic side effects that drive the incentive chain:
//
//	Verify   → next_nym entries when the pair completes,
//	           + an invite token for eligible nyms (automatic)
//	Judge    → invitee promoted: next_nym + election + UBI, atomically
//	JudgeNym → next_nym entry when both court members have judged
//	Commit(n)→ election entry
//	Vote     → UBI minted to the post-mix address
//
// Rank lookups read the live tries: every index is locked at the
// swap. Mix and Transfer are the shared core primitives; this file
// gates and routes them.

import (
	"errors"
	"fmt"
	"math/big"

	"peoplecoin/core"
)

const (
	OpVerify   byte = 0x10
	OpJudge    byte = 0x11
	OpJudgeNym byte = 0x12
	OpUse      byte = 0x14
	OpCommitN  byte = 0x30
	OpCommitE  byte = 0x31
	OpVote     byte = 0x32
	OpReveal   byte = 0x40
	OpHeader   byte = 0xF0
)

const (
	maxInputs  = 1024
	maxOutputs = 1024

	TrieUTXO       byte = 0
	TrieNextNym    byte = 1
	TrieNextInvite byte = 2
	TrieElection   byte = 3
)

// ============================================================ verify

// Verify — sets verified on the signer's nym_trie entry. When the
// virtual pair completes, both members automatically get next_nym
// entries (key = current address, zeroed), and an invite token is
// issued to the pair's lower-position member while the period's
// quota (population_count / 2, min 1) allows.
type Verify struct {
	Key core.PubKey
	Sig core.Sig
}

func (t *Verify) Body() []byte {
	var w core.Buf
	w.U8(OpVerify)
	w.Bytes(t.Key[:])
	return w.B
}
func (t *Verify) TxID() [32]byte { return core.H(t.Body()) }

func (s *State) applyVerify(t *Verify, T uint64) error {
	if !inEvent(s, T) {
		return errors.New("verify: outside event phase")
	}
	e, _ := s.Nym.Get([32]byte(t.Key)).(*NymEntry)
	if e == nil {
		return errors.New("verify: no nym entry")
	}
	if e.Verified {
		return errors.New("verify: already verified")
	}
	r, ok := s.nymRank([32]byte(t.Key))
	if !ok {
		return errors.New("verify: nym not in the boundary snapshot")
	}
	id := t.TxID()
	if !core.VerifySig(t.Key, id[:], t.Sig) {
		return errors.New("verify: bad signature")
	}
	e.Verified = true
	s.Nym.Refresh([32]byte(t.Key))

	// Pair completion?
	pr, hasPartner := s.partnerRank(r)
	if !hasPartner {
		return nil // lone position (odd N): the JudgeNym path
	}
	pk, _ := s.Nym.Select(pr)
	pe, _ := s.Nym.Get(pk).(*NymEntry)
	if pe == nil || !pe.Verified {
		return nil
	}
	// Both verified: auto next_nym entries.
	if err := s.NextNym.Insert([32]byte(t.Key), freshNym(0)); err != nil {
		return fmt.Errorf("verify: %w", err)
	}
	if err := s.NextNym.Insert(pk, freshNym(0)); err != nil {
		return fmt.Errorf("verify: %w", err)
	}
	s.Pop += 2
	// Automatic invite issuance to the lower virtual position, while
	// the growth cap (population_count / 2, min 1) allows.
	if s.InvitesIssued < maxU64(1, s.Pop/2) {
		low := [32]byte(t.Key)
		if s.posOfRank(pr) < s.posOfRank(r) {
			low = pk
		}
		if err := s.NextInvite.Insert(low, freshInvite(0)); err == nil {
			s.InvitesIssued++
		}
	}
	return nil
}

// ============================================================= judge

// Judge — a court pair member confirms a used invite from the
// previous period. Court assignment: invite at rank i (among used
// invites — the index locked at the swap) → court pair
// i mod pair_count. When both members have judged, the invitee is
// promoted atomically: next_nym + election + UBI. The entry stays,
// fully judged, until the swap discards the trie — no operation
// alters the invite index during the period (v15), and the flags
// themselves prevent double promotion.
type Judge struct {
	Key    core.PubKey // court member (nym_trie)
	Target [32]byte    // invite key (invite_trie)
	Sig    core.Sig
}

func (t *Judge) Body() []byte {
	var w core.Buf
	w.U8(OpJudge)
	w.Bytes(t.Key[:])
	w.Bytes(t.Target[:])
	return w.B
}
func (t *Judge) TxID() [32]byte { return core.H(t.Body()) }

func (s *State) applyJudge(t *Judge, T uint64) error {
	if !inEvent(s, T) {
		return errors.New("judge: outside event phase")
	}
	inv, _ := s.Invite.Get(t.Target).(*InviteEntry)
	if inv == nil || !inv.Used {
		return errors.New("judge: no used invite at target")
	}
	i, ok := s.inviteRank(t.Target)
	if !ok {
		return errors.New("judge: invite not in the boundary snapshot")
	}
	side, err := s.courtSide([32]byte(t.Key), i)
	if err != nil {
		return fmt.Errorf("judge: %w", err)
	}
	id := t.TxID()
	if !core.VerifySig(t.Key, id[:], t.Sig) {
		return errors.New("judge: bad signature")
	}
	if side == 0 {
		if inv.CourtJudgedA {
			return errors.New("judge: side a already judged")
		}
		inv.CourtJudgedA = true
	} else {
		if inv.CourtJudgedB {
			return errors.New("judge: side b already judged")
		}
		inv.CourtJudgedB = true
	}
	s.Invite.Refresh(t.Target)
	if !(inv.CourtJudgedA && inv.CourtJudgedB) {
		return nil
	}
	// Promotion, atomic: next_nym + election + UBI. The invite
	// entry remains in place, both flags set.
	if err := s.NextNym.Insert(t.Target, freshNym(0)); err != nil {
		return fmt.Errorf("judge: %w", err)
	}
	if err := s.Election.Insert(t.Target, core.FreshElection(0)); err != nil {
		return fmt.Errorf("judge: %w", err)
	}
	ubi := UBIPerPeriod()
	if err := s.UTXO.Insert(core.CoinKey(id, 0), &core.CoinEntry{
		Owner: core.PubKey(t.Target), Amount: ubi,
		Norm: core.Normalize(ubi, T, s.NormTime), Time: T,
	}); err != nil {
		return fmt.Errorf("judge: %w", err)
	}
	s.Pop++
	return nil
}

// ========================================================== judgenym

// JudgeNym — a court pair member confirms a nym that cannot pair-
// verify (odd population's lone position, or a failed pair). Court:
// nym rank mod pair_count. Both sides set → next_nym entry.
type JudgeNym struct {
	Key    core.PubKey // court member
	Target [32]byte    // nym key
	Sig    core.Sig
}

func (t *JudgeNym) Body() []byte {
	var w core.Buf
	w.U8(OpJudgeNym)
	w.Bytes(t.Key[:])
	w.Bytes(t.Target[:])
	return w.B
}
func (t *JudgeNym) TxID() [32]byte { return core.H(t.Body()) }

func (s *State) applyJudgeNym(t *JudgeNym, T uint64) error {
	if !inEvent(s, T) {
		return errors.New("judge_nym: outside event phase")
	}
	e, _ := s.Nym.Get(t.Target).(*NymEntry)
	if e == nil {
		return errors.New("judge_nym: no nym entry at target")
	}
	tr, ok := s.nymRank(t.Target)
	if !ok {
		return errors.New("judge_nym: target not in the boundary snapshot")
	}
	side, err := s.courtSide([32]byte(t.Key), tr)
	if err != nil {
		return fmt.Errorf("judge_nym: %w", err)
	}
	id := t.TxID()
	if !core.VerifySig(t.Key, id[:], t.Sig) {
		return errors.New("judge_nym: bad signature")
	}
	if side == 0 {
		if e.JudgedA {
			return errors.New("judge_nym: side a already judged")
		}
		e.JudgedA = true
	} else {
		if e.JudgedB {
			return errors.New("judge_nym: side b already judged")
		}
		e.JudgedB = true
	}
	s.Nym.Refresh(t.Target)
	if e.JudgedA && e.JudgedB {
		if err := s.NextNym.Insert(t.Target, freshNym(0)); err != nil {
			return fmt.Errorf("judge_nym: %w", err)
		}
		s.Pop++
	}
	return nil
}

// =============================================================== use

// Use — the inviter hands the (mixed) invite token to the invitee by
// rekeying the next_invite entry to the invitee's key and setting
// used = true.
type Use struct {
	Key    [32]byte    // current token key
	NewKey core.PubKey // invitee's address
	Sig    core.Sig
}

func (t *Use) Body() []byte {
	var w core.Buf
	w.U8(OpUse)
	w.Bytes(t.Key[:])
	w.Bytes(t.NewKey[:])
	return w.B
}
func (t *Use) TxID() [32]byte { return core.H(t.Body()) }

func (s *State) applyUse(t *Use, T uint64) error {
	if !inEvent(s, T) {
		return errors.New("use: outside event phase")
	}
	inv, _ := s.NextInvite.Get(t.Key).(*InviteEntry)
	if inv == nil {
		return errors.New("use: no invite token at key")
	}
	if inv.Used {
		return errors.New("use: token already used")
	}
	if t.NewKey == (core.PubKey{}) || s.NextInvite.Get([32]byte(t.NewKey)) != nil {
		return errors.New("use: invitee key not fresh")
	}
	id := t.TxID()
	if !core.VerifySig(core.PubKey(t.Key), id[:], t.Sig) {
		return errors.New("use: bad signature")
	}
	if _, err := s.NextInvite.Remove(t.Key); err != nil {
		return err
	}
	return s.NextInvite.Insert([32]byte(t.NewKey),
		&InviteEntry{MixCount: inv.MixCount, Used: true})
}

// =============================================================== mix

// applyMix routes and phase-gates the core Mix.
func (s *State) applyMix(t *core.Mix, T uint64) error {
	if !inEvent(s, T) {
		return errors.New("mix: outside event phase")
	}
	trie := s.mixTrie(t.TrieID)
	if trie == nil {
		return errors.New("mix: trie does not support mixing")
	}
	return core.ApplyMix(trie, t)
}

func (s *State) mixTrie(id byte) *core.Trie {
	switch id {
	case TrieNextNym:
		return s.NextNym
	case TrieNextInvite:
		return s.NextInvite
	case TrieElection:
		return s.Election
	}
	return nil
}

// ============================================================ commit

// CommitN — sets the RNG commit on a next_nym entry. Requires
// mix_count > 0 (everyone passes through the mixer). Automatically
// creates the election entry at the same (post-mix) address.
type CommitN struct {
	Key    core.PubKey
	Commit [32]byte
	Sig    core.Sig
}

func (t *CommitN) Body() []byte {
	var w core.Buf
	w.U8(OpCommitN)
	w.Bytes(t.Key[:])
	w.Bytes(t.Commit[:])
	return w.B
}
func (t *CommitN) TxID() [32]byte { return core.H(t.Body()) }

func (s *State) applyCommitN(t *CommitN, T uint64) error {
	if !inEvent(s, T) {
		return errors.New("commit_n: outside event phase (deadline = RNG start)")
	}
	e, _ := s.NextNym.Get([32]byte(t.Key)).(*NymEntry)
	if e == nil {
		return errors.New("commit_n: no next_nym entry")
	}
	if e.MixCount == 0 {
		return errors.New("commit_n: mix_count must be > 0")
	}
	if e.Commit != ([32]byte{}) {
		return errors.New("commit_n: already committed")
	}
	if t.Commit == ([32]byte{}) {
		return errors.New("commit_n: zero commit")
	}
	id := t.TxID()
	if !core.VerifySig(t.Key, id[:], t.Sig) {
		return errors.New("commit_n: bad signature")
	}
	if err := s.Election.Insert([32]byte(t.Key), core.FreshElection(0)); err != nil {
		return fmt.Errorf("commit_n: %w", err)
	}
	e.Commit = t.Commit
	s.NextNym.Touch([32]byte(t.Key)) // the index grows by this commit
	return nil
}

// CommitE — sets commit = hash(validator || nonce) on an election
// entry. Requires mix_count > 0. The voter gives the nonce to the
// validator privately.
type CommitE struct {
	Key    core.PubKey
	Commit [32]byte
	Sig    core.Sig
}

func (t *CommitE) Body() []byte {
	var w core.Buf
	w.U8(OpCommitE)
	w.Bytes(t.Key[:])
	w.Bytes(t.Commit[:])
	return w.B
}
func (t *CommitE) TxID() [32]byte { return core.H(t.Body()) }

func (s *State) applyCommitE(t *CommitE, T uint64) error {
	if !inEvent(s, T) {
		return errors.New("commit_e: outside event phase (deadline = RNG start)")
	}
	e, _ := s.Election.Get([32]byte(t.Key)).(*core.ElectionEntry)
	if e == nil {
		return errors.New("commit_e: no election entry")
	}
	if e.MixCount == 0 {
		return errors.New("commit_e: mix_count must be > 0")
	}
	if e.Commit != ([32]byte{}) {
		return errors.New("commit_e: already committed")
	}
	if t.Commit == ([32]byte{}) {
		return errors.New("commit_e: zero commit")
	}
	id := t.TxID()
	if !core.VerifySig(t.Key, id[:], t.Sig) {
		return errors.New("commit_e: bad signature")
	}
	e.Commit = t.Commit
	s.Election.Touch([32]byte(t.Key))
	return nil
}

// ============================================================== vote

// Vote — triggers the UBI mint to the election entry's address. The
// mint is keyed hash(txid || 0); a replayed Vote produces the same
// txid (deterministic signatures), collides, and is rejected — the
// idempotence is structural.
type Vote struct {
	Key core.PubKey
	Sig core.Sig
}

func (t *Vote) Body() []byte {
	var w core.Buf
	w.U8(OpVote)
	w.Bytes(t.Key[:])
	return w.B
}
func (t *Vote) TxID() [32]byte { return core.H(t.Body()) }

func (s *State) applyVote(t *Vote, T uint64) error {
	if !inEvent(s, T) {
		return errors.New("vote: outside event phase")
	}
	e, _ := s.Election.Get([32]byte(t.Key)).(*core.ElectionEntry)
	if e == nil {
		return errors.New("vote: no election entry")
	}
	if e.Commit == ([32]byte{}) {
		return errors.New("vote: entry has not committed")
	}
	id := t.TxID()
	if !core.VerifySig(t.Key, id[:], t.Sig) {
		return errors.New("vote: bad signature")
	}
	ubi := UBIPerPeriod()
	return s.UTXO.Insert(core.CoinKey(id, 0), &core.CoinEntry{
		Owner: t.Key, Amount: ubi,
		Norm: core.Normalize(ubi, T, s.NormTime), Time: T,
	})
}

// ============================================================ reveal

// Reveal — RNG phase. Preimage matching the nym entry's commit;
// target = (revealer_rank + uint(preimage)) mod count. The index was
// established by commits in next_nym and locked at the swap — counts
// are delta-maintained and Reveal issues no deltas — so the commit
// field is consumable: Reveal zeroes it, and commit == 0 IS the
// replay protection. Without it a block producer could re-include a
// chosen reveal 2k+1 times, inflating its target's hits while the
// XOR folds collapse to one — seed grinding. The revealed entry
// keeps its rank and remains a valid target: N never shrinks.
type Reveal struct {
	Key      [32]byte
	Preimage [32]byte
}

func (t *Reveal) Body() []byte {
	var w core.Buf
	w.U8(OpReveal)
	w.Bytes(t.Key[:])
	w.Bytes(t.Preimage[:])
	return w.B
}
func (t *Reveal) TxID() [32]byte { return core.H(t.Body()) }

func (s *State) applyReveal(t *Reveal, T uint64) error {
	if !inRNG(s, T) {
		return errors.New("reveal: outside RNG phase")
	}
	e, _ := s.Nym.Get(t.Key).(*NymEntry)
	if e == nil {
		return errors.New("reveal: no nym entry")
	}
	if e.Commit == ([32]byte{}) {
		return errors.New("reveal: no commit (or already revealed)")
	}
	if core.H(t.Preimage[:]) != e.Commit {
		return errors.New("reveal: preimage does not match commit")
	}
	rr, ok := s.nymRank(t.Key)
	if !ok {
		return errors.New("reveal: revealer has no rank")
	}
	N := s.nymN()
	tgt := new(big.Int).SetBytes(t.Preimage[:])
	tgt.Add(tgt, new(big.Int).SetUint64(rr))
	tgt.Mod(tgt, new(big.Int).SetUint64(N))
	targetKey, _ := s.Nym.Select(tgt.Uint64())
	te, _ := s.Nym.Get(targetKey).(*NymEntry)
	if te == nil {
		return errors.New("reveal: target lookup failed")
	}
	te.SeedHits++
	s.Nym.Refresh(targetKey)
	s.XorAcc[targetKey] = core.Xor32(s.XorAcc[targetKey], t.Preimage)
	e.Commit = [32]byte{} // consumed; the locked index is untouched (no Touch)
	s.Nym.Refresh(t.Key)
	return nil
}

// ========================================================= utxo layer

// Prune — expired UTXOs anytime (the producer collects); stale
// identity entries after the commit deadline.
type Prune struct {
	TrieID byte
	Keys   [][32]byte
}

func (t *Prune) Body() []byte {
	var w core.Buf
	w.U8(core.OpPrune)
	w.U8(t.TrieID)
	w.U32(uint32(len(t.Keys)))
	for i := range t.Keys {
		w.Bytes(t.Keys[i][:])
	}
	return w.B
}
func (t *Prune) TxID() [32]byte { return core.H(t.Body()) }

func (s *State) applyPrune(t *Prune, T uint64) (*big.Int, error) {
	switch t.TrieID {
	case TrieUTXO:
		return core.PruneCoins(s.UTXO, t.Keys, T)
	case TrieNextNym, TrieNextInvite:
		if inEvent(s, T) {
			return nil, errors.New("prune: identity entries prunable after the commit deadline")
		}
		trie := s.mixTrie(t.TrieID)
		for _, k := range t.Keys {
			e := trie.Get(k)
			if e == nil {
				return nil, errors.New("prune: entry missing")
			}
			if e.Counted() {
				return nil, errors.New("prune: entry is not stale")
			}
			if _, err := trie.Remove(k); err != nil {
				return nil, err
			}
		}
		return new(big.Int), nil
	}
	return nil, errors.New("prune: bad trie id")
}

// ========================================================== dispatch

func (s *State) ApplyTxs(txs []core.Tx, T uint64) (*big.Int, error) {
	fees := new(big.Int)
	for i, tx := range txs {
		var (
			f   *big.Int
			err error
		)
		switch t := tx.(type) {
		case *Verify:
			err = s.applyVerify(t, T)
		case *Judge:
			err = s.applyJudge(t, T)
		case *JudgeNym:
			err = s.applyJudgeNym(t, T)
		case *Use:
			err = s.applyUse(t, T)
		case *core.Mix:
			err = s.applyMix(t, T)
		case *CommitN:
			err = s.applyCommitN(t, T)
		case *CommitE:
			err = s.applyCommitE(t, T)
		case *Vote:
			err = s.applyVote(t, T)
		case *Reveal:
			err = s.applyReveal(t, T)
		case *core.Transfer:
			f, err = core.ApplyTransfer(s.UTXO, t, T, s.NormTime)
		case *Prune:
			f, err = s.applyPrune(t, T)
		default:
			err = errors.New("unknown transaction type")
		}
		if err != nil {
			return nil, fmt.Errorf("tx %d: %w", i, err)
		}
		if f != nil {
			fees.Add(fees, f)
		}
	}
	return fees, nil
}

func maxU64(a, b uint64) uint64 {
	if a > b {
		return a
	}
	return b
}
hierarchy/population module: hierarchical attestation1306 lines
consensus.go311
package hierarchy

// consensus.go — hierarchy's chain: per-block randomness that
// evolves with every onion reveal (rand' = rand XOR H(nonce)), two
// election tries with a yearly swap, no phases, fork choice by
// fewest skips, finality at the previous election-period boundary.

import (
	"crypto/ed25519"
	"errors"
	"fmt"
	"math/big"

	"peoplecoin/core"
)

// ElectionPeriodSeconds — hierarchy elects yearly.
const ElectionPeriodSeconds = core.SecondsPerYear

type State struct {
	Genesis  uint64
	NormTime uint64 // the era's norm epoch — fixed for the deployment
	Seq      uint64
	Time     uint64
	LastHash [32]byte

	People       *PeopleTree
	Election     *core.Trie // active: selection + peeling
	NextElection *core.Trie // building: VoteClaim + Mix + Commit
	UTXO         *core.UTXOSet

	Rand [32]byte // evolves per block: rand ^= H(nonce)
}

func (s *State) Clone() *State {
	c := *s
	c.People = s.People.Clone()
	c.Election = s.Election.Clone()
	c.NextElection = s.NextElection.Clone()
	c.UTXO = s.UTXO.Clone()
	return &c
}

func (s *State) electionPeriod(T uint64) uint64 {
	return (T - s.Genesis) / ElectionPeriodSeconds
}
func (s *State) periodStart(T uint64) uint64 {
	return s.Genesis + s.electionPeriod(T)*ElectionPeriodSeconds
}
func (s *State) seqAt(T uint64) uint64 { return (T - s.Genesis) / core.SlotSeconds }

// advancePeriods swaps the election tries at every yearly boundary
// crossed: next_election becomes active, a fresh next starts.
func advancePeriods(s *State, from, to uint64) {
	for b := s.periodStart(from) + ElectionPeriodSeconds; b <= to; b += ElectionPeriodSeconds {
		s.Election = s.NextElection
		s.NextElection = core.NewElectionTrie()
	}
}

// selected — rand mod count into the ACTIVE election trie; returns
// the entry key so the peel writes back into the trie itself.
func selected(s *State) ([32]byte, [32]byte, error) {
	n := s.Election.Count()
	if n == 0 {
		return core.Zero32, core.Zero32, errors.New("empty election: chain halted")
	}
	i := new(big.Int).SetBytes(s.Rand[:])
	i.Mod(i, new(big.Int).SetUint64(n))
	k, _ := s.Election.Select(i.Uint64())
	e, _ := s.Election.Get(k).(*core.ElectionEntry)
	return k, e.Commit, nil
}

func preProcess(prev *State, T uint64) (*State, [32]byte, [32]byte, error) {
	if T%core.SlotSeconds != 0 || T <= prev.Time {
		return nil, core.Zero32, core.Zero32, errors.New("preprocess: time must land on a future slot")
	}
	ns := prev.Clone()
	advancePeriods(ns, prev.Time, T)
	k, sel, err := selected(ns)
	if err != nil {
		return nil, core.Zero32, core.Zero32, err
	}
	return ns, k, sel, nil
}

// storePeel writes the commit's successor into the active trie —
// the shared peel, nothing else. A zero successor (onion spent)
// drops the entry from the count via Touch; rand mod count adapts.
func storePeel(ns *State, key [32]byte, next [32]byte) {
	ns.Election.Get(key).(*core.ElectionEntry).Commit = next
	ns.Election.Touch(key)
}

// evolveRand is hierarchy's module RNG (interface point 3): each
// reveal's nonce feeds the per-block randomness. Separate from the
// peel — the peel is the election's shared role.
func evolveRand(ns *State, nonce []byte) {
	ns.Rand = core.Xor32(ns.Rand, core.H(nonce))
}

func finish(ns *State, txs []core.Tx, T uint64, validator core.PubKey) error {
	fees, err := ns.ApplyTxs(txs, T)
	if err != nil {
		return err
	}
	if fees.Sign() > 0 {
		feeID := core.H([]byte{0xFD}, core.U64be(ns.seqAt(T)))
		if err := ns.UTXO.Insert(core.CoinKey(feeID, 0), &core.CoinEntry{
			Owner: validator, Amount: fees,
			Norm: core.Normalize(fees, T, ns.NormTime), Time: T,
		}); err != nil {
			return fmt.Errorf("fee mint: %w", err)
		}
	}
	return nil
}

type Header struct {
	Seq       uint64
	Time      uint64
	PeopleT   [32]byte
	ElectionT [32]byte
	NextElT   [32]byte
	UTXOT     [32]byte
	Rand      [32]byte
	Prev      [32]byte
	Validator core.PubKey
	Nonce     []byte // onion reveal: 64 bytes, 32 at the deepest layer
	Sig       core.Sig
}

func (h *Header) encode(withSig bool) []byte {
	var w core.Buf
	w.U8(OpHeader)
	w.U64(h.Seq)
	w.U64(h.Time)
	w.Bytes(h.PeopleT[:])
	w.Bytes(h.ElectionT[:])
	w.Bytes(h.NextElT[:])
	w.Bytes(h.UTXOT[:])
	w.Bytes(h.Rand[:])
	w.Bytes(h.Prev[:])
	w.Bytes(h.Validator[:])
	w.U8(byte(len(h.Nonce)))
	w.Bytes(h.Nonce)
	if withSig {
		w.Bytes(h.Sig[:])
	}
	return w.B
}
func (h *Header) SigHash() [32]byte { return core.H(h.encode(false)) }
func (h *Header) Hash() [32]byte    { return core.H(h.encode(true)) }

type Block struct {
	Header Header
	Txs    []core.Tx
}

func BuildBlock(prev *State, txs []core.Tx, T uint64, valPriv ed25519.PrivateKey,
	nonces map[[32]byte][]byte) (*Block, *State, error) {
	ns, key, sel, err := preProcess(prev, T)
	if err != nil {
		return nil, nil, err
	}
	nonce, ok := nonces[sel]
	if !ok {
		return nil, nil, errors.New("build: selected commit is not ours (or consumed)")
	}
	pub := core.Pub(valPriv)
	next, ok := core.VerifyPeel(sel, pub, nonce)
	if !ok {
		return nil, nil, errors.New("build: reveal material does not match the commit")
	}
	storePeel(ns, key, next)
	evolveRand(ns, nonce)
	if err := finish(ns, txs, T, pub); err != nil {
		return nil, nil, err
	}
	h := Header{
		Seq: prev.seqAt(T), Time: T,
		PeopleT: ns.People.Hash(), ElectionT: ns.Election.Root(),
		NextElT: ns.NextElection.Root(), UTXOT: ns.UTXO.Root(),
		Rand: ns.Rand, Prev: prev.LastHash, Validator: pub, Nonce: nonce,
	}
	sh := h.SigHash()
	h.Sig = core.Sign(valPriv, sh[:])
	ns.Seq, ns.Time, ns.LastHash = h.Seq, T, h.Hash()
	return &Block{Header: h, Txs: txs}, ns, nil
}

func VerifyBlock(prev *State, b *Block) (*State, error) {
	h := &b.Header
	if h.Seq != prev.seqAt(h.Time) {
		return nil, errors.New("verify: seq is not slots-since-genesis")
	}
	if h.Prev != prev.LastHash {
		return nil, errors.New("verify: prev hash mismatch")
	}
	ns, key, sel, err := preProcess(prev, h.Time)
	if err != nil {
		return nil, fmt.Errorf("verify: %w", err)
	}
	next, ok := core.VerifyPeel(sel, h.Validator, h.Nonce)
	if !ok {
		return nil, errors.New("verify: reveal does not peel the selected commit")
	}
	storePeel(ns, key, next)
	evolveRand(ns, h.Nonce)
	if err := finish(ns, b.Txs, h.Time, h.Validator); err != nil {
		return nil, fmt.Errorf("verify: %w", err)
	}
	sh := h.SigHash()
	if !core.VerifySig(h.Validator, sh[:], h.Sig) {
		return nil, errors.New("verify: bad validator signature")
	}
	if ns.Rand != h.Rand {
		return nil, errors.New("verify: rand does not evolve as rand ^= H(nonce)")
	}
	roots := [][2][32]byte{
		{ns.People.Hash(), h.PeopleT}, {ns.Election.Root(), h.ElectionT},
		{ns.NextElection.Root(), h.NextElT}, {ns.UTXO.Root(), h.UTXOT},
	}
	for i, p := range roots {
		if p[0] != p[1] {
			return nil, fmt.Errorf("verify: root %d mismatch", i)
		}
	}
	ns.Seq, ns.Time, ns.LastHash = h.Seq, h.Time, h.Hash()
	return ns, nil
}

// ============================================================ chain

type GenesisBootstrap struct {
	Key    [32]byte
	Commit [32]byte // onion top
}

type GenesisConfig struct {
	T0        uint64
	Root      core.PubKey
	Persons   []core.PubKey
	Bootstrap []GenesisBootstrap // initial active-election entries
	Rand      [32]byte
}

type Chain struct {
	genesisState *State
	Blocks       []*Block
	State        *State
}

func NewChain(cfg GenesisConfig) (*Chain, error) {
	if cfg.T0%core.SlotSeconds != 0 {
		return nil, errors.New("genesis: time must sit on the slot grid")
	}
	if len(cfg.Bootstrap) == 0 {
		return nil, errors.New("genesis: empty bootstrap (chain halted at birth)")
	}
	s := &State{
		Genesis: cfg.T0, NormTime: core.NormTimeFor(cfg.T0), Time: cfg.T0,
		People:   NewPeopleTree(cfg.Root, false, cfg.T0),
		Election: core.NewElectionTrie(), NextElection: core.NewElectionTrie(),
		UTXO: core.NewUTXOSet(), Rand: cfg.Rand,
	}
	for _, p := range cfg.Persons {
		if err := s.People.Add(cfg.Root, p, true, cfg.T0); err != nil {
			return nil, err
		}
	}
	for _, b := range cfg.Bootstrap {
		if b.Commit == core.Zero32 {
			return nil, errors.New("genesis: zero bootstrap commit is unprovable")
		}
		e := &core.ElectionEntry{MixCount: 1, Commit: b.Commit}
		if err := s.Election.Insert(b.Key, e); err != nil {
			return nil, err
		}
	}
	gh := Header{Time: cfg.T0, PeopleT: s.People.Hash(),
		ElectionT: s.Election.Root(), UTXOT: s.UTXO.Root(), Rand: cfg.Rand,
		Nonce: cfg.Rand[:]}
	s.LastHash = gh.Hash()
	return &Chain{genesisState: s.Clone(), State: s}, nil
}

func (c *Chain) Produce(txs []core.Tx, T uint64, priv ed25519.PrivateKey,
	nonces map[[32]byte][]byte) (*Block, error) {
	b, ns, err := BuildBlock(c.State, txs, T, priv, nonces)
	if err != nil {
		return nil, err
	}
	c.Blocks = append(c.Blocks, b)
	c.State = ns
	return b, nil
}

// Replay verifies the whole chain from genesis.
func (c *Chain) Replay() (*State, error) {
	s := c.genesisState.Clone()
	for i, b := range c.Blocks {
		ns, err := VerifyBlock(s, b)
		if err != nil {
			return nil, fmt.Errorf("replay block %d: %w", i, err)
		}
		s = ns
	}
	return s, nil
}
demo.go223
package hierarchy

// demo.go — a compact hierarchy scenario: an org registers persons,
// UBI accrues continuously, vote claims feed the mixed onion
// election, the yearly boundary swaps the tries, and rand evolves
// with every reveal. Ends with a full replay.

import (
	"crypto/ed25519"
	"crypto/rand"
	"fmt"
	"math/big"
	"strings"

	"peoplecoin/core"
)

type valSvc struct {
	name   string
	priv   ed25519.PrivateKey
	pub    core.PubKey
	nonces map[[32]byte][]byte
}

func newVal(name string) *valSvc {
	priv, pub := core.GenKey()
	return &valSvc{name: name, priv: priv, pub: pub, nonces: map[[32]byte][]byte{}}
}

func (v *valSvc) register(l core.OnionLayer) {
	v.nonces[core.H(v.pub[:], l.Nonce)] = l.Nonce
}

func mkOnion(vals []*valSvc, depth int) [32]byte {
	seq := make([]core.PubKey, depth)
	for i := range seq {
		seq[i] = vals[i%len(vals)].pub
	}
	commit, layers := core.BuildOnion(seq, func(p []byte) {
		if _, err := rand.Read(p); err != nil {
			panic(err)
		}
	})
	for _, l := range layers {
		for _, v := range vals {
			if v.pub == l.Validator {
				v.register(l)
			}
		}
	}
	return commit
}

func produceAny(c *Chain, vals []*valSvc, T uint64, txs []core.Tx) (*valSvc, error) {
	for _, v := range vals {
		if _, err := c.Produce(txs, T, v.priv, v.nonces); err == nil {
			return v, nil
		} else if !strings.Contains(err.Error(), "not ours") {
			return nil, err
		}
	}
	return nil, fmt.Errorf("no validator can produce slot at %d", T)
}

func sig(priv ed25519.PrivateKey, id [32]byte) core.Sig { return core.Sign(priv, id[:]) }

func fmtTok(v *big.Int) string {
	q := new(big.Int).Quo(v, core.TOKEN)
	r := new(big.Int).Mod(v, core.TOKEN)
	return fmt.Sprintf("%s.%04s", q, new(big.Int).Quo(r, big.NewInt(1e12)))
}

// RunDemo builds and replays the hierarchy chain.
func RunDemo() (*Chain, error) {
	c, err := runDemo()
	if err != nil {
		fmt.Println("DEMO FAILED:", err)
	}
	return c, err
}

func runDemo() (*Chain, error) {
	day := uint64(24 * 3600)
	T0 := uint64(14) * core.NormPeriod
	v1, v2 := newVal("v1"), newVal("v2")
	rootPriv, rootPub := core.GenKey()
	alicePriv, alice := core.GenKey()
	bobPriv, bob := core.GenKey()

	chain, err := NewChain(GenesisConfig{
		T0: T0, Root: rootPub, Persons: []core.PubKey{alice, bob},
		Bootstrap: []GenesisBootstrap{{Key: core.H([]byte("boot-0")), Commit: mkOnion([]*valSvc{v1, v2}, 16)}},
		Rand:      core.H([]byte("hierarchy genesis rand")),
	})
	if err != nil {
		return nil, err
	}
	fmt.Printf("genesis: org + %d persons, population %d, rand %s\n",
		2, chain.State.People.Population(), core.ShortHash(chain.State.Rand))

	at := func(T uint64, txs []core.Tx, note string) error {
		v, err := produceAny(chain, []*valSvc{v1, v2}, T, txs)
		if err != nil {
			return fmt.Errorf("%s: %w", note, err)
		}
		fmt.Printf("  slot %-8d %-34s producer %s  rand %s\n",
			(T-T0)/core.SlotSeconds, note, v.name, core.ShortHash(chain.State.Rand))
		return nil
	}

	// Day 30: alice claims a month of accrual.
	u1 := &UBIClaim{Key: alice, Seq: T0}
	u1.Sig = sig(alicePriv, u1.TxID())
	if err := at(T0+30*day, []core.Tx{u1}, "alice UBIClaim"); err != nil {
		return nil, err
	}
	fmt.Printf("    alice accrued %s TOKEN over 30 days\n",
		fmtTok(chain.State.UTXO.Get(core.CoinKey(u1.TxID(), 0)).Amount))

	// Day 60: org S under root; carol joins under S.
	orgSPriv, orgS := core.GenKey()
	carolPriv, carol := core.GenKey()
	a1 := &Add{Parent: rootPub, Child: orgS, Leaf: false}
	a1.ParentSig, a1.ChildSig = sig(rootPriv, a1.TxID()), sig(orgSPriv, a1.TxID())
	a2 := &Add{Parent: orgS, Child: carol, Leaf: true}
	a2.ParentSig, a2.ChildSig = sig(orgSPriv, a2.TxID()), sig(carolPriv, a2.TxID())
	if err := at(T0+60*day, []core.Tx{a1, a2}, "Add org S + carol"); err != nil {
		return nil, err
	}
	fmt.Printf("    population %d (tree %s)\n",
		chain.State.People.Population(), core.ShortHash(chain.State.People.Hash()))

	// Day 120: vote claims for period 0 → next_election.
	var vcs []core.Tx
	for _, p := range []struct {
		priv ed25519.PrivateKey
		pub  core.PubKey
	}{{alicePriv, alice}, {bobPriv, bob}, {carolPriv, carol}} {
		vc := &VoteClaim{Key: p.pub, Period: 0}
		vc.Sig = sig(p.priv, vc.TxID())
		vcs = append(vcs, vc)
	}
	if err := at(T0+120*day, vcs, "3× VoteClaim"); err != nil {
		return nil, err
	}

	// Day 121: mix the three vote entries to fresh keys.
	ins := [][32]byte{[32]byte(alice), [32]byte(bob), [32]byte(carol)}
	privs := []ed25519.PrivateKey{alicePriv, bobPriv, carolPriv}
	var eprivs []ed25519.PrivateKey
	mix := &core.Mix{TrieID: TrieElection}
	for range ins {
		np, npub := core.GenKey()
		eprivs = append(eprivs, np)
		mix.Outputs = append(mix.Outputs, core.MixOutput{Key: [32]byte(npub), MixCount: 1})
	}
	mix.Inputs = ins
	mid := mix.TxID()
	for _, p := range privs {
		mix.Sigs = append(mix.Sigs, core.Sign(p, mid[:]))
	}
	if err := at(T0+121*day, []core.Tx{mix}, "Mix(next_election)"); err != nil {
		return nil, err
	}

	// Day 122: onion commits; carol moves to root; bob rekeys.
	var cms []core.Tx
	pick := []*valSvc{v1, v2}
	for i, ep := range eprivs {
		cm := &Commit{Key: core.Pub(ep), Commit: mkOnion([]*valSvc{pick[i%2]}, 16)}
		cm.Sig = sig(ep, cm.TxID())
		cms = append(cms, cm)
	}
	mv := &Move{Child: carol, NewParent: rootPub}
	mv.ChildSig, mv.NewParentSig = sig(carolPriv, mv.TxID()), sig(rootPriv, mv.TxID())
	bob2Priv, bob2 := core.GenKey()
	rk := &Rekey{Old: bob, New: bob2, Nonce: 0}
	rk.OldSig, rk.NewSig = sig(bobPriv, rk.TxID()), sig(bob2Priv, rk.TxID())
	if err := at(T0+122*day, append(cms, mv, rk), "3× Commit + Move + Rekey"); err != nil {
		return nil, err
	}

	// Cross the yearly boundary: next_election becomes active.
	yr := ElectionPeriodSeconds
	if err := at(T0+yr+core.SlotSeconds, nil, "first block of period 1"); err != nil {
		return nil, err
	}
	fmt.Printf("    election swapped: active count %d\n", chain.State.Election.Count())

	// dave joins and immediately leaves — accrued UBI auto-minted.
	davePriv, dave := core.GenKey()
	a3 := &Add{Parent: rootPub, Child: dave, Leaf: true}
	a3.ParentSig, a3.ChildSig = sig(rootPriv, a3.TxID()), sig(davePriv, a3.TxID())
	if err := at(T0+yr+10*day, []core.Tx{a3}, "Add dave"); err != nil {
		return nil, err
	}
	lv := &Leave{Child: dave}
	lv.ChildSig = sig(davePriv, lv.TxID())
	if err := at(T0+yr+40*day, []core.Tx{lv}, "dave Leave (auto-mint)"); err != nil {
		return nil, err
	}
	auto := chain.State.UTXO.Get(core.CoinKey(lv.TxID(), 0))
	fmt.Printf("    dave's accrual auto-minted: %s TOKEN, population %d\n",
		fmtTok(auto.Amount), chain.State.People.Population())

	// bob2 claims with the rekeyed identity (LastUBI followed).
	n := chain.State.People.Get(bob2)
	u2 := &UBIClaim{Key: bob2, Seq: n.LastUBI}
	u2.Sig = sig(bob2Priv, u2.TxID())
	if err := at(T0+yr+41*day, []core.Tx{u2}, "bob2 UBIClaim (post-rekey)"); err != nil {
		return nil, err
	}

	// Full replay.
	tip, err := chain.Replay()
	if err != nil {
		return nil, err
	}
	fmt.Printf("replay: ok (%d blocks, tip %s), population %d, supply %s TOKEN\n",
		len(chain.Blocks), core.ShortHash(tip.LastHash), tip.People.Population(),
		fmtTok(core.ValueAt(tip.UTXO.Sum(), tip.Time, tip.NormTime)))
	return chain, nil
}
hier_test.go177
package hierarchy

import (
	"math/big"
	"testing"

	"peoplecoin/core"
)

// TestTreeOps — add/move/rekey/remove with aggregates, cycle guard
// and canonical hashing.
func TestTreeOps(t *testing.T) {
	_, root := core.GenKey()
	pt := NewPeopleTree(root, false, 1000)
	_, a := core.GenKey()
	_, b := core.GenKey()
	_, s := core.GenKey()
	if err := pt.Add(root, a, true, 1000); err != nil {
		t.Fatal(err)
	}
	if err := pt.Add(root, s, false, 1000); err != nil {
		t.Fatal(err)
	}
	if err := pt.Add(s, b, true, 1000); err != nil {
		t.Fatal(err)
	}
	if pt.Population() != 2 {
		t.Fatalf("population %d, want 2", pt.Population())
	}
	if err := pt.Add(a, b, true, 1000); err == nil {
		t.Fatal("person accepted children")
	}
	if err := pt.Move(s, s); err == nil {
		t.Fatal("self-move accepted")
	}
	if err := pt.Move(root, s); err == nil {
		t.Fatal("root moved / cycle accepted")
	}
	h1 := pt.Hash()
	if err := pt.Move(b, root); err != nil {
		t.Fatal(err)
	}
	if pt.Hash() == h1 {
		t.Fatal("move did not change the tree hash")
	}
	_, b2 := core.GenKey()
	if err := pt.Rekey(b, b2); err != nil {
		t.Fatal(err)
	}
	if pt.Get(b) != nil || pt.Get(b2) == nil || pt.Get(b2).Nonce != 1 {
		t.Fatal("rekey did not transfer the node")
	}
	removed, err := pt.Remove(s)
	if err != nil || len(removed) != 0 {
		t.Fatalf("remove of empty org: %v (%d persons)", err, len(removed))
	}
	if pt.Population() != 2 {
		t.Fatal("population after removing empty org")
	}
}

// TestClaimable — the continuous formula matches first principles:
// one year of accrual on contribution 1 is TOKEN × (1 − 0.8).
func TestClaimable(t *testing.T) {
	acc := ClaimableAt(1, 0, core.SecondsPerYear)
	want := new(big.Int).Quo(new(big.Int).Mul(core.TOKEN, big.NewInt(2)), big.NewInt(10))
	diff := new(big.Int).Sub(acc, want)
	diff.Abs(diff)
	lim := new(big.Int).Quo(core.TOKEN, big.NewInt(100_000))
	if diff.Cmp(lim) > 0 {
		t.Fatalf("claimable(1yr) = %s, want ≈ %s", acc, want)
	}
	if ClaimableAt(1, 500, 500).Sign() != 0 {
		t.Fatal("zero elapsed must accrue zero")
	}
}

// TestHierChain — rand evolves as rand ^= H(nonce) per block, a
// 1-layer bootstrap is consumed (count drops), the yearly swap
// activates committed entries, and the whole chain replays.
func TestHierChain(t *testing.T) {
	T0 := uint64(14) * core.NormPeriod
	v1, v2 := newVal("v1"), newVal("v2")
	rootPriv, root := core.GenKey()
	alicePriv, alice := core.GenKey()
	bobPriv, bob := core.GenKey()
	_ = rootPriv
	oneShot := mkOnion([]*valSvc{v2}, 1) // consumed after one reveal
	deep := mkOnion([]*valSvc{v1}, 12)
	chain, err := NewChain(GenesisConfig{
		T0: T0, Root: root, Persons: []core.PubKey{alice, bob},
		Bootstrap: []GenesisBootstrap{
			{Key: core.H([]byte("deep")), Commit: deep},
			{Key: core.H([]byte("one")), Commit: oneShot},
		},
		Rand: core.H([]byte("t rand")),
	})
	if err != nil {
		t.Fatal(err)
	}
	prevRand := chain.State.Rand
	produce := func(slot uint64, txs []core.Tx) {
		t.Helper()
		if _, err := produceAny(chain, []*valSvc{v1, v2}, T0+slot*core.SlotSeconds, txs); err != nil {
			t.Fatal(err)
		}
		b := chain.Blocks[len(chain.Blocks)-1]
		if chain.State.Rand != core.Xor32(prevRand, core.H(b.Header.Nonce)) {
			t.Fatal("rand did not evolve as rand ^= H(nonce)")
		}
		prevRand = chain.State.Rand
	}
	// Produce until the one-shot entry has been consumed.
	consumed := false
	for slot := uint64(1); slot < 60 && !consumed; slot++ {
		if _, err := produceAny(chain, []*valSvc{v1, v2}, T0+slot*core.SlotSeconds, nil); err != nil {
			continue
		}
		b := chain.Blocks[len(chain.Blocks)-1]
		if chain.State.Rand != core.Xor32(prevRand, core.H(b.Header.Nonce)) {
			t.Fatal("rand did not evolve as rand ^= H(nonce)")
		}
		prevRand = chain.State.Rand
		if chain.State.Election.Count() == 1 {
			consumed = true
		}
	}
	if !consumed {
		t.Fatal("one-shot onion never consumed in 60 slots")
	}
	// Vote claims + mix + commits before the boundary.
	var vcs []core.Tx
	privs := []ed25519pair{{alicePriv, alice}, {bobPriv, bob}}
	for _, p := range privs {
		vc := &VoteClaim{Key: p.pub, Period: 0}
		vc.Sig = core.Sign(p.priv, mustID(vc))
		vcs = append(vcs, vc)
	}
	produce(100, vcs)
	mix := &core.Mix{TrieID: TrieElection,
		Inputs: [][32]byte{[32]byte(alice), [32]byte(bob)}}
	var eprivs []ed25519pair
	for i := 0; i < 2; i++ {
		np, npub := core.GenKey()
		eprivs = append(eprivs, ed25519pair{np, npub})
		mix.Outputs = append(mix.Outputs, core.MixOutput{Key: [32]byte(npub), MixCount: 1})
	}
	mid := mix.TxID()
	for _, p := range privs {
		mix.Sigs = append(mix.Sigs, core.Sign(p.priv, mid[:]))
	}
	produce(101, []core.Tx{mix})
	var cms []core.Tx
	for i, ep := range eprivs {
		vv := []*valSvc{v1, v2}[i]
		cm := &Commit{Key: ep.pub, Commit: mkOnion([]*valSvc{vv}, 12)}
		cm.Sig = core.Sign(ep.priv, mustID(cm))
		cms = append(cms, cm)
	}
	produce(102, cms)
	// Cross the yearly boundary: the two committed entries activate.
	yearSlots := ElectionPeriodSeconds / core.SlotSeconds
	produce(yearSlots+1, nil)
	if chain.State.Election.Count() != 2 {
		t.Fatalf("post-swap active count %d, want 2", chain.State.Election.Count())
	}
	if _, err := chain.Replay(); err != nil {
		t.Fatal(err)
	}
}

type ed25519pair struct {
	priv []byte
	pub  core.PubKey
}

func mustID(tx core.Tx) []byte { id := tx.TxID(); return id[:] }
tree.go232
package hierarchy

// tree.go — the People Tree: hierarchical attestation. Your
// superior attested your identity; maps onto civil registration.
// Every node is a person (leaf) or a group/organization. tree_count
// aggregates persons upward; population = root.tree_count.

import (
	"errors"
	"math/big"

	"peoplecoin/core"
)

type Node struct {
	Key      core.PubKey
	Leaf     bool // person
	Children []core.PubKey
	Nonce    uint64 // rekey counter
	LastUBI  uint64
	LastVote uint64 // last claimed election period + 1 (0 = never)
}

func (n *Node) clone() *Node {
	c := *n
	c.Children = append([]core.PubKey(nil), n.Children...)
	return &c
}

type PeopleTree struct {
	Root   core.PubKey
	nodes  map[core.PubKey]*Node
	parent map[core.PubKey]core.PubKey
}

func NewPeopleTree(root core.PubKey, rootIsPerson bool, t0 uint64) *PeopleTree {
	pt := &PeopleTree{Root: root, nodes: map[core.PubKey]*Node{}, parent: map[core.PubKey]core.PubKey{}}
	pt.nodes[root] = &Node{Key: root, Leaf: rootIsPerson, LastUBI: t0}
	return pt
}

func (pt *PeopleTree) Get(k core.PubKey) *Node { return pt.nodes[k] }
func (pt *PeopleTree) Len() int                { return len(pt.nodes) }

// Population: persons in the whole tree (root.tree_count).
func (pt *PeopleTree) Population() uint64 {
	var walk func(k core.PubKey) uint64
	walk = func(k core.PubKey) uint64 {
		n := pt.nodes[k]
		c := uint64(0)
		if n.Leaf {
			c = 1
		}
		for _, ch := range n.Children {
			c += walk(ch)
		}
		return c
	}
	return walk(pt.Root)
}

// Add — parent adds child with consent (both signatures verified by
// the transaction layer).
func (pt *PeopleTree) Add(parent, child core.PubKey, leaf bool, t0 uint64) error {
	p := pt.nodes[parent]
	if p == nil {
		return errors.New("tree: parent missing")
	}
	if p.Leaf {
		return errors.New("tree: a person cannot hold children")
	}
	if pt.nodes[child] != nil {
		return errors.New("tree: key already in the tree")
	}
	pt.nodes[child] = &Node{Key: child, Leaf: leaf, LastUBI: t0}
	pt.parent[child] = parent
	p.Children = append(p.Children, child)
	return nil
}

// Remove — detach child and its entire subtree; returns the removed
// person nodes so accrued UBI can be auto-minted.
func (pt *PeopleTree) Remove(child core.PubKey) ([]*Node, error) {
	par, ok := pt.parent[child]
	if !ok {
		return nil, errors.New("tree: node missing or is the root")
	}
	p := pt.nodes[par]
	for i, c := range p.Children {
		if c == child {
			p.Children = append(p.Children[:i], p.Children[i+1:]...)
			break
		}
	}
	var removed []*Node
	var walk func(k core.PubKey)
	walk = func(k core.PubKey) {
		n := pt.nodes[k]
		for _, ch := range n.Children {
			walk(ch)
		}
		if n.Leaf {
			removed = append(removed, n)
		}
		delete(pt.nodes, k)
		delete(pt.parent, k)
	}
	walk(child)
	return removed, nil
}

// Move — child moves to a new parent with consent; state preserved.
func (pt *PeopleTree) Move(child, newParent core.PubKey) error {
	if _, ok := pt.parent[child]; !ok {
		return errors.New("tree: node missing or is the root")
	}
	np := pt.nodes[newParent]
	if np == nil || np.Leaf {
		return errors.New("tree: new parent missing or is a person")
	}
	// No cycles: newParent must not sit inside child's subtree.
	for k := newParent; ; {
		if k == child {
			return errors.New("tree: move would create a cycle")
		}
		par, ok := pt.parent[k]
		if !ok {
			break
		}
		k = par
	}
	old := pt.nodes[pt.parent[child]]
	for i, c := range old.Children {
		if c == child {
			old.Children = append(old.Children[:i], old.Children[i+1:]...)
			break
		}
	}
	pt.parent[child] = newParent
	np.Children = append(np.Children, child)
	return nil
}

// Rekey — the node changes key; nonce increments (replay guard),
// children and state follow.
func (pt *PeopleTree) Rekey(old, new core.PubKey) error {
	n := pt.nodes[old]
	if n == nil {
		return errors.New("tree: node missing")
	}
	if pt.nodes[new] != nil {
		return errors.New("tree: new key already in the tree")
	}
	n.Key = new
	n.Nonce++
	pt.nodes[new] = n
	delete(pt.nodes, old)
	if par, ok := pt.parent[old]; ok {
		pt.parent[new] = par
		delete(pt.parent, old)
		ps := pt.nodes[par]
		for i, c := range ps.Children {
			if c == old {
				ps.Children[i] = new
				break
			}
		}
	} else {
		pt.Root = new
	}
	for _, ch := range n.Children {
		pt.parent[ch] = new
	}
	return nil
}

// Hash folds the tree deterministically: each node hashes its
// fields, its tree_count aggregate and its children's hashes in
// order — the people_tree root for the block header.
func (pt *PeopleTree) Hash() [32]byte {
	var fold func(k core.PubKey) ([32]byte, uint64)
	fold = func(k core.PubKey) ([32]byte, uint64) {
		n := pt.nodes[k]
		var w core.Buf
		w.U8(0x50)
		w.Bytes(n.Key[:])
		w.Boolb(n.Leaf)
		w.U64(n.Nonce)
		w.U64(n.LastUBI)
		w.U64(n.LastVote)
		count := uint64(0)
		if n.Leaf {
			count = 1
		}
		var kids core.Buf
		for _, ch := range n.Children {
			h, c := fold(ch)
			count += c
			kids.Bytes(h[:])
		}
		w.U64(count)
		w.U32(uint32(len(n.Children)))
		w.Bytes(kids.B)
		return core.H(w.B), count
	}
	h, _ := fold(pt.Root)
	return h
}

func (pt *PeopleTree) Clone() *PeopleTree {
	c := &PeopleTree{Root: pt.Root, nodes: map[core.PubKey]*Node{}, parent: map[core.PubKey]core.PubKey{}}
	for k, n := range pt.nodes {
		c.nodes[k] = n.clone()
	}
	for k, v := range pt.parent {
		c.parent[k] = v
	}
	return c
}

// ClaimableAt — the hierarchy UBI: continuous accrual since the
// last claim. contribution × TOKEN × (1 − decay^(T − last_ubi)).
func ClaimableAt(contribution uint64, lastUBI, T uint64) *big.Int {
	if T <= lastUBI {
		return new(big.Int)
	}
	p := core.DecayPow(T - lastUBI)
	acc := new(big.Int).Sub(core.Scale, p)
	acc.Mul(acc, core.TOKEN)
	acc.Quo(acc, core.Scale)
	return acc.Mul(acc, new(big.Int).SetUint64(contribution))
}
tx.go363
package hierarchy

// tx.go — hierarchy transactions. Tree operations carry the consent
// signatures the spec requires; UBIClaim settles continuous accrual;
// VoteClaim + Mix + Commit feed the shared onion election.

import (
	"errors"
	"fmt"
	"math/big"

	"peoplecoin/core"
)

const (
	OpAdd      byte = 0x60
	OpRemove   byte = 0x61
	OpMove     byte = 0x62
	OpLeave    byte = 0x63
	OpRekey    byte = 0x64
	OpUBIClaim byte = 0x65
	OpVClaim   byte = 0x66
	OpCommit   byte = 0x67
	OpHeader   byte = 0xF1

	TrieElection byte = 3 // Mix routing (matches bitpeople's id)
)

type Add struct {
	Parent, Child core.PubKey
	Leaf          bool
	ParentSig     core.Sig
	ChildSig      core.Sig
}

func (t *Add) Body() []byte {
	var w core.Buf
	w.U8(OpAdd)
	w.Bytes(t.Parent[:])
	w.Bytes(t.Child[:])
	w.Boolb(t.Leaf)
	return w.B
}
func (t *Add) TxID() [32]byte { return core.H(t.Body()) }

type Remove struct {
	Child     core.PubKey
	ParentSig core.Sig
}

func (t *Remove) Body() []byte {
	var w core.Buf
	w.U8(OpRemove)
	w.Bytes(t.Child[:])
	return w.B
}
func (t *Remove) TxID() [32]byte { return core.H(t.Body()) }

type Move struct {
	Child, NewParent core.PubKey
	ChildSig         core.Sig
	NewParentSig     core.Sig
}

func (t *Move) Body() []byte {
	var w core.Buf
	w.U8(OpMove)
	w.Bytes(t.Child[:])
	w.Bytes(t.NewParent[:])
	return w.B
}
func (t *Move) TxID() [32]byte { return core.H(t.Body()) }

type Leave struct {
	Child    core.PubKey
	ChildSig core.Sig
}

func (t *Leave) Body() []byte {
	var w core.Buf
	w.U8(OpLeave)
	w.Bytes(t.Child[:])
	return w.B
}
func (t *Leave) TxID() [32]byte { return core.H(t.Body()) }

type Rekey struct {
	Old, New core.PubKey
	Nonce    uint64 // must match the node's current nonce (replay)
	OldSig   core.Sig
	NewSig   core.Sig
}

func (t *Rekey) Body() []byte {
	var w core.Buf
	w.U8(OpRekey)
	w.Bytes(t.Old[:])
	w.Bytes(t.New[:])
	w.U64(t.Nonce)
	return w.B
}
func (t *Rekey) TxID() [32]byte { return core.H(t.Body()) }

type UBIClaim struct {
	Key core.PubKey
	Seq uint64 // last_ubi at claim time (replay guard)
	Sig core.Sig
}

func (t *UBIClaim) Body() []byte {
	var w core.Buf
	w.U8(OpUBIClaim)
	w.Bytes(t.Key[:])
	w.U64(t.Seq)
	return w.B
}
func (t *UBIClaim) TxID() [32]byte { return core.H(t.Body()) }

type VoteClaim struct {
	Key    core.PubKey
	Period uint64
	Sig    core.Sig
}

func (t *VoteClaim) Body() []byte {
	var w core.Buf
	w.U8(OpVClaim)
	w.Bytes(t.Key[:])
	w.U64(t.Period)
	return w.B
}
func (t *VoteClaim) TxID() [32]byte { return core.H(t.Body()) }

// Commit — sets the onion top on a mixed next_election entry.
type Commit struct {
	Key    core.PubKey
	Commit [32]byte
	Sig    core.Sig
}

func (t *Commit) Body() []byte {
	var w core.Buf
	w.U8(OpCommit)
	w.Bytes(t.Key[:])
	w.Bytes(t.Commit[:])
	return w.B
}
func (t *Commit) TxID() [32]byte { return core.H(t.Body()) }

// ============================================================ apply

func (s *State) applyAdd(t *Add, T uint64) error {
	id := t.TxID()
	if !core.VerifySig(t.Parent, id[:], t.ParentSig) || !core.VerifySig(t.Child, id[:], t.ChildSig) {
		return errors.New("add: consent requires both signatures")
	}
	return s.People.Add(t.Parent, t.Child, t.Leaf, T)
}

func (s *State) applyRemove(t *Remove, T uint64) error {
	par, ok := s.People.parent[t.Child]
	if !ok {
		return errors.New("remove: node missing or is the root")
	}
	id := t.TxID()
	if !core.VerifySig(par, id[:], t.ParentSig) {
		return errors.New("remove: parent signature required")
	}
	removed, err := s.People.Remove(t.Child)
	if err != nil {
		return err
	}
	return s.mintAccrued(removed, id, T)
}

func (s *State) applyLeave(t *Leave, T uint64) error {
	id := t.TxID()
	if !core.VerifySig(t.Child, id[:], t.ChildSig) {
		return errors.New("leave: child signature required")
	}
	removed, err := s.People.Remove(t.Child)
	if err != nil {
		return err
	}
	return s.mintAccrued(removed, id, T)
}

// mintAccrued — "Accrued UBI auto-minted" on removal: each removed
// person's claimable value lands at their key.
func (s *State) mintAccrued(removed []*Node, id [32]byte, T uint64) error {
	for i, n := range removed {
		acc := ClaimableAt(1, n.LastUBI, T)
		if acc.Sign() <= 0 {
			continue
		}
		if err := s.UTXO.Insert(core.CoinKey(id, uint32(i)), &core.CoinEntry{
			Owner: n.Key, Amount: acc,
			Norm: core.Normalize(acc, T, s.NormTime), Time: T,
		}); err != nil {
			return fmt.Errorf("auto-mint: %w", err)
		}
	}
	return nil
}

func (s *State) applyMove(t *Move) error {
	id := t.TxID()
	if !core.VerifySig(t.Child, id[:], t.ChildSig) || !core.VerifySig(t.NewParent, id[:], t.NewParentSig) {
		return errors.New("move: consent requires both signatures")
	}
	return s.People.Move(t.Child, t.NewParent)
}

func (s *State) applyRekey(t *Rekey) error {
	n := s.People.Get(t.Old)
	if n == nil {
		return errors.New("rekey: node missing")
	}
	if n.Nonce != t.Nonce {
		return errors.New("rekey: nonce mismatch (replay)")
	}
	id := t.TxID()
	if !core.VerifySig(t.Old, id[:], t.OldSig) || !core.VerifySig(t.New, id[:], t.NewSig) {
		return errors.New("rekey: both keys must sign")
	}
	return s.People.Rekey(t.Old, t.New)
}

func (s *State) applyUBIClaim(t *UBIClaim, T uint64) error {
	n := s.People.Get(t.Key)
	if n == nil || !n.Leaf {
		return errors.New("ubi: person not in the tree")
	}
	if n.LastUBI != t.Seq {
		return errors.New("ubi: stale claim (replay)")
	}
	id := t.TxID()
	if !core.VerifySig(t.Key, id[:], t.Sig) {
		return errors.New("ubi: bad signature")
	}
	acc := ClaimableAt(1, n.LastUBI, T)
	if acc.Sign() <= 0 {
		return errors.New("ubi: nothing accrued")
	}
	if err := s.UTXO.Insert(core.CoinKey(id, 0), &core.CoinEntry{
		Owner: t.Key, Amount: acc,
		Norm: core.Normalize(acc, T, s.NormTime), Time: T,
	}); err != nil {
		return err
	}
	n.LastUBI = T
	return nil
}

func (s *State) applyVoteClaim(t *VoteClaim, T uint64) error {
	n := s.People.Get(t.Key)
	if n == nil || !n.Leaf {
		return errors.New("voteclaim: person not in the tree")
	}
	period := s.electionPeriod(T)
	// LastVote stores period+1 so the zero value means "never".
	if t.Period != period || n.LastVote > period {
		return errors.New("voteclaim: wrong or already-claimed period")
	}
	id := t.TxID()
	if !core.VerifySig(t.Key, id[:], t.Sig) {
		return errors.New("voteclaim: bad signature")
	}
	if err := s.NextElection.Insert([32]byte(t.Key), core.FreshElection(0)); err != nil {
		return err
	}
	n.LastVote = period + 1
	return nil
}

func (s *State) applyCommit(t *Commit) error {
	e, _ := s.NextElection.Get([32]byte(t.Key)).(*core.ElectionEntry)
	if e == nil {
		return errors.New("commit: no next-election entry at key")
	}
	if e.Commit != core.Zero32 {
		return errors.New("commit: already committed")
	}
	if e.MixCount == 0 {
		return errors.New("commit: entry must be mixed first")
	}
	if t.Commit == core.Zero32 {
		return errors.New("commit: zero commit")
	}
	id := t.TxID()
	if !core.VerifySig(t.Key, id[:], t.Sig) {
		return errors.New("commit: bad signature")
	}
	e.Commit = t.Commit
	s.NextElection.Touch([32]byte(t.Key))
	return nil
}

// ApplyTxs — no phases: hierarchy operations are continuous.
func (s *State) ApplyTxs(txs []core.Tx, T uint64) (*big.Int, error) {
	fees := new(big.Int)
	seen := map[[32]byte]struct{}{}
	for _, tx := range txs {
		id := tx.TxID()
		if _, dup := seen[id]; dup {
			return nil, errors.New("apply: duplicate tx in block")
		}
		seen[id] = struct{}{}
		var err error
		var f *big.Int
		switch t := tx.(type) {
		case *Add:
			err = s.applyAdd(t, T)
		case *Remove:
			err = s.applyRemove(t, T)
		case *Move:
			err = s.applyMove(t)
		case *Leave:
			err = s.applyLeave(t, T)
		case *Rekey:
			err = s.applyRekey(t)
		case *UBIClaim:
			err = s.applyUBIClaim(t, T)
		case *VoteClaim:
			err = s.applyVoteClaim(t, T)
		case *Commit:
			err = s.applyCommit(t)
		case *core.Mix:
			if t.TrieID != TrieElection {
				err = errors.New("mix: hierarchy mixes the election trie only")
			} else {
				err = core.ApplyMix(s.NextElection, t)
			}
		case *core.Transfer:
			f, err = core.ApplyTransfer(s.UTXO, t, T, s.NormTime)
		case *Prune:
			f, err = core.PruneCoins(s.UTXO, t.Keys, T)
		default:
			err = errors.New("apply: unknown tx type")
		}
		if err != nil {
			return nil, fmt.Errorf("tx %x: %w", id[:4], err)
		}
		if f != nil {
			fees.Add(fees, f)
		}
	}
	return fees, nil
}

// Prune — hierarchy prunes expired UTXOs only.
type Prune struct{ Keys [][32]byte }

func (t *Prune) Body() []byte {
	var w core.Buf
	w.U8(core.OpPrune)
	w.U32(uint32(len(t.Keys)))
	for i := range t.Keys {
		w.Bytes(t.Keys[i][:])
	}
	return w.B
}
func (t *Prune) TxID() [32]byte { return core.H(t.Body()) }
root/entry point28 lines
main.go25
// peoplecoin — pluggable UTXO + UBI by demurrage over a population-module
// interface. Two modules share one core: bitpeople (periodic
// anonymous verification) and hierarchy (hierarchical attestation).
package main

import (
	"flag"
	"fmt"

	"peoplecoin/bitpeople"
	"peoplecoin/hierarchy"
)

func main() {
	module := flag.String("module", "both", "bitpeople | hierarchy | both")
	flag.Parse()
	if *module == "bitpeople" || *module == "both" {
		fmt.Println("════════════════ population module: bitpeople ════════════════")
		bitpeople.RunDemo()
	}
	if *module == "hierarchy" || *module == "both" {
		fmt.Println("════════════════ population module: hierarchy ════════════════")
		hierarchy.RunDemo()
	}
}
go.mod3
module peoplecoin

go 1.22
README.md71
# peoplecoin — pluggable UTXO + UBI by demurrage over a population-register interface

> **Status: this is the project.** Coin + UBI + "one person, one
> unit of stake". Supersedes bpc (the v21 reference); the bitpeople
> module is bpc ported onto the core, plus the multi-validator hash
> onion.

One core, two population modules. The core provides payments (UTXO
with 20% annual demurrage as the UBI), the generic Mix, and the
election with a **multi-validator hash onion**. The register is a
module: anything that can answer the interface's seven points
(population_count, is_verified, randomness source, when election
entries are created, when UBI is minted, header fields, transactions)
plugs in.

```
go test ./...          # 21 tests across three packages
go run .               # both demos; -module bitpeople|hierarchy
```

## Packages

| Package | Contents |
|---|---|
| `core` | Binary Merkle radix trie (delta-maintained uint64 counts, max_seed_hits, sum, Merkle proofs), Mix, UTXO/Transfer/Prune (rent relocates value to the validator — nothing is destroyed), the norm tool (`sum ×= decay^Δ`, a planned era event), the election: entry + the **hash onion** (BuildOnion/VerifyPeel — successor commit, zero = consumed) |
| `bitpeople` | Periodic anonymous verification: nym/invite tries, Feistel pairing, phases, the incentive chain, per-period seed with the copied-down schedule **peeled in place** per reveal |
| `hierarchy` | Hierarchical attestation: the People Tree (Add/Remove/Move/Leave/Rekey with consent signatures, tree_count aggregates), continuous UBI (`ClaimableAt` — resurrected from bitpeople's dead code), VoteClaim, **two election tries** with a yearly swap, per-block randomness `rand ^= H(nonce)` |

## The onion

`layer_0 = H(val_Z‖nonce_Z)`, `layer_i = H(val‖nonce‖below)`. Each
layer can name a different validator — DoS protection at *every*
reveal, not just the first. The header nonce is 64 bytes
(`random_32 ‖ next_commit`), 32 at the deepest layer = consumption.
`VerifyPeel` is the shared role and returns the successor (zero =
consumed); that hierarchy additionally feeds the nonce into its
randomness (`evolveRand`) is interface point 3 and stands separate.
In bitpeople the copied-down schedule is peeled (derived state — its
election trie resets at the boundary); in hierarchy the active trie
is peeled live (count drops on consumption, `rand mod count` adapts —
the sequence evolves anyway).

## Documented module choices

Bitpeople: everything from the single-file implementation bpc holds
(frozen indexes, commit as the registration act, fixed era epoch,
uint64 counts). Hierarchy: `LastVote` stores period+1 (zero =
never), Remove/Leave auto-mint accrued UBI per removed person, Rekey
preserves LastUBI/LastVote through a nonce-counted key move, cycle
guard in Move. The UBI accrual is one shared kernel in core
(`AccruedUBI`); the modules differ only in trigger and cadence, and
that difference is forced rather than stylistic: an anonymous
register cannot carry a per-person accumulator — a `last_ubi`
surviving the Mix would be a tracking tag through it — so bitpeople
pays a uniform vote-gated lump to fresh keys, while hierarchy's
public tree accrues continuously. The modules import `core` qualified — every
`core.ApplyMix`, `core.VerifyPeel`, `core.Trie` displays the
core/module boundary at the call site, which is the point of the
architecture. (An earlier dot-import shortcut was reverted: it
caused three name collisions during the build alone, and hid the
interface the restructure exists to demonstrate.)

## Known limits

Same as bpc: Clone is O(N) per block (production form: persistent
nodes), block_size is TBD in the spec, no network layer. The
hierarchy People Tree hashes as a deterministic fold (not a Merkle
trie — the spec's Node is an explicit tree); Merkle proofs of tree
membership are the next step if proof-of-person is to be verified
externally. The hierarchy module is new and tested at demo depth;
bitpeople carries the full ported 18-test suite.

Run it

go test ./...          // 21 tests across three packages
go run .               // both demos, each ending in a full replay through VerifyBlock
go run . -module hierarchy