1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
package nostr
import (
"fmt"
"strings"
)
// Bech32 encoding/decoding for NIP-19 (npub, nsec, note, etc.)
// Implements BIP-173 bech32 encoding.
const bech32Alphabet = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
var bech32AlphabetMap [256]int8
func init() {
for i := range bech32AlphabetMap {
bech32AlphabetMap[i] = -1
}
for i, c := range bech32Alphabet {
bech32AlphabetMap[c] = int8(i)
}
}
// bech32Polymod computes the BCH checksum.
func bech32Polymod(values []int) int {
gen := []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}
chk := 1
for _, v := range values {
top := chk >> 25
chk = (chk&0x1ffffff)<<5 ^ v
for i := 0; i < 5; i++ {
if (top>>i)&1 == 1 {
chk ^= gen[i]
}
}
}
return chk
}
// bech32HRPExpand expands the human-readable part for checksum computation.
func bech32HRPExpand(hrp string) []int {
result := make([]int, len(hrp)*2+1)
for i, c := range hrp {
result[i] = int(c) >> 5
result[i+len(hrp)+1] = int(c) & 31
}
return result
}
// bech32CreateChecksum creates the 6-character checksum.
func bech32CreateChecksum(hrp string, data []int) []int {
values := append(bech32HRPExpand(hrp), data...)
values = append(values, []int{0, 0, 0, 0, 0, 0}...)
polymod := bech32Polymod(values) ^ 1
checksum := make([]int, 6)
for i := 0; i < 6; i++ {
checksum[i] = (polymod >> (5 * (5 - i))) & 31
}
return checksum
}
// bech32VerifyChecksum verifies the checksum of bech32 data.
func bech32VerifyChecksum(hrp string, data []int) bool {
return bech32Polymod(append(bech32HRPExpand(hrp), data...)) == 1
}
// convertBits converts between bit groups.
func convertBits(data []byte, fromBits, toBits int, pad bool) ([]int, error) {
acc := 0
bits := 0
result := make([]int, 0, len(data)*fromBits/toBits+1)
maxv := (1 << toBits) - 1
for _, value := range data {
acc = (acc << fromBits) | int(value)
bits += fromBits
for bits >= toBits {
bits -= toBits
result = append(result, (acc>>bits)&maxv)
}
}
if pad {
if bits > 0 {
result = append(result, (acc<<(toBits-bits))&maxv)
}
} else if bits >= fromBits || ((acc<<(toBits-bits))&maxv) != 0 {
return nil, fmt.Errorf("invalid padding")
}
return result, nil
}
// Bech32Encode encodes data with the given human-readable prefix.
func Bech32Encode(hrp string, data []byte) (string, error) {
values, err := convertBits(data, 8, 5, true)
if err != nil {
return "", err
}
checksum := bech32CreateChecksum(hrp, values)
combined := append(values, checksum...)
var result strings.Builder
result.WriteString(hrp)
result.WriteByte('1')
for _, v := range combined {
result.WriteByte(bech32Alphabet[v])
}
return result.String(), nil
}
// Bech32Decode decodes a bech32 string, returning the HRP and data.
func Bech32Decode(s string) (string, []byte, error) {
s = strings.ToLower(s)
pos := strings.LastIndexByte(s, '1')
if pos < 1 || pos+7 > len(s) {
return "", nil, fmt.Errorf("invalid bech32 string")
}
hrp := s[:pos]
dataStr := s[pos+1:]
data := make([]int, len(dataStr))
for i, c := range dataStr {
val := bech32AlphabetMap[c]
if val == -1 {
return "", nil, fmt.Errorf("invalid character: %c", c)
}
data[i] = int(val)
}
if !bech32VerifyChecksum(hrp, data) {
return "", nil, fmt.Errorf("invalid checksum")
}
// Remove checksum
data = data[:len(data)-6]
// Convert from 5-bit to 8-bit
result, err := convertBits(intSliceToBytes(data), 5, 8, false)
if err != nil {
return "", nil, err
}
bytes := make([]byte, len(result))
for i, v := range result {
bytes[i] = byte(v)
}
return hrp, bytes, nil
}
func intSliceToBytes(data []int) []byte {
result := make([]byte, len(data))
for i, v := range data {
result[i] = byte(v)
}
return result
}
|