rng/rng_test.go
167 lines
1package rng23import (4"crypto/sha256"5"math"6"math/big"7"testing"8)910func TestHashMod(t *testing.T) {11h := sha256.Sum256([]byte("x"))12for _, n := range []uint64{1, 2, 3, 1000, 1 << 40, math.MaxUint64} {13want := new(big.Int).Mod(new(big.Int).SetBytes(h[:]), new(big.Int).SetUint64(n)).Uint64()14if got := HashMod(h, n); got != want {15t.Fatalf("HashMod(n=%d) = %d, want %d", n, got, want)16}17}18if HashMod(h, 1) != 0 {19t.Fatal("mod 1")20}21defer func() {22if recover() == nil {23t.Fatal("mod 0 should panic")24}25}()26HashMod(h, 0)27}2829func TestSeedRoundTrip(t *testing.T) {30s := Seed(0x0102030405060708)31want := [32]byte{}32copy(want[24:], []byte{1, 2, 3, 4, 5, 6, 7, 8})33if s != want {34t.Fatalf("seed encoding %x", s)35}36if SeedNumber(s) != 0x0102030405060708 {37t.Fatal("round trip")38}39if Seed(0) != ([32]byte{}) {40t.Fatal("zero seed")41}42}4344func TestTally(t *testing.T) {45ta := NewTally()46if _, ok := ta.Winner(); ok {47t.Fatal("empty tally has no winner")48}49if ta.NextSeed() != ([32]byte{}) {50t.Fatal("no reveals → seed 0")51}52ta.Add(7)53ta.Add(3)54ta.Add(7)55ta.Add(3)56ta.Add(9)57if w, ok := ta.Winner(); !ok || w != 3 {58t.Fatalf("tie must go to the lowest number, got %d", w)59}60ta.Add(7)61if w, _ := ta.Winner(); w != 7 {62t.Fatalf("most votes wins, got %d", w)63}64if ta.Total() != 6 || len(ta.Counts()) != 3 || ta.Counts()[0] != [2]uint64{3, 2} {65t.Fatal("counts")66}67if SeedNumber(ta.NextSeed()) != 7 {68t.Fatal("next seed")69}70c := ta.Clone()71c.Add(3)72c.Add(3)73if w, _ := c.Winner(); w != 3 {74t.Fatal("clone should tally independently")75}76if w, _ := ta.Winner(); w != 7 || ta.Total() != 6 {77t.Fatal("clone mutated the original")78}79}8081// Votes over N numbers are spread roughly uniformly per vote, so the82// maximum count over N voters is small (Poisson e^-1/k!): with 10 00083// entries the winner has ~5–7 votes.84func TestVoteSpread(t *testing.T) {85const n = 1000086seed := Seed(12345)87ta := NewTally()88for i := 0; i < n; i++ {89addr := sha256.Sum256([]byte{byte(i), byte(i >> 8), 'a'})90pre := sha256.Sum256([]byte{byte(i), byte(i >> 8), 'p'})91v := Vote(addr, pre, seed, n)92if v >= n {93t.Fatalf("vote %d out of range", v)94}95ta.Add(v)96}97_, ok := ta.Winner()98if !ok {99t.Fatal("no winner")100}101var max uint64102for _, c := range ta.Counts() {103if c[1] > max {104max = c[1]105}106}107if max < 3 || max > 12 {108t.Fatalf("max votes on one number = %d, expected ~5–7 for N=10000", max)109}110if len(ta.Counts()) < 6000 || len(ta.Counts()) > 6700 {111t.Fatalf("%d distinct numbers hit, expected ~63%% of N", len(ta.Counts()))112}113// The seed changes every vote.114changed := 0115other := Seed(12346)116for i := 0; i < 100; i++ {117addr := sha256.Sum256([]byte{byte(i), 'a'})118pre := sha256.Sum256([]byte{byte(i), 'p'})119if Vote(addr, pre, seed, n) != Vote(addr, pre, other, n) {120changed++121}122}123if changed < 90 {124t.Fatalf("seed barely affects votes: %d/100 changed", changed)125}126}127128func TestValidatorIndex(t *testing.T) {129seed := Seed(99)130const entries = 37131hits := make([]int, entries)132for slot := uint64(0); slot < 37*200; slot++ {133i := ValidatorIndex(seed, slot, entries)134if i >= entries {135t.Fatalf("index %d out of range", i)136}137hits[i]++138}139for i, h := range hits {140if h < 120 || h > 300 {141t.Fatalf("entry %d selected %d times of ~200", i, h)142}143}144if ValidatorIndex(seed, 5, entries) != ValidatorIndex(seed, 5, entries) {145t.Fatal("not deterministic")146}147if ValidatorIndex(seed, 5, 1) != 0 {148t.Fatal("single validator")149}150}151152func TestCommits(t *testing.T) {153pre := sha256.Sum256([]byte("p"))154if Commit(pre) != sha256.Sum256(pre[:]) {155t.Fatal("commit")156}157v := sha256.Sum256([]byte("v"))158nonce := sha256.Sum256([]byte("n"))159want := sha256.Sum256(append(append([]byte{}, v[:]...), nonce[:]...))160if ElectionCommit(v, nonce) != want {161t.Fatal("election commit")162}163if ElectionCommit(v, nonce) == ElectionCommit(v, [32]byte{}) {164t.Fatal("nonce must matter")165}166}