rng/rng.go
149 lines
1// Package rng holds the random number and what it drives: the votes cast2// by reveals, the tally that turns them into a seed, and the seeded choice3// of block producer for each slot.4//5// Every Verified creates an RNG entry that commits to a number in phases6// 0–1 and reveals it in phase 2 of the following period. A reveal casts a7// vote,8//9// SHA256(address ‖ preimage ‖ seed) mod rngEntryCount10//11// with the seed of the period the reveal takes place in, which was unknown12// when the preimage was chosen. At the period boundary the number with the13// most votes wins, the lowest on a tie, and becomes the seed as a14// big-endian uint64 zero-padded to 32 bytes; 0 if nobody revealed.15//16// The slot's validator is the election entry at index17//18// SHA256(seed ‖ uint64(slot)) mod electionEntryCount19//20// hidden until block production by the commit SHA256(validator ‖ nonce).21package rng2223import (24"crypto/sha256"25"encoding/binary"26"math/big"27"sort"28)2930// HashMod interprets h as a big-endian integer and reduces it mod n. It31// panics if n is zero.32func HashMod(h [32]byte, n uint64) uint64 {33if n == 0 {34panic("rng: modulus zero")35}36return new(big.Int).Mod(new(big.Int).SetBytes(h[:]), new(big.Int).SetUint64(n)).Uint64()37}3839// Commit is the commitment to a preimage.40func Commit(preimage [32]byte) [32]byte { return sha256.Sum256(preimage[:]) }4142// Vote returns the number a reveal votes for, given the entry's address,43// the revealed preimage, the current seed and the number of RNG entries.44func Vote(addr, preimage, seed [32]byte, entries uint64) uint64 {45h := sha256.New()46h.Write(addr[:])47h.Write(preimage[:])48h.Write(seed[:])49var sum [32]byte50copy(sum[:], h.Sum(nil))51return HashMod(sum, entries)52}5354// Seed encodes a winning number as a seed: big-endian uint64 in the last55// eight bytes.56func Seed(number uint64) [32]byte {57var s [32]byte58binary.BigEndian.PutUint64(s[24:], number)59return s60}6162// SeedNumber recovers the number from a seed.63func SeedNumber(seed [32]byte) uint64 { return binary.BigEndian.Uint64(seed[24:]) }6465// Tally counts votes over a period's reveals. It is auxiliary state, not66// committed in any trie: it is recomputed from the Reveal operations in the67// period's phase 2 blocks, and only its outcome, the seed, persists.68type Tally struct {69votes map[uint64]uint6470}7172// NewTally returns an empty tally.73func NewTally() *Tally { return &Tally{votes: map[uint64]uint64{}} }7475// Add records one vote for number.76func (t *Tally) Add(number uint64) { t.votes[number]++ }7778// Clone returns an independent copy.79func (t *Tally) Clone() *Tally {80c := NewTally()81for n, v := range t.votes {82c.votes[n] = v83}84return c85}8687// Total returns the number of votes cast.88func (t *Tally) Total() uint64 {89var n uint6490for _, c := range t.votes {91n += c92}93return n94}9596// Winner returns the number with the most votes, the lowest on a tie, and97// false if no votes were cast.98func (t *Tally) Winner() (uint64, bool) {99var best, bestCount uint64100found := false101for n, c := range t.votes {102if !found || c > bestCount || (c == bestCount && n < best) {103best, bestCount, found = n, c, true104}105}106return best, found107}108109// NextSeed is the seed for the following period: the winner, or 0.110func (t *Tally) NextSeed() [32]byte {111n, _ := t.Winner()112return Seed(n)113}114115// Counts returns (number, votes) pairs sorted by number, for inspection.116func (t *Tally) Counts() [][2]uint64 {117out := make([][2]uint64, 0, len(t.votes))118for n, c := range t.votes {119out = append(out, [2]uint64{n, c})120}121sort.Slice(out, func(i, j int) bool { return out[i][0] < out[j][0] })122return out123}124125// ValidatorIndex returns the index in election_trie[0] of the entry whose126// validator owns slot. entries must be non-zero: a period without votes has127// no validators and the chain ends.128func ValidatorIndex(seed [32]byte, slot, entries uint64) uint64 {129h := sha256.New()130h.Write(seed[:])131var b [8]byte132binary.BigEndian.PutUint64(b[:], slot)133h.Write(b[:])134var sum [32]byte135copy(sum[:], h.Sum(nil))136return HashMod(sum, entries)137}138139// ElectionCommit is SHA256(validator_pubkey ‖ nonce), the content of a140// Vote, revealed in the header of every block the validator produces.141func ElectionCommit(validator, nonce [32]byte) [32]byte {142h := sha256.New()143h.Write(validator[:])144h.Write(nonce[:])145var sum [32]byte146copy(sum[:], h.Sum(nil))147return sum148}