feistel/feistel_test.go
159 lines
1package feistel23import (4"crypto/sha256"5"encoding/binary"6"testing"7)89var sizes = []uint64{1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 32, 33, 100, 255, 256, 257, 1000, 1023, 1024, 1025, 4097, 30000}1011func seedOf(s string) [32]byte { return sha256.Sum256([]byte(s)) }1213func TestWidths(t *testing.T) {14cases := []struct{ n, k, l, r uint64 }{15{1, 0, 0, 0}, {2, 1, 0, 1}, {3, 2, 1, 1}, {4, 2, 1, 1}, {5, 3, 1, 2},16{8, 3, 1, 2}, {9, 4, 2, 2}, {1024, 10, 5, 5}, {1025, 11, 5, 6},17}18for _, c := range cases {19k, l, r := widths(c.n)20if uint64(k) != c.k || uint64(l) != c.l || uint64(r) != c.r {21t.Errorf("widths(%d) = %d,%d,%d; want %d,%d,%d", c.n, k, l, r, c.k, c.l, c.r)22}23}24}2526func TestMaskWidth(t *testing.T) {27seed := seedOf("m")28for left := uint(0); left <= 32; left++ {29for half := uint64(0); half < 50; half++ {30m := mask(&seed, 3, half, left)31if left < 64 && m>>left != 0 {32t.Fatalf("mask wider than %d bits: %x", left, m)33}34}35}36// Spot check the definition for left = 12 (2 bytes, shifted by 4).37h := sha256.New()38h.Write(seed[:])39h.Write([]byte{5})40var b [8]byte41binary.BigEndian.PutUint64(b[:], 77)42h.Write(b[:])43sum := h.Sum(nil)44want := (uint64(sum[0])<<8 | uint64(sum[1])) >> 445if got := mask(&seed, 5, 77, 12); got != want {46t.Fatalf("mask = %x, want %x", got, want)47}48}4950func TestBijectionAndInverse(t *testing.T) {51for _, n := range sizes {52for _, s := range []string{"a", "b", "c"} {53seed := seedOf(s)54seen := make([]bool, n)55for i := uint64(0); i < n; i++ {56p := Permute(i, n, seed)57if p >= n {58t.Fatalf("n=%d: Permute(%d) = %d out of range", n, i, p)59}60if seen[p] {61t.Fatalf("n=%d: collision at %d", n, p)62}63seen[p] = true64if back := Invert(p, n, seed); back != i {65t.Fatalf("n=%d: Invert(Permute(%d)) = %d", n, i, back)66}67}68}69}70}7172func TestPartnerSymmetry(t *testing.T) {73for _, n := range sizes {74seed := seedOf("pairs")75unpaired := 076for i := uint64(0); i < n; i++ {77p, ok := Partner(i, n, seed)78if !ok {79unpaired++80continue81}82if p == i {83t.Fatalf("n=%d: %d paired with itself", n, i)84}85q, ok := Partner(p, n, seed)86if !ok || q != i {87t.Fatalf("n=%d: partner not symmetric: %dā%dā%d", n, i, p, q)88}89// Adjacent in the shuffled order.90if Permute(i, n, seed)^1 != Permute(p, n, seed) {91t.Fatalf("n=%d: partners not adjacent", n)92}93}94if want := int(n % 2); unpaired != want {95t.Fatalf("n=%d: %d unpaired, want %d", n, unpaired, want)96}97}98}99100func TestSeedMatters(t *testing.T) {101const n = 1000102a, b := seedOf("x"), seedOf("y")103same := 0104for i := uint64(0); i < n; i++ {105if Permute(i, n, a) == Permute(i, n, b) {106same++107}108}109// Two random permutations agree at ~1 position on average.110if same > 20 {111t.Fatalf("permutations under different seeds agree at %d/%d positions", same, n)112}113// And the seed spreads: no long run of fixed points.114fixed := 0115for i := uint64(0); i < n; i++ {116if Permute(i, n, a) == i {117fixed++118}119}120if fixed > 20 {121t.Fatalf("%d fixed points", fixed)122}123}124125func TestOutOfRangePanics(t *testing.T) {126defer func() {127if recover() == nil {128t.Fatal("expected panic")129}130}()131Permute(5, 5, seedOf("z"))132}133134// Fixed vectors so that other implementations can be checked against this135// one. Regenerate deliberately if the algorithm is ever changed.136func TestVectors(t *testing.T) {137seed := seedOf("peoplecoin")138const n = 1000139got := [8]uint64{}140for i := range got {141got[i] = Permute(uint64(i), n, seed)142}143t.Logf("Permute(0..7, 1000, SHA256(\"peoplecoin\")) = %v", got)144// Self-consistency of the vector with the inverse.145for i, p := range got {146if Invert(p, n, seed) != uint64(i) {147t.Fatal("vector inconsistent")148}149}150}151152func BenchmarkPartner(b *testing.B) {153seed := seedOf("bench")154const n = 1_000_000155for i := 0; i < b.N; i++ {156Partner(uint64(i)%n, n, seed)157}158}