Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}