carlos / movement Public

Clone
git clone https://amadan.net/carlos/movement

Plain git — no account needed to clone.

Download

Download this file

1package movement
2
3import (
4 "strconv"
5 "testing"
6)
7
8// testDifficulty is low so the tests can brute-force a solution quickly. The
9// property being tested is the binding, not the cost.
10const testDifficulty = 10
11
12func solve(t *testing.T, nonce, email string, difficulty int) string {
13 t.Helper()
14 for i := 0; i < 1<<24; i++ {
15 c := strconv.Itoa(i)
16 if VerifyPow(nonce, email, c, difficulty) {
17 return c
18 }
19 }
20 t.Fatalf("no solution found for %q at difficulty %d", email, difficulty)
21 return ""
22}
23
24func TestValidSolutionVerifies(t *testing.T) {
25 counter := solve(t, "nonce1", "alice@example.com", testDifficulty)
26 if !VerifyPow("nonce1", "alice@example.com", counter, testDifficulty) {
27 t.Fatal("a solution this package found must verify in this package")
28 }
29}
30
31// This is ruling 3's only real defence, and the reason the scheme is worth
32// anything: a solution must not transfer.
33func TestSolutionDoesNotTransferToAnotherAddress(t *testing.T) {
34 counter := solve(t, "nonce1", "alice@example.com", testDifficulty)
35
36 if VerifyPow("nonce1", "bob@example.com", counter, testDifficulty) {
37 t.Fatal("one solve must not buy a second address; that is the whole scheme")
38 }
39}
40
41func TestSolutionDoesNotTransferToAnotherNonce(t *testing.T) {
42 counter := solve(t, "nonce1", "alice@example.com", testDifficulty)
43
44 if VerifyPow("nonce2", "alice@example.com", counter, testDifficulty) {
45 t.Fatal("a solution must be bound to its own challenge")
46 }
47}
48
49func TestShortSolutionIsRejected(t *testing.T) {
50 counter := solve(t, "nonce1", "alice@example.com", testDifficulty)
51
52 // The same counter, judged against a harder challenge.
53 if VerifyPow("nonce1", "alice@example.com", counter, testDifficulty+10) {
54 t.Fatal("a solution below the required difficulty must be refused")
55 }
56 if VerifyPow("nonce1", "alice@example.com", "", testDifficulty) {
57 t.Fatal("an empty counter must be refused")
58 }
59}
60
61// Case and surrounding space must not change the preimage, or a visitor who
62// typed their address with a capital would be told nothing and simply fail.
63func TestVerificationIsCaseAndSpaceInsensitive(t *testing.T) {
64 counter := solve(t, "nonce1", "alice@example.com", testDifficulty)
65
66 for _, variant := range []string{"Alice@Example.com", " alice@example.com ", "ALICE@EXAMPLE.COM"} {
67 if !VerifyPow("nonce1", variant, counter, testDifficulty) {
68 t.Errorf("%q should normalise to the same preimage", variant)
69 }
70 }
71}
72
73// pow.js does the same thing with a regex over A-Z. Anything cleverer here
74// would diverge from it on some input, and the divergence would be invisible.
75func TestNormalizeEmailTouchesASCIIOnly(t *testing.T) {
76 if got := NormalizeEmail(" Alice@Example.COM "); got != "alice@example.com" {
77 t.Errorf("NormalizeEmail = %q", got)
78 }
79 // A non-ASCII capital is left exactly as it is, on both sides.
80 if got := NormalizeEmail("İX@e.com"); got != "İx@e.com" {
81 t.Errorf("NormalizeEmail must not fold non-ASCII, got %q", got)
82 }
83}
84
85func TestLeadingZeroBitsCounts(t *testing.T) {
86 cases := []struct {
87 in []byte
88 want int
89 }{
90 {[]byte{0xFF}, 0},
91 {[]byte{0x7F}, 1},
92 {[]byte{0x01}, 7},
93 {[]byte{0x00, 0xFF}, 8},
94 {[]byte{0x00, 0x0F}, 12},
95 {[]byte{0x00, 0x00}, 16},
96 }
97 for _, c := range cases {
98 if got := leadingZeroBits(c.in); got != c.want {
99 t.Errorf("leadingZeroBits(%x) = %d, want %d", c.in, got, c.want)
100 }
101 }
102}
103