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.

bech32demo.gno

4.59 Kb · 128 lines
  1// Package bech32demo is a small gnoweb demo of the Bech32 and Bech32m codec
  2// provided by the [p/moul/x/daily/bech32](/p/moul/x/daily/bech32/v0) library:
  3// it takes a real gno.land address apart, shows the BIP-173 and BIP-350 test
  4// vectors side by side, and reproduces the length-extension weakness that is
  5// the whole reason the second variant exists.
  6//
  7// It contains no codec logic of its own. Stateless, so Render is deterministic.
  8package bech32demo
  9
 10import (
 11	"strconv"
 12	"strings"
 13
 14	"gno.land/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/md/v0"
 15	"gno.land/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/kit/ui/v0"
 16	"gno.land/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/x/daily/bech32/v0"
 17	"gno.land/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/x/daily/hexdump/v0"
 18)
 19
 20// sample is a real mainnet address: human-readable part "g", 20 payload bytes.
 21const sample = "g1manfred47kzduec920z88wfr64ylksmdcedlf5"
 22
 23// The BIP-173 (Bech32) and BIP-350 (Bech32m) test vectors, paired by shape so
 24// the only difference on each row is the six-character checksum.
 25var vectors = [][2]string{
 26	{"a12uel5l", "a1lqfn3a"},
 27	{"?1ezyfcl", "?1v759aa"},
 28	{
 29		"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w",
 30		"split1checkupstagehandshakeupstreamerranterredcaperredlc445v",
 31	},
 32}
 33
 34// The insertion that Bech32 accepts and Bech32m refuses. Both strings below
 35// carry a checksum Bech32 verifies; only the second has the extra "q".
 36const (
 37	extBase     = "a1q3g6mnsp"
 38	extInserted = "a1q3g6mnsqp"
 39	extMBase    = "a1q6dp699p"
 40	extMInsert  = "a1q6dp699qp"
 41)
 42
 43// Render renders the demo for gnoweb.
 44//
 45//	Render("")        / Render("/")  -> the full demo
 46//	Render("/<string>")              -> decode that string
 47func Render(path string) string {
 48	var b strings.Builder
 49	b.WriteString("# Bech32\n\n")
 50	b.WriteString("Addresses that carry their own error-detecting checksum, demoing the ")
 51	b.WriteString("[`p/moul/x/daily/bech32`](/p/moul/x/daily/bech32/v0) library.\n\n")
 52
 53	if in := parseArg(path); in != "" {
 54		b.WriteString(decodeOne(in))
 55		return b.String()
 56	}
 57
 58	b.WriteString("## A gno.land address, taken apart\n\n")
 59	b.WriteString(decodeOne(sample))
 60
 61	b.WriteString("\n## The two variants\n\n")
 62	b.WriteString("Same string shape, same data, different checksum constant: ")
 63	b.WriteString("`1` for Bech32, `0x2bc830a3` for Bech32m.\n\n")
 64	vt := ui.NewTable("Bech32 (BIP-173)", "Bech32m (BIP-350)")
 65	for _, pair := range vectors {
 66		vt.Row(md.InlineCode(pair[0]), md.InlineCode(pair[1]))
 67	}
 68	b.WriteString(vt.String() + "\n")
 69
 70	b.WriteString("## Why Bech32m exists\n\n")
 71	b.WriteString("A Bech32 string ending in `p` keeps a valid checksum when `q` ")
 72	b.WriteString("characters are inserted in front of that final `p`, so a reader ")
 73	b.WriteString("accepts a string nobody wrote. Bech32m's constant closes it:\n\n")
 74	wt := ui.NewTable("string", "variant", "verdict")
 75	for _, s := range []string{extBase, extInserted, extMBase, extMInsert} {
 76		_, _, v, err := bech32.Decode(s)
 77		if err != nil {
 78			wt.Row(md.InlineCode(s), "-", "rejected: "+err.Error())
 79			continue
 80		}
 81		wt.Row(md.InlineCode(s), v.String(), "accepted")
 82	}
 83	b.WriteString(wt.String() + "\n")
 84
 85	b.WriteString("Pass a string of your own to decode it: ")
 86	b.WriteString("`Render(\"/<bech32 string>\")`.\n")
 87	return b.String()
 88}
 89
 90// decodeOne renders one string's decode. Everything derived from the input is
 91// escaped: the argument reaches this realm from whoever typed the url.
 92func decodeOne(in string) string {
 93	var b strings.Builder
 94	b.WriteString("Decoding " + ui.Inline(in) + ":\n\n")
 95
 96	hrp, payload, variant, err := bech32.DecodeBytes(in)
 97	if err != nil {
 98		b.WriteString("> " + ui.Inline(err.Error()) + "\n")
 99		return b.String()
100	}
101
102	t := ui.NewTable("field", "value")
103	// hrp bytes run the whole printable-ASCII range, so this cell is escaped,
104	// and escaped text can never sit inside a code span.
105	t.Row("human-readable part", ui.Cell(hrp))
106	t.Row("variant", variant.String())
107	t.Row("payload", strconv.Itoa(len(payload))+" bytes")
108	t.Row("payload (hex)", md.InlineCode(hexOf(payload)))
109	b.WriteString(t.String())
110	return b.String()
111}
112
113// hexOf renders a payload through the hexdump library, which owns the digits.
114func hexOf(payload []byte) string {
115	var b strings.Builder
116	for _, c := range payload {
117		b.WriteString(hexdump.Hex(c))
118	}
119	return b.String()
120}
121
122// parseArg strips the one routing slash in front of the argument and returns
123// the rest verbatim. Nothing else is removed: a slash is legal in a
124// human-readable part, so cutting at one refuses a valid string, and trimming
125// whitespace would render a clean decode of a string nobody typed.
126func parseArg(path string) string {
127	return strings.TrimPrefix(path, "/")
128}