or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

base-encodings.mdbase58-encodings.mdbech32-encodings.mdindex.mdutils.md

bech32-encodings.mddocs/

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.