0
# Bech32 Encodings
1
2
Bitcoin BIP 173/350 compliant bech32 and bech32m encodings designed for Bitcoin addresses and Lightning Network invoices. These encodings work with 5-bit words and provide strong error detection with typed prefix support.
3
4
## Capabilities
5
6
### Bech32 Standard
7
8
BIP 173 bech32 implementation for Bitcoin segwit v0 addresses.
9
10
```typescript { .api }
11
/**
12
* bech32 from BIP 173 for Bitcoin segwit v0 addresses
13
* Operates on 5-bit words with strong error detection
14
* For high-level Bitcoin operations, use scure-btc-signer instead
15
*/
16
const bech32: Bech32;
17
18
interface Bech32 {
19
encode<Prefix extends string>(
20
prefix: Prefix,
21
words: number[] | Uint8Array,
22
limit?: number | false
23
): `${Lowercase<Prefix>}1${string}`;
24
25
decode<Prefix extends string>(
26
str: `${Prefix}1${string}`,
27
limit?: number | false
28
): Bech32Decoded<Prefix>;
29
30
encodeFromBytes(prefix: string, bytes: Uint8Array): string;
31
decodeToBytes(str: string): Bech32DecodedWithArray;
32
decodeUnsafe(str: string, limit?: number | false): void | Bech32Decoded<string>;
33
fromWords(to: number[]): Uint8Array;
34
fromWordsUnsafe(to: number[]): void | Uint8Array;
35
toWords(from: Uint8Array): number[];
36
}
37
```
38
39
### Bech32m
40
41
BIP 350 bech32m implementation that fixes bech32 length extension weaknesses.
42
43
```typescript { .api }
44
/**
45
* bech32m from BIP 350 for Bitcoin segwit v1+ addresses
46
* Mitigates length extension attacks found in original bech32
47
* Same interface as bech32 but different checksum constant
48
*/
49
const bech32m: Bech32;
50
```
51
52
### Bech32 Type Definitions
53
54
Type definitions for bech32 decoding results.
55
56
```typescript { .api }
57
interface Bech32Decoded<Prefix extends string = string> {
58
prefix: Prefix;
59
words: number[];
60
}
61
62
interface Bech32DecodedWithArray<Prefix extends string = string> {
63
prefix: Prefix;
64
words: number[];
65
bytes: Uint8Array;
66
}
67
```
68
69
## Usage Examples
70
71
### Basic Encoding and Decoding
72
73
```typescript
74
import { bech32, bech32m } from "@scure/base";
75
76
// Encode from bytes (for simple data)
77
const data = new Uint8Array([0x12, 0x34, 0x56, 0x78]);
78
const encoded = bech32.encodeFromBytes("test", data);
79
console.log(encoded); // "test1zg69v0y73adq4p"
80
81
// Decode back to bytes
82
const decoded = bech32.decodeToBytes(encoded);
83
console.log(decoded.prefix); // "test"
84
console.log(decoded.bytes); // Uint8Array([0x12, 0x34, 0x56, 0x78])
85
console.log(decoded.words); // 5-bit word representation
86
```
87
88
### Working with 5-bit Words
89
90
```typescript
91
import { bech32 } from "@scure/base";
92
93
// Convert bytes to 5-bit words
94
const bytes = new Uint8Array([0xff, 0xee, 0xdd]);
95
const words = bech32.toWords(bytes);
96
console.log(words); // Array of 5-bit values
97
98
// Encode with words directly
99
const encoded = bech32.encode("bc", words);
100
101
// Decode to get words back
102
const decoded = bech32.decode(encoded);
103
console.log(decoded.prefix); // "bc"
104
console.log(decoded.words); // Original 5-bit words
105
106
// Convert words back to bytes
107
const originalBytes = bech32.fromWords(decoded.words);
108
```
109
110
### Bitcoin Address Example
111
112
```typescript
113
import { bech32, bech32m } from "@scure/base";
114
115
// Example: Parsing Bitcoin bech32 address (simplified)
116
function parseBitcoinAddress(address: string) {
117
try {
118
const decoded = bech32.decode(address);
119
120
if (decoded.prefix !== "bc" && decoded.prefix !== "tb") {
121
throw new Error("Invalid Bitcoin address prefix");
122
}
123
124
// BIP-141: First word is version, rest is witness program
125
const [version, ...dataWords] = decoded.words;
126
const program = bech32.fromWords(dataWords);
127
128
return {
129
network: decoded.prefix === "bc" ? "mainnet" : "testnet",
130
version,
131
program
132
};
133
} catch (error) {
134
// Try bech32m for v1+ addresses
135
const decoded = bech32m.decode(address);
136
const [version, ...dataWords] = decoded.words;
137
const program = bech32m.fromWords(dataWords);
138
139
return {
140
network: decoded.prefix === "bc" ? "mainnet" : "testnet",
141
version,
142
program
143
};
144
}
145
}
146
147
// Example usage
148
const address = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
149
const parsed = parseBitcoinAddress(address);
150
console.log(parsed);
151
```
152
153
### Lightning Invoice Example
154
155
```typescript
156
import { bech32 } from "@scure/base";
157
158
// Example: Basic Lightning invoice parsing (simplified)
159
function parseLightningInvoice(invoice: string) {
160
// Lightning invoices use bech32 with "ln" + network prefix
161
const decoded = bech32.decode(invoice.toLowerCase());
162
163
// Extract network from prefix (lnbc = mainnet, lntb = testnet, etc.)
164
const network = decoded.prefix.startsWith("lnbc") ? "mainnet" :
165
decoded.prefix.startsWith("lntb") ? "testnet" : "unknown";
166
167
// Lightning invoices encode amount in prefix after "ln"
168
const amountMatch = decoded.prefix.match(/^ln[a-z]+(\d+)([munp]?)$/);
169
let amount = null;
170
171
if (amountMatch) {
172
const [, value, unit] = amountMatch;
173
const multiplier = unit === "m" ? 0.001 : unit === "u" ? 0.000001 :
174
unit === "n" ? 0.000000001 : unit === "p" ? 0.000000000001 : 1;
175
amount = parseInt(value) * multiplier;
176
}
177
178
return {
179
network,
180
amount,
181
words: decoded.words,
182
// In practice, you'd parse the data field for payment details
183
};
184
}
185
```
186
187
### Safe Decoding
188
189
```typescript
190
import { bech32 } from "@scure/base";
191
192
// Use decodeUnsafe for validation without throwing
193
const maybeDecoded = bech32.decodeUnsafe("potentially_invalid_string");
194
if (maybeDecoded) {
195
console.log("Valid bech32:", maybeDecoded);
196
} else {
197
console.log("Invalid bech32 string");
198
}
199
200
// Use fromWordsUnsafe for word conversion without throwing
201
const words = [31, 15, 20, 8]; // Some 5-bit words
202
const maybeBytes = bech32.fromWordsUnsafe(words);
203
if (maybeBytes) {
204
console.log("Valid conversion:", maybeBytes);
205
} else {
206
console.log("Invalid word sequence");
207
}
208
```
209
210
### String Length Limits
211
212
```typescript
213
import { bech32 } from "@scure/base";
214
215
// Default limit is 90 characters
216
const shortData = new Uint8Array(10);
217
const encoded = bech32.encodeFromBytes("test", shortData); // OK
218
219
// Disable length limit
220
const longData = new Uint8Array(100);
221
const encodedLong = bech32.encodeFromBytes("test", longData, false); // OK
222
223
// Custom limit
224
try {
225
const encoded = bech32.encode("test", [1, 2, 3], 10); // Very short limit
226
} catch (error) {
227
console.log(error.message); // Length exceeds limit
228
}
229
```
230
231
## Advanced Features
232
233
### Typed Prefix Support
234
235
```typescript
236
import { bech32 } from "@scure/base";
237
238
// TypeScript enforces correct prefix format
239
type BitcoinPrefix = "bc" | "tb";
240
241
function encodeBitcoinAddress<T extends BitcoinPrefix>(
242
prefix: T,
243
program: Uint8Array
244
): `${T}1${string}` {
245
return bech32.encodeFromBytes(prefix, program);
246
}
247
248
const mainnetAddr = encodeBitcoinAddress("bc", new Uint8Array(20));
249
// Return type is `bc1${string}`
250
251
function decodeBitcoinAddress<T extends BitcoinPrefix>(
252
address: `${T}1${string}`
253
) {
254
return bech32.decodeToBytes(address);
255
}
256
```
257
258
### Error Handling
259
260
```typescript
261
import { bech32, bech32m } from "@scure/base";
262
263
try {
264
// Invalid checksum
265
bech32.decode("bc1invalid_checksum");
266
} catch (error) {
267
console.log(error.message); // "Invalid checksum in bc1invalid_checksum: expected ..."
268
}
269
270
try {
271
// Invalid prefix
272
bech32.decode("invalidformat");
273
} catch (error) {
274
console.log(error.message); // 'Letter "1" must be present between prefix and data only'
275
}
276
277
try {
278
// Invalid characters
279
bech32.decode("bc1invalid#characters1234567");
280
} catch (error) {
281
console.log(error.message); // "Unknown letter: #"
282
}
283
284
try {
285
// Convert invalid 5-bit words to bytes
286
bech32.fromWords([32]); // Invalid: 5-bit values must be 0-31
287
} catch (error) {
288
console.log(error.message); // "Invalid word value"
289
}
290
```
291
292
## When to Use Bech32 vs Bech32m
293
294
- **bech32**: Use for Bitcoin segwit v0 addresses and Legacy Lightning invoices
295
- **bech32m**: Use for Bitcoin segwit v1+ addresses (Taproot) and newer applications
296
297
Both have identical APIs but use different checksum constants internally for security reasons.