statement_test.gno
5.51 Kb · 147 lines
1package wesh
2
3import (
4 "encoding/hex"
5 "testing"
6
7 "gno.land/p/nt/uassert/v0"
8)
9
10// Fixtures. The signatures were produced with Go's crypto/ed25519 over the
11// exact statement text this package builds, using the account key whose seed
12// is 32 x 0x42, the same key as testAccountPKHex.
13const (
14 testChainID = "gnoland-1"
15 testGnoAddr = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"
16
17 testDev1Hex = "0707070707070707070707070707070707070707070707070707070707070707"
18 testDev2Hex = "0909090909090909090909090909090909090909090909090909090909090909"
19
20 bindRev1Sig = "4cd53e6096994d2801de1b16cfd1b2d2aa34ff35b265c53fa176cfb18c949be7" +
21 "0078161fe2afaf503cf808b2f52a321e15a625f56b1b2d0aaaf8c460f6d64702"
22
23 dev0Sig = "19d0bd2517ca6308b089196528e6c27359117b80e1972c71c25434a6f260d62a" +
24 "703a28c2f973d89056e7c691b4f879d38e1b016745508b357d217eb54c984305"
25 dev0Digest = "003570bf50d50213f9f3e9c76a8271435a8f2e4b7c24010a8e8383dedd0ed2a6"
26
27 dev1Sig = "c26b338045373f7504a0d251c2126e31ff3330f08463672a82467daf4dedca51" +
28 "1da4681715a6c3afe4115622cfcca9ec3f44d5a35f9973c3b9856cc06859810c"
29 dev1Digest = "35a8219e60f6cd375e1378879de8921f9d554f054cd8a70ea2d3914b20b77bbb"
30)
31
32func TestBindStatementIsCanonical(t *testing.T) {
33 got := BindStatement(testChainID, testGnoAddr, mustHex(t, testAccountPKHex), mustHex(t, testSeedHex), 1)
34 want := "gno.wesh/v0:bind\n" + testChainID + "\n" + testGnoAddr + "\n" +
35 testAccountPKHex + "\n" + testSeedHex + "\n1"
36 uassert.Equal(t, want, got)
37}
38
39func TestVerifyBind(t *testing.T) {
40 pk := mustHex(t, testAccountPKHex)
41 seed := mustHex(t, testSeedHex)
42 sig := mustHex(t, bindRev1Sig)
43
44 uassert.NoError(t, VerifyBind(testChainID, testGnoAddr, pk, seed, 1, sig),
45 "a signature made by the account key verifies")
46}
47
48// TestVerifyBindRejectsEveryTamperedField is the heart of the scheme: each
49// field is in the signed text precisely so that changing it invalidates the
50// signature. One case per field, so a regression names the field it broke.
51func TestVerifyBindRejectsEveryTamperedField(t *testing.T) {
52 pk := mustHex(t, testAccountPKHex)
53 seed := mustHex(t, testSeedHex)
54 sig := mustHex(t, bindRev1Sig)
55
56 cases := []struct {
57 name string
58 chainID string
59 addr string
60 payload []byte
61 rev int
62 }{
63 {"replayed on another chain", "test7", testGnoAddr, seed, 1},
64 {"claimed from another gno address", testChainID, "g1manfred47kzduec920z88wfr64ylksmdcedlf5", seed, 1},
65 {"a different seed swapped in", testChainID, testGnoAddr, repeat(0xEE, 32), 1},
66 {"an older revision replayed", testChainID, testGnoAddr, seed, 0},
67 {"a newer revision forged", testChainID, testGnoAddr, seed, 2},
68 }
69 for _, tc := range cases {
70 err := VerifyBind(tc.chainID, tc.addr, pk, tc.payload, tc.rev, sig)
71 uassert.ErrorIs(t, err, ErrBadSignature, tc.name)
72 }
73}
74
75func TestVerifyBindRejectsAnotherAccountsKey(t *testing.T) {
76 err := VerifyBind(testChainID, testGnoAddr, repeat(0x33, 32), mustHex(t, testSeedHex), 1, mustHex(t, bindRev1Sig))
77 uassert.ErrorIs(t, err, ErrBadSignature)
78
79 err = VerifyBind(testChainID, testGnoAddr, repeat(0x33, 31), mustHex(t, testSeedHex), 1, mustHex(t, bindRev1Sig))
80 uassert.ErrorIs(t, err, ErrBadAccountPKLen)
81}
82
83func TestGenesisDigest(t *testing.T) {
84 uassert.Equal(t, DigestLen, len(GenesisDigest()))
85 uassert.Equal(t,
86 "0000000000000000000000000000000000000000000000000000000000000000",
87 hex.EncodeToString(GenesisDigest()))
88}
89
90// TestVerifyDeviceChain walks a three-entry sigchain: add, add, revoke. Each
91// entry chains to the digest of the previous one, which is what stops an entry
92// being reordered or silently dropped.
93func TestVerifyDeviceChain(t *testing.T) {
94 pk := mustHex(t, testAccountPKHex)
95
96 d0, err := VerifyDevice(testChainID, pk, 0, GenesisDigest(), OpAdd, mustHex(t, testDev1Hex), mustHex(t, dev0Sig))
97 uassert.NoError(t, err, "first entry")
98 uassert.Equal(t, dev0Digest, hex.EncodeToString(d0), "digest the next entry must chain to")
99
100 d1, err := VerifyDevice(testChainID, pk, 1, d0, OpAdd, mustHex(t, testDev2Hex), mustHex(t, dev1Sig))
101 uassert.NoError(t, err, "second entry")
102 uassert.Equal(t, dev1Digest, hex.EncodeToString(d1))
103}
104
105func TestVerifyDeviceRejectsABrokenChain(t *testing.T) {
106 pk := mustHex(t, testAccountPKHex)
107 dev1 := mustHex(t, testDev1Hex)
108 dev2 := mustHex(t, testDev2Hex)
109
110 cases := []struct {
111 name string
112 seq int
113 prev []byte
114 op string
115 device []byte
116 sig string
117 wantErr error
118 }{
119 {"wrong previous digest", 1, GenesisDigest(), OpAdd, dev2, dev1Sig, ErrBadSignature},
120 {"wrong sequence number", 2, mustHexRaw(dev0Digest), OpAdd, dev2, dev1Sig, ErrBadSignature},
121 {"operation flipped to revoke", 0, GenesisDigest(), OpRevoke, dev1, dev0Sig, ErrBadSignature},
122 {"a different device substituted", 0, GenesisDigest(), OpAdd, dev2, dev0Sig, ErrBadSignature},
123 {"unknown operation", 0, GenesisDigest(), "delete", dev1, dev0Sig, ErrBadOp},
124 {"negative sequence", -1, GenesisDigest(), OpAdd, dev1, dev0Sig, ErrBadSeq},
125 {"short previous digest", 0, repeat(0, 31), OpAdd, dev1, dev0Sig, ErrBadPrevLen},
126 {"short device key", 0, GenesisDigest(), OpAdd, repeat(7, 31), dev0Sig, ErrBadDevicePKLen},
127 }
128 for _, tc := range cases {
129 _, err := VerifyDevice(testChainID, pk, tc.seq, tc.prev, tc.op, tc.device, mustHexRaw(tc.sig))
130 uassert.ErrorIs(t, err, tc.wantErr, tc.name)
131 }
132}
133
134func TestValidOp(t *testing.T) {
135 uassert.True(t, ValidOp(OpAdd))
136 uassert.True(t, ValidOp(OpRevoke))
137 uassert.False(t, ValidOp("Add"), "operations are case-sensitive")
138 uassert.False(t, ValidOp(""))
139}
140
141func mustHexRaw(s string) []byte {
142 b, err := hex.DecodeString(s)
143 if err != nil {
144 panic("bad hex fixture: " + s)
145 }
146 return b
147}