bech32.go 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 := make([]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 := make([]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 // DecodeNpub decodes an npub string to 32 bytes.
77 func DecodeNpub(s string) []byte {
78 hrp, data := Bech32Decode(s)
79 if hrp != "npub" || len(data) != 32 {
80 return nil
81 }
82 return data
83 }
84
85 // DecodeNsec decodes an nsec string to 32 bytes.
86 func DecodeNsec(s string) []byte {
87 hrp, data := Bech32Decode(s)
88 if hrp != "nsec" || len(data) != 32 {
89 return nil
90 }
91 return data
92 }
93
94 // DecodeNote decodes a note string to 32 bytes.
95 func DecodeNote(s string) []byte {
96 hrp, data := Bech32Decode(s)
97 if hrp != "note" || len(data) != 32 {
98 return nil
99 }
100 return data
101 }
102
103 // PubkeyShort returns first 8 chars of hex pubkey.
104 func PubkeyShort(pubkey string) string {
105 if len(pubkey) >= 8 {
106 return pubkey[:8]
107 }
108 return pubkey
109 }
110
111 // Internal bech32 functions.
112
113 func bytesToBase32(data []byte) []byte {
114 var out []byte
115 acc := 0
116 bits := 0
117 for _, b := range data {
118 acc = (acc << 8) | int(b)
119 bits += 8
120 for bits >= 5 {
121 bits -= 5
122 out = append(out, byte((acc>>bits)&0x1f))
123 }
124 acc &= (1 << uint(bits)) - 1
125 }
126 if bits > 0 {
127 out = append(out, byte((acc<<(5-bits))&0x1f))
128 }
129 return out
130 }
131
132 func base32ToBytes(data []byte) []byte {
133 var out []byte
134 acc := 0
135 bits := 0
136 for _, v := range data {
137 acc = (acc << 5) | int(v)
138 bits += 5
139 for bits >= 8 {
140 bits -= 8
141 out = append(out, byte((acc>>bits)&0xff))
142 }
143 acc &= (1 << uint(bits)) - 1
144 }
145 return out
146 }
147
148 func bech32Polymod(values []byte) uint32 {
149 gen := [5]uint32{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}
150 chk := uint32(1)
151 for _, v := range values {
152 b := chk >> 25
153 chk = ((chk & 0x1ffffff) << 5) ^ uint32(v)
154 for i := 0; i < 5; i++ {
155 if (b>>uint(i))&1 == 1 {
156 chk ^= gen[i]
157 }
158 }
159 }
160 return chk
161 }
162
163 func bech32HRPExpand(hrp string) []byte {
164 out := make([]byte, 0, len(hrp)*2+1)
165 for i := 0; i < len(hrp); i++ {
166 out = append(out, byte(hrp[i]>>5))
167 }
168 out = append(out, 0)
169 for i := 0; i < len(hrp); i++ {
170 out = append(out, byte(hrp[i]&0x1f))
171 }
172 return out
173 }
174
175 func bech32Checksum(hrp string, data []byte) []byte {
176 values := append(bech32HRPExpand(hrp), data...)
177 values = append(values, 0, 0, 0, 0, 0, 0)
178 polymod := bech32Polymod(values) ^ 1
179 out := make([]byte, 6)
180 for i := 0; i < 6; i++ {
181 out[i] = byte((polymod >> (5 * (5 - uint(i)))) & 0x1f)
182 }
183 return out
184 }
185
186 func bech32Verify(hrp string, data []byte) bool {
187 values := append(bech32HRPExpand(hrp), data...)
188 return bech32Polymod(values) == 1
189 }
190
191 func charsetIndex(c byte) int {
192 for i := 0; i < len(bech32Charset); i++ {
193 if bech32Charset[i] == c {
194 return i
195 }
196 }
197 return -1
198 }
199