b58demo.gno
4.42 Kb · 139 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/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/md/v0"
14 "gno.land/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/x/daily/b58/v0"
15)
16
17// Render produces the gnoweb Markdown view.
18//
19// - "/" → the alphabet + a small gallery of worked examples.
20// - "/<hex>" → decode the hex input to bytes, show its Base58, and the
21// hex-encoded decoded round-trip to prove Encode/Decode are inverse.
22func Render(path string) string {
23 arg := strings.TrimPrefix(path, "/")
24 arg = strings.TrimSpace(arg)
25
26 if arg == "" {
27 return renderHome()
28 }
29 return renderHex(arg)
30}
31
32func renderHome() string {
33 var sb strings.Builder
34 sb.WriteString("# Base58 (Bitcoin alphabet)\n\n")
35 sb.WriteString("Demo of the [`p/moul/x/daily/b58`](/p/moul/x/daily/b58/v0) library — ")
36 sb.WriteString("base conversion from raw bytes (base-256) to base-58, using the ")
37 sb.WriteString("Bitcoin alphabet (no `0`, `O`, `I`, `l`), pure byte-slice math, no `math/big`.\n\n")
38
39 sb.WriteString("## Alphabet\n\n")
40 sb.WriteString("```\n")
41 sb.WriteString(b58.Alphabet)
42 sb.WriteString("\n```\n\n")
43 sb.WriteString("58 characters, index 0…57.\n\n")
44
45 sb.WriteString("## Examples\n\n")
46 sb.WriteString("| input (utf-8) | hex | Base58 |\n")
47 sb.WriteString("|---|---|---|\n")
48 examples := []string{"", "hello world", "gno.land", "Bitcoin"}
49 for _, e := range examples {
50 h := hex.EncodeToString([]byte(e))
51 if h == "" {
52 h = "(empty)"
53 }
54 label := e
55 if label == "" {
56 label = "(empty)"
57 }
58 sb.WriteString("| `" + label + "` | `" + h + "` | `" + b58.Encode([]byte(e)) + "` |\n")
59 }
60 sb.WriteString("\n")
61
62 sb.WriteString("Leading zero bytes → leading `1`s:\n\n")
63 sb.WriteString("| bytes | Base58 |\n|---|---|\n")
64 sb.WriteString("| `[0x00]` | `" + b58.Encode([]byte{0}) + "` |\n")
65 sb.WriteString("| `[0x00 0x00 0x00]` | `" + b58.Encode([]byte{0, 0, 0}) + "` |\n\n")
66
67 sb.WriteString("## Try it\n\n")
68 sb.WriteString("Append a hex string to the path to encode/round-trip it:\n\n")
69 sb.WriteString("- `/68656c6c6f` — the bytes for \"hello\"\n")
70 sb.WriteString("- `/00000000` — four zero bytes\n")
71 sb.WriteString("- `/deadbeef`\n")
72 return sb.String()
73}
74
75func renderHex(arg string) string {
76 var sb strings.Builder
77 sb.WriteString("# Base58 — round-trip\n\n")
78 sb.WriteString("[← alphabet & examples](/r/moul/x/daily/b58demo/v0)\n\n")
79
80 raw, err := hex.DecodeString(arg)
81 if err != nil {
82 sb.WriteString("**Invalid hex input.**\n\n")
83 // arg is the visitor's URL path and this branch is the one ANY
84 // non-hex path reaches, so the span has to be built by something that
85 // sizes its fence past a backtick in the content. Hand-rolled
86 // backticks here let a crafted path close the span and render its own
87 // markdown on a gno.land page.
88 sb.WriteString(md.InlineCode(arg) + " is not a valid hex string. ")
89 sb.WriteString("Provide an even number of hex digits, e.g. `/deadbeef`.\n")
90 return sb.String()
91 }
92
93 encoded := b58.Encode(raw)
94 decoded := b58.Decode(encoded)
95 roundTrip := hex.EncodeToString(decoded)
96
97 inHex := arg
98 if inHex == "" {
99 inHex = "(empty)"
100 }
101 outHex := roundTrip
102 if outHex == "" {
103 outHex = "(empty)"
104 }
105 enc := encoded
106 if enc == "" {
107 enc = "(empty)"
108 }
109
110 sb.WriteString("| step | value |\n|---|---|\n")
111 //gnovet:ignore render-path-in-code-span inHex is the output of a successful hex.DecodeString, so [0-9a-fA-F] only
112 sb.WriteString("| hex input | `" + inHex + "` |\n")
113 sb.WriteString("| bytes | `" + byteList(raw) + "` |\n")
114 sb.WriteString("| **Base58** | `" + enc + "` |\n")
115 sb.WriteString("| decoded (hex) | `" + outHex + "` |\n\n")
116
117 if roundTrip == arg {
118 sb.WriteString("✓ Round-trip matches: `Decode(Encode(x)) == x`.\n")
119 } else {
120 sb.WriteString("✗ Round-trip mismatch.\n")
121 }
122 return sb.String()
123}
124
125// byteList renders a byte slice as a compact space-separated hex list.
126func byteList(b []byte) string {
127 if len(b) == 0 {
128 return "(none)"
129 }
130 const digits = "0123456789abcdef"
131 out := make([]byte, 0, len(b)*3)
132 for i, c := range b {
133 if i > 0 {
134 out = append(out, ' ')
135 }
136 out = append(out, digits[c>>4], digits[c&0x0f])
137 }
138 return string(out)
139}