bech32.mx raw
1 package helpers
2
3 // Bech32 encoding/decoding for NIP-19.
4 // Implements bech32 (BIP-173) without external deps.
5
6 const bech32Charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
7
8 // Bech32Encode encodes data with the given human-readable part.
9 func Bech32Encode(hrp string, data []byte) string {
10 values := bytesToBase32(data)
11 checksum := bech32Checksum(hrp, values)
12 values = append(values, checksum...)
13
14 buf := []byte{:0:len(hrp)+1+len(values)}
15 buf = append(buf, hrp...)
16 buf = append(buf, '1')
17 for _, v := range values {
18 buf = append(buf, bech32Charset[v])
19 }
20 return string(buf)
21 }
22
23 // Bech32Decode decodes a bech32 string. Returns hrp and data bytes.
24 func Bech32Decode(s string) (string, []byte) {
25 // Find separator.
26 pos := -1
27 for i := len(s) - 1; i >= 0; i-- {
28 if s[i] == '1' {
29 pos = i
30 break
31 }
32 }
33 if pos < 1 || pos+7 > len(s) {
34 return "", nil
35 }
36
37 hrp := s[:pos]
38 dataStr := s[pos+1:]
39
40 values := []byte{:len(dataStr)}
41 for i := 0; i < len(dataStr); i++ {
42 idx := charsetIndex(dataStr[i])
43 if idx < 0 {
44 return "", nil
45 }
46 values[i] = byte(idx)
47 }
48
49 if !bech32Verify(hrp, values) {
50 return "", nil
51 }
52
53 // Strip checksum (last 6 chars).
54 values = values[:len(values)-6]
55 data := base32ToBytes(values)
56 return hrp, data
57 }
58
59 // NIP-19 helpers.
60
61 // EncodeNpub encodes a 32-byte public key as npub.
62 func EncodeNpub(pubkey []byte) string {
63 return Bech32Encode("npub", pubkey)
64 }
65
66 // EncodeNsec encodes a 32-byte secret key as nsec.
67 func EncodeNsec(seckey []byte) string {
68 return Bech32Encode("nsec", seckey)
69 }
70
71 // EncodeNote encodes a 32-byte event ID as note.
72 func EncodeNote(eventID []byte) string {
73 return Bech32Encode("note", eventID)
74 }
75
76 // EncodeNevent encodes an event reference as nevent (NIP-19 TLV).
77 func EncodeNevent(id string, relays []string, author string) string {
78 var data []byte
79 idBytes := HexDecode(id)
80 if len(idBytes) == 32 {
81 data = append(data, 0, 32)
82 data = append(data, idBytes...)
83 }
84 for _, r := range relays {
85 rb := []byte(r)
86 data = append(data, 1, byte(len(rb)))
87 data = append(data, rb...)
88 }
89 if author != "" {
90 ab := HexDecode(author)
91 if len(ab) == 32 {
92 data = append(data, 2, 32)
93 data = append(data, ab...)
94 }
95 }
96 return Bech32Encode("nevent", data)
97 }
98
99 // DecodeNpub decodes an npub string to 32 bytes.
100 func DecodeNpub(s string) []byte {
101 hrp, data := Bech32Decode(s)
102 if hrp != "npub" || len(data) != 32 {
103 return nil
104 }
105 return data
106 }
107
108 // DecodeNsec decodes an nsec string to 32 bytes.
109 func DecodeNsec(s string) []byte {
110 hrp, data := Bech32Decode(s)
111 if hrp != "nsec" || len(data) != 32 {
112 return nil
113 }
114 return data
115 }
116
117 // DecodeNote decodes a note string to 32 bytes.
118 func DecodeNote(s string) []byte {
119 hrp, data := Bech32Decode(s)
120 if hrp != "note" || len(data) != 32 {
121 return nil
122 }
123 return data
124 }
125
126 // Nevent holds decoded nevent TLV data (NIP-19).
127 type Nevent struct {
128 ID string // hex event ID
129 Relays []string // optional relay hints
130 Author string // hex pubkey (optional)
131 }
132
133 // DecodeNevent decodes a nevent1... bech32 string (TLV format).
134 func DecodeNevent(s string) *Nevent {
135 hrp, data := Bech32Decode(s)
136 if hrp != "nevent" {
137 return nil
138 }
139 result := &Nevent{}
140 i := 0
141 for i+2 <= len(data) {
142 t := data[i]
143 l := int(data[i+1])
144 i += 2
145 if i+l > len(data) {
146 break
147 }
148 v := data[i : i+l]
149 i += l
150 switch t {
151 case 0:
152 if l == 32 {
153 result.ID = HexEncode(v)
154 }
155 case 1:
156 result.Relays = append(result.Relays, string(v))
157 case 2:
158 if l == 32 {
159 result.Author = HexEncode(v)
160 }
161 }
162 }
163 if result.ID == "" {
164 return nil
165 }
166 return result
167 }
168
169 // Nprofile holds decoded nprofile TLV data (NIP-19).
170 type Nprofile struct {
171 Pubkey string // hex pubkey
172 Relays []string // optional relay hints
173 }
174
175 // DecodeNprofile decodes an nprofile1... bech32 string (TLV format).
176 func DecodeNprofile(s string) *Nprofile {
177 hrp, data := Bech32Decode(s)
178 if hrp != "nprofile" {
179 return nil
180 }
181 result := &Nprofile{}
182 i := 0
183 for i+2 <= len(data) {
184 t := data[i]
185 l := int(data[i+1])
186 i += 2
187 if i+l > len(data) {
188 break
189 }
190 v := data[i : i+l]
191 i += l
192 switch t {
193 case 0:
194 if l == 32 {
195 result.Pubkey = HexEncode(v)
196 }
197 case 1:
198 result.Relays = append(result.Relays, string(v))
199 }
200 }
201 if result.Pubkey == "" {
202 return nil
203 }
204 return result
205 }
206
207 // PubkeyShort returns first 8 chars of hex pubkey.
208 func PubkeyShort(pubkey string) string {
209 if len(pubkey) >= 8 {
210 return pubkey[:8]
211 }
212 return pubkey
213 }
214
215 // Internal bech32 functions.
216
217 func bytesToBase32(data []byte) []byte {
218 var out []byte
219 acc := 0
220 bits := 0
221 for _, b := range data {
222 acc = (acc << 8) | int(b)
223 bits += 8
224 for bits >= 5 {
225 bits -= 5
226 out = append(out, byte((acc>>bits)&0x1f))
227 }
228 acc &= (1 << uint(bits)) - 1
229 }
230 if bits > 0 {
231 out = append(out, byte((acc<<(5-bits))&0x1f))
232 }
233 return out
234 }
235
236 func base32ToBytes(data []byte) []byte {
237 var out []byte
238 acc := 0
239 bits := 0
240 for _, v := range data {
241 acc = (acc << 5) | int(v)
242 bits += 5
243 for bits >= 8 {
244 bits -= 8
245 out = append(out, byte((acc>>bits)&0xff))
246 }
247 acc &= (1 << uint(bits)) - 1
248 }
249 return out
250 }
251
252 func bech32Polymod(values []byte) uint32 {
253 gen := [5]uint32{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}
254 chk := uint32(1)
255 for _, v := range values {
256 b := chk >> 25
257 chk = ((chk & 0x1ffffff) << 5) ^ uint32(v)
258 for i := 0; i < 5; i++ {
259 if (b>>uint(i))&1 == 1 {
260 chk ^= gen[i]
261 }
262 }
263 }
264 return chk
265 }
266
267 func bech32HRPExpand(hrp string) []byte {
268 out := []byte{:0:len(hrp)*2+1}
269 for i := 0; i < len(hrp); i++ {
270 out = append(out, byte(hrp[i]>>5))
271 }
272 out = append(out, 0)
273 for i := 0; i < len(hrp); i++ {
274 out = append(out, byte(hrp[i]&0x1f))
275 }
276 return out
277 }
278
279 func bech32Checksum(hrp string, data []byte) []byte {
280 values := append(bech32HRPExpand(hrp), data...)
281 values = append(values, 0, 0, 0, 0, 0, 0)
282 polymod := bech32Polymod(values) ^ 1
283 out := []byte{:6}
284 for i := 0; i < 6; i++ {
285 out[i] = byte((polymod >> (5 * (5 - uint(i)))) & 0x1f)
286 }
287 return out
288 }
289
290 func bech32Verify(hrp string, data []byte) bool {
291 values := append(bech32HRPExpand(hrp), data...)
292 return bech32Polymod(values) == 1
293 }
294
295 func charsetIndex(c byte) int {
296 for i := 0; i < len(bech32Charset); i++ {
297 if bech32Charset[i] == c {
298 return i
299 }
300 }
301 return -1
302 }
303