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.gno

10.57 Kb · 360 lines
  1// Package bech32 implements the Bech32 and Bech32m string formats of BIP-173
  2// and BIP-350 as a pure, reusable package.
  3//
  4// A Bech32 string is a human-readable part ("g", "bc", "cosmos"), the separator
  5// '1', and a data part written in a 32-character alphabet that ends in a 6-character
  6// BCH checksum. The checksum is the point: it detects every error of up to four
  7// characters, and locates a single wrong character well enough for a wallet to say
  8// which one it is. That is why a gno.land address, a Bitcoin segwit address and a
  9// Cosmos account address all look the way they do.
 10//
 11// Two variants, differing only in the constant the checksum is xored with:
 12//
 13//   - [Bech32] (constant 1), BIP-173. What gno.land addresses use.
 14//   - [Bech32m] (constant 0x2bc830a3), BIP-350. Adopted after a length-extension
 15//     weakness was found in the original: a Bech32 string ending in 'p' keeps its
 16//     checksum valid when 'q' characters are inserted before that final 'p'.
 17//
 18// [Decode] accepts either and reports which one it read, so a caller that cares
 19// (segwit v1 and later must be Bech32m) can check rather than assume.
 20//
 21// The data part is a sequence of 5-bit groups. [ConvertBits] regroups an ordinary
 22// 8-bit payload into those groups and back, and [EncodeBytes] / [DecodeBytes] are
 23// the two wrapped together for the common case:
 24//
 25//	hrp, payload, _, err := bech32.DecodeBytes("g1manfred47kzduec920z88wfr64ylksmdcedlf5")
 26//	// hrp == "g", payload is the 20-byte address
 27//
 28// A live demo of this package is at
 29// [r/moul/x/daily/bech32demo](/r/moul/x/daily/bech32demo/v0).
 30package bech32
 31
 32import "errors"
 33
 34// Variant distinguishes the two checksum constants.
 35type Variant int
 36
 37// The two variants.
 38const (
 39	// Bech32 is BIP-173, checksum constant 1.
 40	Bech32 Variant = iota
 41	// Bech32m is BIP-350, checksum constant 0x2bc830a3.
 42	Bech32m
 43)
 44
 45// String names the variant, "bech32" or "bech32m".
 46func (v Variant) String() string {
 47	switch v {
 48	case Bech32:
 49		return "bech32"
 50	case Bech32m:
 51		return "bech32m"
 52	}
 53	return "invalid"
 54}
 55
 56func (v Variant) constant() uint32 {
 57	if v == Bech32m {
 58		return 0x2bc830a3
 59	}
 60	return 1
 61}
 62
 63var (
 64	// ErrTooLong is returned when a string exceeds MaxLen.
 65	ErrTooLong = errors.New("bech32: string too long")
 66	// ErrTooShort is returned when a string is shorter than the minimum.
 67	ErrTooShort = errors.New("bech32: string too short")
 68	// ErrMixedCase is returned when a string mixes upper and lower case.
 69	ErrMixedCase = errors.New("bech32: mixed case")
 70	// ErrNoSeparator is returned when the '1' separator is missing.
 71	ErrNoSeparator = errors.New("bech32: no separator")
 72	// ErrInvalidHRP is returned for an out-of-range human-readable part.
 73	ErrInvalidHRP = errors.New("bech32: invalid human-readable part")
 74	// ErrInvalidChar is returned when the data part holds a non-charset byte.
 75	ErrInvalidChar = errors.New("bech32: invalid data character")
 76	// ErrInvalidData is returned when a group does not fit its bit width.
 77	ErrInvalidData = errors.New("bech32: invalid data value")
 78	// ErrChecksum is returned when the trailing checksum does not verify.
 79	ErrChecksum = errors.New("bech32: bad checksum")
 80	// ErrInvalidBits is returned for an unsupported ConvertBits width.
 81	ErrInvalidBits = errors.New("bech32: invalid bit width")
 82	// ErrInvalidPadding is returned when unpadded conversion leaves bits over.
 83	ErrInvalidPadding = errors.New("bech32: invalid padding")
 84	// ErrInvalidVariant is returned for a Variant that is neither constant.
 85	ErrInvalidVariant = errors.New("bech32: invalid variant")
 86)
 87
 88
 89// MaxLen is BIP-173's cap on the whole encoded string, separator and checksum
 90// included. It exists so that a wrong character can always be located, and it is
 91// what keeps a decode bounded in gas.
 92const MaxLen = 90
 93
 94// ChecksumLen is the number of data characters the checksum occupies.
 95const ChecksumLen = 6
 96
 97// Charset is the 32-character data alphabet. It omits '1', 'b', 'i' and 'o',
 98// the four that are easiest to misread.
 99const Charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
100
101// Encode builds a Bech32 string from a human-readable part and 5-bit groups.
102func Encode(hrp string, data []byte) (string, error) {
103	return EncodeVariant(hrp, data, Bech32)
104}
105
106// EncodeVariant is [Encode] with an explicit checksum variant.
107//
108// hrp is lowercased; every data byte must be below 32. The result is always
109// lowercase, which is the form BIP-173 requires anything machine-generated to use.
110func EncodeVariant(hrp string, data []byte, v Variant) (string, error) {
111	if v != Bech32 && v != Bech32m {
112		return "", ErrInvalidVariant
113	}
114	hrp = toLower(hrp)
115	if err := checkHRP(hrp); err != nil {
116		return "", err
117	}
118	for _, d := range data {
119		if d >= 32 {
120			return "", ErrInvalidData
121		}
122	}
123	if len(hrp)+1+len(data)+ChecksumLen > MaxLen {
124		return "", ErrTooLong
125	}
126
127	out := make([]byte, 0, len(hrp)+1+len(data)+ChecksumLen)
128	out = append(out, hrp...)
129	out = append(out, '1')
130	for _, d := range data {
131		out = append(out, Charset[d])
132	}
133	for _, d := range checksum(hrp, data, v) {
134		out = append(out, Charset[d])
135	}
136	return string(out), nil
137}
138
139// Decode parses a Bech32 or Bech32m string into its human-readable part, its
140// 5-bit data groups (checksum removed) and the variant whose checksum verified.
141//
142// The input may be all upper or all lower case, never a mix; the returned
143// human-readable part is lowercase.
144func Decode(s string) (string, []byte, Variant, error) {
145	if len(s) > MaxLen {
146		return "", nil, Bech32, ErrTooLong
147	}
148	hasUpper, hasLower := false, false
149	for i := 0; i < len(s); i++ {
150		c := s[i]
151		if c < 33 || c > 126 {
152			return "", nil, Bech32, ErrInvalidChar
153		}
154		if c >= 'A' && c <= 'Z' {
155			hasUpper = true
156		}
157		if c >= 'a' && c <= 'z' {
158			hasLower = true
159		}
160	}
161	if hasUpper && hasLower {
162		return "", nil, Bech32, ErrMixedCase
163	}
164	s = toLower(s)
165
166	sep := lastIndexByte(s, '1')
167	if sep < 0 {
168		return "", nil, Bech32, ErrNoSeparator
169	}
170	hrp := s[:sep]
171	if err := checkHRP(hrp); err != nil {
172		return "", nil, Bech32, err
173	}
174	part := s[sep+1:]
175	if len(part) < ChecksumLen {
176		return "", nil, Bech32, ErrTooShort
177	}
178
179	data := make([]byte, 0, len(part))
180	for i := 0; i < len(part); i++ {
181		idx := indexByte(Charset, part[i])
182		if idx < 0 {
183			return "", nil, Bech32, ErrInvalidChar
184		}
185		data = append(data, byte(idx))
186	}
187
188	v := Bech32
189	got := polymod(append(hrpExpand(hrp), data...))
190	if got != v.constant() {
191		v = Bech32m
192		if got != v.constant() {
193			return "", nil, Bech32, ErrChecksum
194		}
195	}
196	return hrp, data[:len(data)-ChecksumLen], v, nil
197}
198
199// ConvertBits regroups data from one bit width to another, most significant bit
200// first. Going 8 to 5 needs pad set, since the last group is rarely full; coming
201// back 5 to 8 must not pad, so that leftover bits are reported rather than turned
202// into a trailing zero byte.
203func ConvertBits(data []byte, from, to uint8, pad bool) ([]byte, error) {
204	if from < 1 || from > 8 || to < 1 || to > 8 {
205		return nil, ErrInvalidBits
206	}
207	maxv := uint32(1)<<to - 1
208	acc := uint32(0)
209	bits := uint8(0)
210	out := make([]byte, 0, len(data)*int(from)/int(to)+1)
211	for _, b := range data {
212		if from < 8 && uint32(b) >= uint32(1)<<from {
213			return nil, ErrInvalidData
214		}
215		acc = acc<<from | uint32(b)
216		bits += from
217		for bits >= to {
218			bits -= to
219			out = append(out, byte(acc>>bits&maxv))
220		}
221	}
222	if pad {
223		if bits > 0 {
224			out = append(out, byte(acc<<(to-bits)&maxv))
225		}
226		return out, nil
227	}
228	if bits >= from || acc<<(to-bits)&maxv != 0 {
229		return nil, ErrInvalidPadding
230	}
231	return out, nil
232}
233
234// EncodeBytes encodes an 8-bit payload as Bech32, converting to 5-bit groups
235// first. This is the call an address wants.
236func EncodeBytes(hrp string, payload []byte) (string, error) {
237	if err := checkHRP(toLower(hrp)); err != nil {
238		return "", err
239	}
240	// Refuse before regrouping, not after: ConvertBits would otherwise scan and
241	// materialise a payload of any size only for Encode to reject the result.
242	if len(payload) > MaxPayloadLen(hrp) {
243		return "", ErrTooLong
244	}
245	data, err := ConvertBits(payload, 8, 5, true)
246	if err != nil {
247		return "", err
248	}
249	return Encode(hrp, data)
250}
251
252// DecodeBytes decodes a string and converts its data part back to 8-bit bytes.
253func DecodeBytes(s string) (string, []byte, Variant, error) {
254	hrp, data, v, err := Decode(s)
255	if err != nil {
256		return "", nil, Bech32, err
257	}
258	payload, err := ConvertBits(data, 5, 8, false)
259	if err != nil {
260		return "", nil, Bech32, err
261	}
262	return hrp, payload, v, nil
263}
264
265// MaxPayloadLen is the largest 8-bit payload [EncodeBytes] can fit under hrp
266// without passing MaxLen. It is 51 bytes for a gno.land address, whose
267// human-readable part is one character, and 0 when hrp leaves no room at all.
268func MaxPayloadLen(hrp string) int {
269	groups := MaxLen - len(hrp) - 1 - ChecksumLen
270	if groups <= 0 {
271		return 0
272	}
273	// A payload of b bytes needs ceil(8b/5) groups, so b groups fit exactly
274	// while 8b <= 5*groups.
275	return groups * 5 / 8
276}
277
278// checksum returns the six 5-bit groups that terminate the data part.
279func checksum(hrp string, data []byte, v Variant) []byte {
280	values := append(hrpExpand(hrp), data...)
281	values = append(values, 0, 0, 0, 0, 0, 0)
282	mod := polymod(values) ^ v.constant()
283	out := make([]byte, ChecksumLen)
284	for i := 0; i < ChecksumLen; i++ {
285		out[i] = byte(mod>>uint(5*(5-i))) & 31
286	}
287	return out
288}
289
290// polymod is the BCH code of BIP-173 over GF(32).
291func polymod(values []byte) uint32 {
292	gen := [5]uint32{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}
293	chk := uint32(1)
294	for _, v := range values {
295		top := chk >> 25
296		chk = (chk&0x1ffffff)<<5 ^ uint32(v)
297		for i := 0; i < 5; i++ {
298			if (top>>uint(i))&1 == 1 {
299				chk ^= gen[i]
300			}
301		}
302	}
303	return chk
304}
305
306// hrpExpand spreads the human-readable part into the 5-bit groups the checksum
307// is computed over: every high 3 bits, a zero, then every low 5 bits.
308func hrpExpand(hrp string) []byte {
309	out := make([]byte, 0, len(hrp)*2+1)
310	for i := 0; i < len(hrp); i++ {
311		out = append(out, hrp[i]>>5)
312	}
313	out = append(out, 0)
314	for i := 0; i < len(hrp); i++ {
315		out = append(out, hrp[i]&31)
316	}
317	return out
318}
319
320// checkHRP rejects an empty human-readable part and any byte outside printable
321// US-ASCII, which is what BIP-173 allows there.
322func checkHRP(hrp string) error {
323	if len(hrp) == 0 {
324		return ErrInvalidHRP
325	}
326	for i := 0; i < len(hrp); i++ {
327		if hrp[i] < 33 || hrp[i] > 126 {
328			return ErrInvalidHRP
329		}
330	}
331	return nil
332}
333
334func toLower(s string) string {
335	out := []byte(s)
336	for i := 0; i < len(out); i++ {
337		if out[i] >= 'A' && out[i] <= 'Z' {
338			out[i] += 32
339		}
340	}
341	return string(out)
342}
343
344func indexByte(s string, c byte) int {
345	for i := 0; i < len(s); i++ {
346		if s[i] == c {
347			return i
348		}
349	}
350	return -1
351}
352
353func lastIndexByte(s string, c byte) int {
354	for i := len(s) - 1; i >= 0; i-- {
355		if s[i] == c {
356			return i
357		}
358	}
359	return -1
360}