const Charset
Charset is the 32-character data alphabet. It omits '1', 'b', 'i' and 'o', the four that are easiest to misread.
Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.
Package bech32 implements the Bech32 and Bech32m string formats of BIP-173 and BIP-350 as a pure, reusable package.
gno.land/p/moul/x/daily/bech32/v0Bech32 and Bech32m, BIP-173 and BIP-350: Encode, EncodeVariant,
Decode, ConvertBits, EncodeBytes, DecodeBytes.
1import "gno.land/p/moul/x/daily/bech32/v0"
2
3hrp, payload, v, err := bech32.DecodeBytes("g1manfred47kzduec920z88wfr64ylksmdcedlf5")
4// hrp == "g", payload is the 20-byte address, v.String() == "bech32"
5
6bech32.EncodeBytes("g", payload) // back to the same string
7bech32.EncodeVariant("a", nil, bech32.Bech32m) // "a1lqfn3a"
What the format buys. A human-readable part, the separator 1, and a data
part in a 32-character alphabet that ends in a six-character BCH checksum. The
checksum is the point: it detects every error of up to four characters, and the
alphabet leaves out 1, b, i and o, the four that are easiest to misread.
That is why a gno.land address, a Bitcoin segwit address and a Cosmos account
address all look the way they do.
Two variants, one constant apart. Bech32 xors its checksum with 1,
Bech32m with 0x2bc830a3. Decode accepts either and reports which one
verified, so a caller that cares can check instead of assuming.
Why the second one exists, reproduced as a test rather than quoted: a Bech32
string ending in p keeps a valid checksum when q characters are inserted in
front of that final p. a1q3g6mnsp and a1q3g6mnsqp both verify, and decode
to different data. The same insertion on a Bech32m string is refused.
Verified against the BIP-173 and BIP-350 vectors, and against a live mainnet address, which is the externally anchored check: it decodes to 20 bytes and re-encodes to the same 40 characters.
Strict where it matters: mixed case is rejected rather than folded, the 90
character cap of BIP-173 is enforced (it is what keeps a wrong character
locatable, and a decode bounded in gas), and ConvertBits going back to 8 bits
refuses leftover bits rather than inventing a trailing zero byte. A Variant
that is neither constant is refused rather than quietly encoded as Bech32, and
EncodeBytes checks MaxPayloadLen(hrp) before it regroups anything, so an
oversized payload costs a comparison and not a full scan.
Live demo: r/moul/x/daily/bech32demo
· render it at /r/moul/x/daily/bech32demo/v0.
Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.
🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.
Package bech32 implements the Bech32 and Bech32m string formats of BIP-173 and BIP-350 as a pure, reusable package.
A Bech32 string is a human-readable part ("g", "bc", "cosmos"), the separator '1', and a data part written in a 32-character alphabet that ends in a 6-character BCH checksum. The checksum is the point: it detects every error of up to four characters, and locates a single wrong character well enough for a wallet to say which one it is. That is why a gno.land address, a Bitcoin segwit address and a Cosmos account address all look the way they do.
Two variants, differing only in the constant the checksum is xored with:
Decode accepts either and reports which one it read, so a caller that cares (segwit v1 and later must be Bech32m) can check rather than assume.
The data part is a sequence of 5-bit groups. ConvertBits regroups an ordinary 8-bit payload into those groups and back, and EncodeBytes / DecodeBytes are the two wrapped together for the common case:
1hrp, payload, _, err := bech32.DecodeBytes("g1manfred47kzduec920z88wfr64ylksmdcedlf5")
2// hrp == "g", payload is the 20-byte address
A live demo of this package is at r/moul/x/daily/bech32demo(/r/moul/x/daily/bech32demo/v0).
Charset is the 32-character data alphabet. It omits '1', 'b', 'i' and 'o', the four that are easiest to misread.
ChecksumLen is the number of data characters the checksum occupies.
MaxLen is BIP-173's cap on the whole encoded string, separator and checksum included. It exists so that a wrong character can always be located, and it is what keeps a decode bounded in gas.
The two variants.
1var (
2 // ErrTooLong is returned when a string exceeds MaxLen.
3 ErrTooLong = errors.New("bech32: string too long")
4 // ErrTooShort is returned when a string is shorter than the minimum.
5 ErrTooShort = errors.New("bech32: string too short")
6 // ErrMixedCase is returned when a string mixes upper and lower case.
7 ErrMixedCase = errors.New("bech32: mixed case")
8 // ErrNoSeparator is returned when the '1' separator is missing.
9 ErrNoSeparator = errors.New("bech32: no separator")
10 // ErrInvalidHRP is returned for an out-of-range human-readable part.
11 ErrInvalidHRP = errors.New("bech32: invalid human-readable part")
12 // ErrInvalidChar is returned when the data part holds a non-charset byte.
13 ErrInvalidChar = errors.New("bech32: invalid data character")
14 // ErrInvalidData is returned when a group does not fit its bit width.
15 ErrInvalidData = errors.New("bech32: invalid data value")
16 // ErrChecksum is returned when the trailing checksum does not verify.
17 ErrChecksum = errors.New("bech32: bad checksum")
18 // ErrInvalidBits is returned for an unsupported ConvertBits width.
19 ErrInvalidBits = errors.New("bech32: invalid bit width")
20 // ErrInvalidPadding is returned when unpadded conversion leaves bits over.
21 ErrInvalidPadding = errors.New("bech32: invalid padding")
22 // ErrInvalidVariant is returned for a Variant that is neither constant.
23 ErrInvalidVariant = errors.New("bech32: invalid variant")
24)ConvertBits regroups data from one bit width to another, most significant bit first. Going 8 to 5 needs pad set, since the last group is rarely full; coming back 5 to 8 must not pad, so that leftover bits are reported rather than turned into a trailing zero byte.
Encode builds a Bech32 string from a human-readable part and 5-bit groups.
EncodeBytes encodes an 8-bit payload as Bech32, converting to 5-bit groups first. This is the call an address wants.
EncodeVariant is Encode with an explicit checksum variant.
hrp is lowercased; every data byte must be below 32. The result is always lowercase, which is the form BIP-173 requires anything machine-generated to use.
MaxPayloadLen is the largest 8-bit payload EncodeBytes can fit under hrp without passing MaxLen. It is 51 bytes for a gno.land address, whose human-readable part is one character, and 0 when hrp leaves no room at all.
Decode parses a Bech32 or Bech32m string into its human-readable part, its 5-bit data groups (checksum removed) and the variant whose checksum verified.
The input may be all upper or all lower case, never a mix; the returned human-readable part is lowercase.
DecodeBytes decodes a string and converts its data part back to 8-bit bytes.