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

b58demo.gno

3.90 Kb · 132 lines
  1// Package b58demo is a small gnoweb demo of the Base58 codec provided by the
  2// [p/moul/x/daily/b58](/p/moul/x/daily/b58/v0) library: it renders the alphabet,
  3// a gallery of worked examples, and an interactive hex → Base58 round-tripper.
  4//
  5// It contains no codec logic of its own — everything comes from `b58.Encode`,
  6// `b58.Decode` and `b58.Alphabet`.
  7package b58demo
  8
  9import (
 10	"encoding/hex"
 11	"strings"
 12
 13	"gno.land/p/moul/x/daily/b58/v0"
 14)
 15
 16// Render produces the gnoweb Markdown view.
 17//
 18//   - "/"        → the alphabet + a small gallery of worked examples.
 19//   - "/<hex>"   → decode the hex input to bytes, show its Base58, and the
 20//     hex-encoded decoded round-trip to prove Encode/Decode are inverse.
 21func Render(path string) string {
 22	arg := strings.TrimPrefix(path, "/")
 23	arg = strings.TrimSpace(arg)
 24
 25	if arg == "" {
 26		return renderHome()
 27	}
 28	return renderHex(arg)
 29}
 30
 31func renderHome() string {
 32	var sb strings.Builder
 33	sb.WriteString("# Base58 (Bitcoin alphabet)\n\n")
 34	sb.WriteString("Demo of the [`p/moul/x/daily/b58`](/p/moul/x/daily/b58/v0) library — ")
 35	sb.WriteString("base conversion from raw bytes (base-256) to base-58, using the ")
 36	sb.WriteString("Bitcoin alphabet (no `0`, `O`, `I`, `l`), pure byte-slice math, no `math/big`.\n\n")
 37
 38	sb.WriteString("## Alphabet\n\n")
 39	sb.WriteString("```\n")
 40	sb.WriteString(b58.Alphabet)
 41	sb.WriteString("\n```\n\n")
 42	sb.WriteString("58 characters, index 0…57.\n\n")
 43
 44	sb.WriteString("## Examples\n\n")
 45	sb.WriteString("| input (utf-8) | hex | Base58 |\n")
 46	sb.WriteString("|---|---|---|\n")
 47	examples := []string{"", "hello world", "gno.land", "Bitcoin"}
 48	for _, e := range examples {
 49		h := hex.EncodeToString([]byte(e))
 50		if h == "" {
 51			h = "(empty)"
 52		}
 53		label := e
 54		if label == "" {
 55			label = "(empty)"
 56		}
 57		sb.WriteString("| `" + label + "` | `" + h + "` | `" + b58.Encode([]byte(e)) + "` |\n")
 58	}
 59	sb.WriteString("\n")
 60
 61	sb.WriteString("Leading zero bytes → leading `1`s:\n\n")
 62	sb.WriteString("| bytes | Base58 |\n|---|---|\n")
 63	sb.WriteString("| `[0x00]` | `" + b58.Encode([]byte{0}) + "` |\n")
 64	sb.WriteString("| `[0x00 0x00 0x00]` | `" + b58.Encode([]byte{0, 0, 0}) + "` |\n\n")
 65
 66	sb.WriteString("## Try it\n\n")
 67	sb.WriteString("Append a hex string to the path to encode/round-trip it:\n\n")
 68	sb.WriteString("- `/68656c6c6f` — the bytes for \"hello\"\n")
 69	sb.WriteString("- `/00000000` — four zero bytes\n")
 70	sb.WriteString("- `/deadbeef`\n")
 71	return sb.String()
 72}
 73
 74func renderHex(arg string) string {
 75	var sb strings.Builder
 76	sb.WriteString("# Base58 — round-trip\n\n")
 77	sb.WriteString("[← alphabet & examples](/r/moul/x/daily/b58demo/v0)\n\n")
 78
 79	raw, err := hex.DecodeString(arg)
 80	if err != nil {
 81		sb.WriteString("**Invalid hex input.**\n\n")
 82		sb.WriteString("`" + arg + "` is not a valid hex string. ")
 83		sb.WriteString("Provide an even number of hex digits, e.g. `/deadbeef`.\n")
 84		return sb.String()
 85	}
 86
 87	encoded := b58.Encode(raw)
 88	decoded := b58.Decode(encoded)
 89	roundTrip := hex.EncodeToString(decoded)
 90
 91	inHex := arg
 92	if inHex == "" {
 93		inHex = "(empty)"
 94	}
 95	outHex := roundTrip
 96	if outHex == "" {
 97		outHex = "(empty)"
 98	}
 99	enc := encoded
100	if enc == "" {
101		enc = "(empty)"
102	}
103
104	sb.WriteString("| step | value |\n|---|---|\n")
105	sb.WriteString("| hex input | `" + inHex + "` |\n")
106	sb.WriteString("| bytes | `" + byteList(raw) + "` |\n")
107	sb.WriteString("| **Base58** | `" + enc + "` |\n")
108	sb.WriteString("| decoded (hex) | `" + outHex + "` |\n\n")
109
110	if roundTrip == arg {
111		sb.WriteString("✓ Round-trip matches: `Decode(Encode(x)) == x`.\n")
112	} else {
113		sb.WriteString("✗ Round-trip mismatch.\n")
114	}
115	return sb.String()
116}
117
118// byteList renders a byte slice as a compact space-separated hex list.
119func byteList(b []byte) string {
120	if len(b) == 0 {
121		return "(none)"
122	}
123	const digits = "0123456789abcdef"
124	out := make([]byte, 0, len(b)*3)
125	for i, c := range b {
126		if i > 0 {
127			out = append(out, ' ')
128		}
129		out = append(out, digits[c>>4], digits[c&0x0f])
130	}
131	return string(out)
132}