Skip to content

Commit 2f7c83b

Browse files
committed
Support address de/serialization from/to future bech32m outputs
1 parent 40e73b4 commit 2f7c83b

File tree

2 files changed

+115
-10
lines changed

2 files changed

+115
-10
lines changed

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');

ts_src/address.ts

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as payments from './payments';
44
import * as bscript from './script';
55
import * as types from './types';
66

7-
const { bech32 } = require('bech32');
7+
const { bech32, bech32m } = require('bech32');
88
const bs58check = require('bs58check');
99
const typeforce = require('typeforce');
1010

@@ -19,6 +19,35 @@ export interface Bech32Result {
1919
data: Buffer;
2020
}
2121

22+
const FUTURE_SEGWIT_MAX_SIZE: number = 40;
23+
const FUTURE_SEGWIT_MIN_SIZE: number = 2;
24+
const FUTURE_SEGWIT_MAX_VERSION: number = 16;
25+
const FUTURE_SEGWIT_MIN_VERSION: number = 1;
26+
const FUTURE_SEGWIT_VERSION_DIFF: number = 0x50;
27+
28+
function _toFutureSegwitAddress(output: Buffer, network: Network): string {
29+
const data = output.slice(2);
30+
31+
if (
32+
data.length < FUTURE_SEGWIT_MIN_SIZE ||
33+
data.length > FUTURE_SEGWIT_MAX_SIZE
34+
)
35+
throw new TypeError('Invalid program length for segwit address');
36+
37+
const version = output[0] - FUTURE_SEGWIT_VERSION_DIFF;
38+
39+
if (
40+
version < FUTURE_SEGWIT_MIN_VERSION ||
41+
version > FUTURE_SEGWIT_MAX_VERSION
42+
)
43+
throw new TypeError('Invalid version for segwit address');
44+
45+
if (output[1] !== data.length)
46+
throw new TypeError('Invalid script for segwit address');
47+
48+
return toBech32(data, version, network.bech32);
49+
}
50+
2251
export function fromBase58Check(address: string): Base58CheckResult {
2352
const payload = bs58check.decode(address);
2453

@@ -33,11 +62,25 @@ export function fromBase58Check(address: string): Base58CheckResult {
3362
}
3463

3564
export function fromBech32(address: string): Bech32Result {
36-
const result = bech32.decode(address);
65+
let result;
66+
let version;
67+
try {
68+
result = bech32.decode(address);
69+
} catch (e) {}
70+
71+
if (result) {
72+
version = result.words[0];
73+
if (version !== 0) throw new TypeError(address + ' uses wrong encoding');
74+
} else {
75+
result = bech32m.decode(address);
76+
version = result.words[0];
77+
if (version === 0) throw new TypeError(address + ' uses wrong encoding');
78+
}
79+
3780
const data = bech32.fromWords(result.words.slice(1));
3881

3982
return {
40-
version: result.words[0],
83+
version,
4184
prefix: result.prefix,
4285
data: Buffer.from(data),
4386
};
@@ -61,7 +104,9 @@ export function toBech32(
61104
const words = bech32.toWords(data);
62105
words.unshift(version);
63106

64-
return bech32.encode(prefix, words);
107+
return version === 0
108+
? bech32.encode(prefix, words)
109+
: bech32m.encode(prefix, words);
65110
}
66111

67112
export function fromOutputScript(output: Buffer, network?: Network): string {
@@ -80,6 +125,9 @@ export function fromOutputScript(output: Buffer, network?: Network): string {
80125
try {
81126
return payments.p2wsh({ output, network }).address as string;
82127
} catch (e) {}
128+
try {
129+
return _toFutureSegwitAddress(output, network);
130+
} catch (e) {}
83131

84132
throw new Error(bscript.toASM(output) + ' has no matching Address');
85133
}
@@ -111,7 +159,16 @@ export function toOutputScript(address: string, network?: Network): Buffer {
111159
return payments.p2wpkh({ hash: decodeBech32.data }).output as Buffer;
112160
if (decodeBech32.data.length === 32)
113161
return payments.p2wsh({ hash: decodeBech32.data }).output as Buffer;
114-
}
162+
} else if (
163+
decodeBech32.version >= FUTURE_SEGWIT_MIN_VERSION &&
164+
decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION &&
165+
decodeBech32.data.length >= FUTURE_SEGWIT_MIN_SIZE &&
166+
decodeBech32.data.length <= FUTURE_SEGWIT_MAX_SIZE
167+
)
168+
return bscript.compile([
169+
decodeBech32.version + FUTURE_SEGWIT_VERSION_DIFF,
170+
decodeBech32.data,
171+
]);
115172
}
116173
}
117174

0 commit comments

Comments
 (0)