Skip to content

Commit 239711b

Browse files
authored
Merge pull request #1676 from andrewtoth/bech32m
Support BIP350 bech32m address serialization and future segwit versions
2 parents 576e4e2 + ef9f809 commit 239711b

File tree

10 files changed

+237
-32
lines changed

10 files changed

+237
-32
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ npm run-script coverage
146146
- [BIP69](https://github.com/bitcoinjs/bip69) - Lexicographical Indexing of Transaction Inputs and Outputs
147147
- [Base58](https://github.com/cryptocoinjs/bs58) - Base58 encoding/decoding
148148
- [Base58 Check](https://github.com/bitcoinjs/bs58check) - Base58 check encoding/decoding
149-
- [Bech32](https://github.com/bitcoinjs/bech32) - A BIP173 compliant Bech32 encoding library
149+
- [Bech32](https://github.com/bitcoinjs/bech32) - A BIP173/BIP350 compliant Bech32/Bech32m encoding library
150150
- [coinselect](https://github.com/bitcoinjs/coinselect) - A fee-optimizing, transaction input selection module for bitcoinjs-lib.
151151
- [merkle-lib](https://github.com/bitcoinjs/merkle-lib) - A performance conscious library for merkle root and tree calculations.
152152
- [minimaldata](https://github.com/bitcoinjs/minimaldata) - A module to check bitcoin policy: SCRIPT_VERIFY_MINIMALDATA

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"types"
5050
],
5151
"dependencies": {
52-
"bech32": "^1.1.2",
52+
"bech32": "^2.0.0",
5353
"bip174": "^2.0.1",
5454
"bip32": "^2.0.4",
5555
"bip66": "^1.1.0",

src/address.js

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,31 @@ const networks = require('./networks');
44
const payments = require('./payments');
55
const bscript = require('./script');
66
const types = require('./types');
7-
const bech32 = require('bech32');
7+
const { bech32, bech32m } = require('bech32');
88
const bs58check = require('bs58check');
99
const typeforce = require('typeforce');
10+
const FUTURE_SEGWIT_MAX_SIZE = 40;
11+
const FUTURE_SEGWIT_MIN_SIZE = 2;
12+
const FUTURE_SEGWIT_MAX_VERSION = 16;
13+
const FUTURE_SEGWIT_MIN_VERSION = 1;
14+
const FUTURE_SEGWIT_VERSION_DIFF = 0x50;
15+
function _toFutureSegwitAddress(output, network) {
16+
const data = output.slice(2);
17+
if (
18+
data.length < FUTURE_SEGWIT_MIN_SIZE ||
19+
data.length > FUTURE_SEGWIT_MAX_SIZE
20+
)
21+
throw new TypeError('Invalid program length for segwit address');
22+
const version = output[0] - FUTURE_SEGWIT_VERSION_DIFF;
23+
if (
24+
version < FUTURE_SEGWIT_MIN_VERSION ||
25+
version > FUTURE_SEGWIT_MAX_VERSION
26+
)
27+
throw new TypeError('Invalid version for segwit address');
28+
if (output[1] !== data.length)
29+
throw new TypeError('Invalid script for segwit address');
30+
return toBech32(data, version, network.bech32);
31+
}
1032
function fromBase58Check(address) {
1133
const payload = bs58check.decode(address);
1234
// TODO: 4.0.0, move to "toOutputScript"
@@ -18,10 +40,22 @@ function fromBase58Check(address) {
1840
}
1941
exports.fromBase58Check = fromBase58Check;
2042
function fromBech32(address) {
21-
const result = bech32.decode(address);
43+
let result;
44+
let version;
45+
try {
46+
result = bech32.decode(address);
47+
} catch (e) {}
48+
if (result) {
49+
version = result.words[0];
50+
if (version !== 0) throw new TypeError(address + ' uses wrong encoding');
51+
} else {
52+
result = bech32m.decode(address);
53+
version = result.words[0];
54+
if (version === 0) throw new TypeError(address + ' uses wrong encoding');
55+
}
2256
const data = bech32.fromWords(result.words.slice(1));
2357
return {
24-
version: result.words[0],
58+
version,
2559
prefix: result.prefix,
2660
data: Buffer.from(data),
2761
};
@@ -38,7 +72,9 @@ exports.toBase58Check = toBase58Check;
3872
function toBech32(data, version, prefix) {
3973
const words = bech32.toWords(data);
4074
words.unshift(version);
41-
return bech32.encode(prefix, words);
75+
return version === 0
76+
? bech32.encode(prefix, words)
77+
: bech32m.encode(prefix, words);
4278
}
4379
exports.toBech32 = toBech32;
4480
function fromOutputScript(output, network) {
@@ -56,6 +92,9 @@ function fromOutputScript(output, network) {
5692
try {
5793
return payments.p2wsh({ output, network }).address;
5894
} catch (e) {}
95+
try {
96+
return _toFutureSegwitAddress(output, network);
97+
} catch (e) {}
5998
throw new Error(bscript.toASM(output) + ' has no matching Address');
6099
}
61100
exports.fromOutputScript = fromOutputScript;
@@ -83,7 +122,16 @@ function toOutputScript(address, network) {
83122
return payments.p2wpkh({ hash: decodeBech32.data }).output;
84123
if (decodeBech32.data.length === 32)
85124
return payments.p2wsh({ hash: decodeBech32.data }).output;
86-
}
125+
} else if (
126+
decodeBech32.version >= FUTURE_SEGWIT_MIN_VERSION &&
127+
decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION &&
128+
decodeBech32.data.length >= FUTURE_SEGWIT_MIN_SIZE &&
129+
decodeBech32.data.length <= FUTURE_SEGWIT_MAX_SIZE
130+
)
131+
return bscript.compile([
132+
decodeBech32.version + FUTURE_SEGWIT_VERSION_DIFF,
133+
decodeBech32.data,
134+
]);
87135
}
88136
}
89137
throw new Error(address + ' has no matching Script');

src/payments/p2wpkh.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const lazy = require('./lazy');
77
const typef = require('typeforce');
88
const OPS = bscript.OPS;
99
const ecc = require('tiny-secp256k1');
10-
const bech32 = require('bech32');
10+
const { bech32 } = require('bech32');
1111
const EMPTY_BUFFER = Buffer.alloc(0);
1212
// witness: {signature} {pubKey}
1313
// input: <>

src/payments/p2wsh.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const lazy = require('./lazy');
77
const typef = require('typeforce');
88
const OPS = bscript.OPS;
99
const ecc = require('tiny-secp256k1');
10-
const bech32 = require('bech32');
10+
const { bech32 } = require('bech32');
1111
const EMPTY_BUFFER = Buffer.alloc(0);
1212
function stacksEqual(a, b) {
1313
if (a.length !== b.length) return false;

test/fixtures/address.json

Lines changed: 113 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -77,23 +77,58 @@
7777
"bech32": "bcrt1qqqqqqqqqqqqqqahrwf6d62emdxmpq8gu3xe9au9fjwc9sxxn4k2qujfh7u",
7878
"data": "000000000000000076e37274dd2b3b69b6101d1c89b25ef0a993b05818d3ad94",
7979
"script": "OP_0 000000000000000076e37274dd2b3b69b6101d1c89b25ef0a993b05818d3ad94"
80+
},
81+
{
82+
"network": "bitcoin",
83+
"bech32": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y",
84+
"version": 1,
85+
"data": "751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6",
86+
"script": "OP_1 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"
87+
},
88+
{
89+
"network": "bitcoin",
90+
"bech32": "BC1SW50QGDZ25J",
91+
"version": 16,
92+
"data": "751e",
93+
"script": "OP_16 751e"
94+
},
95+
{
96+
"network": "bitcoin",
97+
"bech32": "bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs",
98+
"version": 2,
99+
"data": "751e76e8199196d454941c45d1b3a323",
100+
"script": "OP_2 751e76e8199196d454941c45d1b3a323"
101+
},
102+
{
103+
"network": "testnet",
104+
"bech32": "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c",
105+
"version": 1,
106+
"data": "000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
107+
"script": "OP_1 000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"
108+
},
109+
{
110+
"network": "bitcoin",
111+
"bech32": "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0",
112+
"version": 1,
113+
"data": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
114+
"script": "OP_1 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
80115
}
81116
],
82117
"bech32": [
83118
{
84-
"address": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx",
119+
"address": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y",
85120
"version": 1,
86121
"prefix": "bc",
87122
"data": "751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"
88123
},
89124
{
90-
"address": "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj",
125+
"address": "bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs",
91126
"version": 2,
92127
"prefix": "bc",
93128
"data": "751e76e8199196d454941c45d1b3a323"
94129
},
95130
{
96-
"address": "BC1SW50QA3JX3S",
131+
"address": "BC1SW50QGDZ25J",
97132
"version": 16,
98133
"prefix": "bc",
99134
"data": "751e"
@@ -110,16 +145,24 @@
110145
"exception": "Mixed-case string"
111146
},
112147
{
113-
"address": "tb1pw508d6qejxtdg4y5r3zarqfsj6c3",
148+
"address": "tb1pw508d6qejxtdg4y5r3zarquvzkan",
114149
"exception": "Excess padding"
115150
},
116151
{
117-
"address": "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du",
152+
"address": "bc1zw508d6qejxtdg4y5r3zarvaryvq37eag7",
118153
"exception": "Excess padding"
119154
},
120155
{
121156
"address": "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv",
122157
"exception": "Non-zero padding"
158+
},
159+
{
160+
"address": "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du",
161+
"exception": "uses wrong encoding"
162+
},
163+
{
164+
"address": "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd",
165+
"exception": "uses wrong encoding"
123166
}
124167
],
125168
"fromBase58Check": [
@@ -161,7 +204,7 @@
161204
},
162205
{
163206
"exception": "has an invalid prefix",
164-
"address": "BC1SW50QA3JX3S",
207+
"address": "BC1SW50QGDZ25J",
165208
"network": {
166209
"bech32": "foo"
167210
}
@@ -172,31 +215,88 @@
172215
},
173216
{
174217
"exception": "has no matching Script",
175-
"address": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx"
218+
"address": "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90"
176219
},
177220
{
178221
"exception": "has no matching Script",
179-
"address": "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj"
222+
"address": "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2"
180223
},
181224
{
182225
"exception": "has no matching Script",
183-
"address": "BC1SW50QA3JX3S"
226+
"address": "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5"
184227
},
185228
{
186229
"exception": "has no matching Script",
187-
"address": "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90"
230+
"address": "bc1qqqqqqqqqqv9qus"
188231
},
189232
{
233+
"address": "tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut",
234+
"exception": "has an invalid prefix"
235+
},
236+
{
237+
"address": "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd",
238+
"exception": "has no matching Script"
239+
},
240+
{
241+
"address": "tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf",
190242
"exception": "has no matching Script",
191-
"address": "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2"
243+
"network": {
244+
"bech32": "tb"
245+
}
246+
},
247+
{
248+
"address": "BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL",
249+
"exception": "has no matching Script"
192250
},
193251
{
252+
"address": "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh",
253+
"exception": "has no matching Script"
254+
},
255+
{
256+
"address": "tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47",
194257
"exception": "has no matching Script",
195-
"address": "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5"
258+
"network": {
259+
"bech32": "tb"
260+
}
261+
},
262+
{
263+
"address": "bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4",
264+
"exception": "has no matching Script"
265+
},
266+
{
267+
"address": "BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R",
268+
"exception": "has no matching Script"
269+
},
270+
{
271+
"address": "bc1pw5dgrnzv",
272+
"exception": "has no matching Script"
273+
},
274+
{
275+
"address": "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav",
276+
"exception": "has no matching Script"
277+
},
278+
{
279+
"address": "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P",
280+
"exception": "has no matching Script"
196281
},
197282
{
283+
"address": "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq",
198284
"exception": "has no matching Script",
199-
"address": "bc1qqqqqqqqqqv9qus"
285+
"network": {
286+
"bech32": "tb"
287+
}
288+
},
289+
{
290+
"address": "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf",
291+
"exception": "has no matching Script"
292+
},
293+
{
294+
"address": "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j",
295+
"exception": "has no matching Script"
296+
},
297+
{
298+
"address": "bc1gmk9yu",
299+
"exception": "has no matching Script"
200300
}
201301
]
202302
}

0 commit comments

Comments
 (0)