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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

bech32_test.gno

7.26 Kb · 230 lines
  1package bech32
  2
  3import (
  4	"testing"
  5
  6	"gno.land/p/nt/uassert/v0"
  7)
  8
  9// BIP-173 §Test vectors: strings that must decode with the Bech32 constant.
 10func TestValidBech32(t *testing.T) {
 11	cases := []struct{ s, hrp string }{
 12		{"A12UEL5L", "a"},
 13		{"a12uel5l", "a"},
 14		{"?1ezyfcl", "?"},
 15		{"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", "abcdef"},
 16		{"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", "split"},
 17	}
 18	for _, c := range cases {
 19		hrp, data, v, err := Decode(c.s)
 20		uassert.NoError(t, err, c.s)
 21		uassert.Equal(t, c.hrp, hrp)
 22		uassert.Equal(t, "bech32", v.String())
 23		// Re-encoding the decoded parts must reproduce the lowercase input.
 24		back, err2 := Encode(hrp, data)
 25		uassert.NoError(t, err2)
 26		uassert.Equal(t, lower(c.s), back)
 27	}
 28}
 29
 30// BIP-350 §Test vectors: the same shapes under the Bech32m constant.
 31func TestValidBech32m(t *testing.T) {
 32	for _, c := range []struct{ s, hrp string }{
 33		{"A1LQFN3A", "a"},
 34		{"a1lqfn3a", "a"},
 35		{"?1v759aa", "?"},
 36		{"split1checkupstagehandshakeupstreamerranterredcaperredlc445v", "split"},
 37	} {
 38		hrp, data, v, err := Decode(c.s)
 39		uassert.NoError(t, err, c.s)
 40		uassert.Equal(t, c.hrp, hrp)
 41		uassert.Equal(t, "bech32m", v.String())
 42		back, err2 := EncodeVariant(hrp, data, Bech32m)
 43		uassert.NoError(t, err2)
 44		uassert.Equal(t, lower(c.s), back)
 45	}
 46}
 47
 48// A gno.land address is a Bech32 string with hrp "g" over a 20-byte payload,
 49// which is the externally-anchored check that the checksum maths is right.
 50func TestGnoAddress(t *testing.T) {
 51	const addr = "g1manfred47kzduec920z88wfr64ylksmdcedlf5"
 52	hrp, payload, v, err := DecodeBytes(addr)
 53	uassert.NoError(t, err)
 54	uassert.Equal(t, "g", hrp)
 55	uassert.Equal(t, 20, len(payload))
 56	uassert.Equal(t, "bech32", v.String())
 57
 58	back, err2 := EncodeBytes(hrp, payload)
 59	uassert.NoError(t, err2)
 60	uassert.Equal(t, addr, back)
 61}
 62
 63// One flipped data character invalidates the checksum.
 64func TestBadChecksum(t *testing.T) {
 65	const addr = "g1manfred47kzduec920z88wfr64ylksmdcedlf5"
 66	broken := addr[:len(addr)-1] + "4"
 67	_, _, _, err := Decode(broken)
 68	uassert.ErrorIs(t, err, ErrChecksum)
 69}
 70
 71func TestDecodeRejects(t *testing.T) {
 72	cases := []struct {
 73		s    string
 74		want error
 75	}{
 76		{"A12UEL5l", ErrMixedCase},
 77		{"abcdef", ErrNoSeparator},
 78		{"1qzzfhee", ErrInvalidHRP},  // empty human-readable part
 79		{"a1qqqqq", ErrTooShort},     // data part shorter than the 6-char checksum
 80		{"a1bqqqqqq", ErrInvalidChar}, // 'b' is not in the charset
 81	}
 82	for _, c := range cases {
 83		_, _, _, err := Decode(c.s)
 84		uassert.ErrorIs(t, err, c.want, c.s)
 85	}
 86}
 87
 88func TestTooLong(t *testing.T) {
 89	long := "a1"
 90	for i := 0; i < MaxLen; i++ {
 91		long += "q"
 92	}
 93	_, _, _, err := Decode(long)
 94	uassert.ErrorIs(t, err, ErrTooLong)
 95
 96	payload := make([]byte, 80)
 97	_, err2 := EncodeBytes("hrp", payload)
 98	uassert.ErrorIs(t, err2, ErrTooLong)
 99}
100
101func TestConvertBits(t *testing.T) {
102	// 8 -> 5 with padding, then back, is the identity.
103	for _, in := range [][]byte{{}, {0x00}, {0xff}, {0xde, 0xad, 0xbe, 0xef}, {1, 2, 3, 4, 5, 6, 7}} {
104		five, err := ConvertBits(in, 8, 5, true)
105		uassert.NoError(t, err)
106		eight, err2 := ConvertBits(five, 5, 8, false)
107		uassert.NoError(t, err2)
108		uassert.Equal(t, len(in), len(eight))
109		for i := range in {
110			uassert.Equal(t, int(in[i]), int(eight[i]))
111		}
112	}
113	// Every produced group fits in 5 bits.
114	five, _ := ConvertBits([]byte{0xff, 0xff}, 8, 5, true)
115	for _, g := range five {
116		uassert.True(t, g < 32)
117	}
118	// A 5-bit input holding a 6-bit value is refused.
119	_, err := ConvertBits([]byte{32}, 5, 8, true)
120	uassert.ErrorIs(t, err, ErrInvalidData)
121	// An unsupported width is refused.
122	_, err2 := ConvertBits([]byte{1}, 0, 5, true)
123	uassert.ErrorIs(t, err2, ErrInvalidBits)
124}
125
126func TestEncodeRejects(t *testing.T) {
127	_, err := Encode("", []byte{0})
128	uassert.ErrorIs(t, err, ErrInvalidHRP)
129	// A human-readable part must stay in the printable US-ASCII range.
130	_, err2 := Encode("a\x7f", []byte{0})
131	uassert.ErrorIs(t, err2, ErrInvalidHRP)
132	// A data group wider than 5 bits is not representable.
133	_, err3 := Encode("a", []byte{32})
134	uassert.ErrorIs(t, err3, ErrInvalidData)
135}
136
137func TestVariantString(t *testing.T) {
138	uassert.Equal(t, "bech32", Bech32.String())
139	uassert.Equal(t, "bech32m", Bech32m.String())
140}
141
142func lower(s string) string {
143	out := []byte(s)
144	for i, c := range out {
145		if c >= 'A' && c <= 'Z' {
146			out[i] = c + 32
147		}
148	}
149	return string(out)
150}
151
152// Why Bech32m exists. A Bech32 string whose final character is 'p' keeps a valid
153// checksum when 'q' characters are inserted in front of that 'p', so a reader
154// accepts a string it was never handed. Both of these verify under the Bech32
155// constant, and they decode to different data parts.
156func TestLengthExtensionWeakness(t *testing.T) {
157	const base = "a1q3g6mnsp"
158	const extended = "a1q3g6mnsqp"
159
160	_, d1, v1, err := Decode(base)
161	uassert.NoError(t, err)
162	uassert.Equal(t, "bech32", v1.String())
163
164	_, d2, v2, err2 := Decode(extended)
165	uassert.NoError(t, err2, "the inserted 'q' does not break the Bech32 checksum")
166	uassert.Equal(t, "bech32", v2.String())
167	uassert.Equal(t, len(d1)+1, len(d2))
168
169	// Bech32m's constant closes it: the same insertion on a Bech32m string of the
170	// same shape is refused.
171	_, _, _, err3 := Decode("a1q6dp699qp")
172	uassert.ErrorIs(t, err3, ErrChecksum)
173	_, _, vm, err4 := Decode("a1q6dp699p")
174	uassert.NoError(t, err4)
175	uassert.Equal(t, "bech32m", vm.String())
176}
177
178// Variant is an exported int, so a caller can hand over a value that is neither
179// constant. Encoding one as Bech32 anyway would answer a question nobody asked.
180func TestUnknownVariant(t *testing.T) {
181	_, err := EncodeVariant("a", nil, Variant(2))
182	uassert.ErrorIs(t, err, ErrInvalidVariant)
183	_, err2 := EncodeVariant("a", nil, Variant(-1))
184	uassert.ErrorIs(t, err2, ErrInvalidVariant)
185	uassert.Equal(t, "invalid", Variant(2).String())
186}
187
188// ConvertBits promises ErrInvalidPadding, and a round trip never reaches it:
189// the padded direction always lands on a group boundary.
190func TestConvertBitsPadding(t *testing.T) {
191	// Five bits in, eight out, unpadded: one group is not a whole byte.
192	_, err := ConvertBits([]byte{1}, 5, 8, false)
193	uassert.ErrorIs(t, err, ErrInvalidPadding)
194	// Ten bits in, eight out: the two leftover bits are not zero, so they are
195	// data the caller would lose rather than padding the encoder added.
196	_, err2 := ConvertBits([]byte{0, 1}, 5, 8, false)
197	uassert.ErrorIs(t, err2, ErrInvalidPadding)
198	// The same ten bits with zero leftovers convert cleanly.
199	out, err3 := ConvertBits([]byte{0, 4}, 5, 8, false)
200	uassert.NoError(t, err3)
201	uassert.Equal(t, 1, len(out))
202}
203
204// An oversized payload is refused before it is regrouped, so the work stays
205// bounded by the 90-character format rather than by what the caller sent.
206func TestEncodeBytesBoundsBeforeWork(t *testing.T) {
207	// "g" plus the separator plus six checksum characters leaves 82 groups,
208	// which is 51 whole bytes.
209	uassert.Equal(t, 51, MaxPayloadLen("g"))
210
211	ok, err := EncodeBytes("g", make([]byte, MaxPayloadLen("g")))
212	uassert.NoError(t, err)
213	uassert.Equal(t, MaxLen, len(ok))
214
215	_, err2 := EncodeBytes("g", make([]byte, MaxPayloadLen("g")+1))
216	uassert.ErrorIs(t, err2, ErrTooLong)
217
218	// A human-readable part that leaves no room at all.
219	uassert.Equal(t, 0, MaxPayloadLen(longHRP()))
220	_, err3 := EncodeBytes(longHRP(), []byte{1})
221	uassert.ErrorIs(t, err3, ErrTooLong)
222}
223
224func longHRP() string {
225	s := ""
226	for i := 0; i < MaxLen; i++ {
227		s += "a"
228	}
229	return s
230}