Skip to content

Commit cc1cc2c

Browse files
sipajesseposner
authored andcommitted
Add support for Bech32m
1 parent d8a50fa commit cc1cc2c

File tree

17 files changed

+847
-302
lines changed

17 files changed

+847
-302
lines changed

demo/demo.html

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@
3030
function update_status() {
3131
var address = document.getElementById("address").value;
3232
var res = segwit_addr_ecc.check(address, ["bc", "tb"]);
33-
var cp = "";
33+
var cp = "<br/>";
3434
if (res.error === null) {
3535
document.getElementById("result").innerHTML = "<a style=\"color:green\">Ok, witness version " + res.version + ", program " + toHexString(res.program) + "</a>";
36-
cp = "<br/>";
3736
} else {
3837
document.getElementById("result").innerHTML = res.error;
3938
if (res.pos !== null) {
39+
cp = "";
4040
for (var p = 0; p < address.length; ++p) {
4141
if (res.pos.indexOf(p) != -1) {
4242
cp += "<a style=\"color:red\">" + address.charAt(p) + "</a>";
@@ -59,11 +59,11 @@
5959
<div class="card mb-3 text-left">
6060
<h3 class="card-header">SegWit address format</h3>
6161
<div class="card-block">
62-
<p><a href="https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki">BIP 173</a> is a proposal for a new SegWit address format to replace <a href="https://github.com/bitcoin/bips/blob/master/bip-0142.mediawiki">BIP 142</a>.
62+
<p><a href="https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki">BIP 173</a> defines the address format used for native segwit outputs.
6363
This format is not required for using segwit, but is more
64-
efficient, flexible, and nicer to use.</p>
64+
efficient, flexible, and nicer to use than the compatibility P2SH wrapper format.</p>
6565

66-
<p>The format is base 32 and uses a simple checksum algorithm with strong
66+
<p>The used Bech32 encoding is generally usable and uses a simple checksum algorithm with strong
6767
error detection properties. Reference code in several languages as
6868
well as a website demonstrating it are included.</p>
6969

@@ -77,6 +77,20 @@ <h3 class="card-header">SegWit address format</h3>
7777
</div>
7878
</div>
7979

80+
<div class="card mb-3 text-left">
81+
<h3 class="card-header">Bech32m for version 1 witnesses and higher</h3>
82+
<div class="card-block">
83+
<p>To address <a href="https://github.com/sipa/bech32/issues/51">weaknesses</a> discovered in Bech32, <a href="https://github.com/sipa/bips/blob/bip-bech32m/bip-0350.mediawiki">BIP 350</a> proposes using an improved format called Bech32m
84+
for addresses for witness versions 1 and higher. Such addresses would be used by the <a href="https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki">Taproot</a> proposal.</p>
85+
86+
<ul>
87+
<li><a href="https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2020-October/018236.html">Mailinglist discussion</a></li>
88+
<li><a href="https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-January/018338.html">BIP discussion</a></li>
89+
<li><a href="https://github.com/bitcoin/bips/pull/1056">BIP PR</a></li>
90+
</ul>
91+
</div>
92+
</div>
93+
8094
<div class="card text-left">
8195
<h3 class="card-header">Decoder demo</h3>
8296
<div class="card-block">
@@ -93,6 +107,8 @@ <h3 class="card-header">Decoder demo</h3>
93107
<li> <a class="demo_link" href='javascript:load_addr("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4");'>P2WPKH example</a>
94108
<li> <a class="demo_link" href='javascript:load_addr("BC1QW508D6QEJXTDG4Y5R3ZARVAYR0C5XW7KV8F3T4");'>P2WPKH example with errors</a>
95109
<li> <a class="demo_link" href='javascript:load_addr("bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3");'>P2WSH example</a>
110+
<li> <a class="demo_link" href='javascript:load_addr("bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0");'>P2TR example (proposed)</a>
111+
<li> <a class="demo_link" href='javascript:load_addr("bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd");'>P2TR example with errors (using Bech32 instead of Bech32m)</a>
96112
</ul>
97113
</div>
98114
</div>

demo/demo.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ecc/javascript/bech32_ecc.js

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,24 @@ var GF1024_LOG = [
147147
452, 710, 552, 128, 612, 600, 275, 322, 193
148148
];
149149

150+
const encodings = {
151+
BECH32: "bech32",
152+
BECH32M: "bech32m",
153+
};
154+
155+
function getEncodingConst (encoding) {
156+
if (encoding == encodings.BECH32) {
157+
return 1;
158+
} else if (encoding == encodings.BECH32M) {
159+
return 0x2bc830a3;
160+
} else {
161+
return null;
162+
}
163+
}
164+
150165
module.exports = {
151166
check: check,
167+
encodings: encodings,
152168
};
153169

154170
function syndrome (residue) {
@@ -258,7 +274,7 @@ function range (from, to) {
258274
return ret;
259275
}
260276

261-
function check (bechString, validHrp) {
277+
function check (bechString, validHrp, encoding) {
262278
if (bechString.length > 90) {
263279
return {error:"Too long", pos:range(90, bechString.length)};
264280
}
@@ -298,16 +314,19 @@ function check (bechString, validHrp) {
298314
if (validHrp.indexOf(hrp) == -1) {
299315
return {error:"Unknown part before the separator '1'", pos:range(0, hrp.length)};
300316
}
301-
var residue = polymod(hrpExpand(hrp).concat(data)) ^ 1;
317+
var residue = polymod(hrpExpand(hrp).concat(data)) ^ getEncodingConst(encoding);
302318
if (residue != 0) {
303-
var epos = locate_errors(residue, bechString.length - 1);
304-
if (epos.length == 0) {
305-
return {error:"Invalid", pos:null};
306-
}
307-
for (var ep = 0; ep < epos.length; ++ep) {
308-
epos[ep] = bechString.length - epos[ep] - (epos[ep] >= data.length ? 2 : 1);
319+
var epos = locate_errors(residue, data.length);
320+
if (epos.length == 0) return {error:"Invalid checksum", data_pattern:null};
321+
pattern = [];
322+
for (var pos = 0; pos < data.length; ++pos) {
323+
if (epos.includes(data.length - 1 - pos)) {
324+
pattern.push(-1);
325+
} else {
326+
pattern.push(data[pos]);
327+
}
309328
}
310-
return {error:"Invalid", pos:epos};
329+
return {error:"Invalid checksum", data_pattern:pattern};
311330
}
312331
return {error:null, hrp:hrp, data:data.slice(0, -6)};
313332
}

ecc/javascript/segwit_addr_ecc.js

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -58,25 +58,60 @@ function check (addr, validHrp) {
5858
if (addr.length > 74) {
5959
return {error:"Too long", pos:null};
6060
}
61-
if ((addr.length % 8) == 0 || (addr.length % 8) == 3 || (addr.length % 8) == 5) {
62-
return {error:"Invalid length", pos:null};
63-
}
64-
var dec = bech32_ecc.check(addr, validHrp);
65-
if (dec.error !== null) {
66-
return {error:dec.error, pos:dec.pos};
67-
}
68-
var res = convertbits(dec.data.slice(1), 5, 8, false);
69-
if (res === null) {
70-
return {error:"Padding error", pos:[addr.length - 6]};
71-
}
72-
if (res.length < 2 || res.length > 40) {
73-
return {error:"Invalid witness program length", pos:null};
61+
eposs = []
62+
for (var encname in bech32_ecc.encodings) {
63+
const encoding = bech32_ecc.encodings[encname];
64+
var dec = bech32_ecc.check(addr, validHrp, encoding);
65+
if ("data_pattern" in dec) {
66+
if (dec.data_pattern !== null) {
67+
const numbytes = ((dec.data_pattern.length - 6 - 1) * 5) >> 3;
68+
if (dec.data_pattern.length - 6 - 1 != ((numbytes * 8 + 4) / 5|0)) continue;
69+
if (dec.data_pattern[0] == 0 && encoding != bech32_ecc.encodings.BECH32) continue;
70+
if (dec.data_pattern[0] > 0 && dec.data_pattern[0] <= 16 && encoding != bech32_ecc.encodings.BECH32M) continue;
71+
if (dec.data_pattern[0] > 16) continue;
72+
if (dec.data_pattern[0] == 0 && numbytes != 20 && numbytes != 32) continue;
73+
var epos = [];
74+
for (var i = 0; i < dec.data_pattern.length; ++i) {
75+
if (dec.data_pattern[i] == -1) {
76+
epos.push(addr.length - dec.data_pattern.length + i);
77+
}
78+
}
79+
eposs.push(epos);
80+
}
81+
continue;
82+
}
83+
if (dec.error !== null) {
84+
return {error:dec.error, pos:dec.pos};
85+
}
86+
var res = convertbits(dec.data.slice(1), 5, 8, false);
87+
if (res === null) {
88+
return {error:"Padding error", pos:[addr.length - 6]};
89+
}
90+
if (res.length < 2 || res.length > 40) {
91+
return {error:"Invalid witness program length", pos:null};
92+
}
93+
if (dec.data[0] > 16) {
94+
return {error:"Invalid witness version", pos:[dec.hrp.length + 1]};
95+
}
96+
if (dec.data[0] == 0 && encoding != bech32_ecc.encodings.BECH32) {
97+
return {error:"Bech32 must be used for witness v0 programs", pos:null};
98+
}
99+
if (dec.data[0] != 0 && encoding != bech32_ecc.encodings.BECH32M) {
100+
return {error:"Bech32m must be used for witness v1+ programs", pos:null};
101+
}
102+
if (dec.data[0] === 0 && res.length !== 20 && res.length !== 32) {
103+
return {error:"Invalid witness program length for v0", pos:null};
104+
}
105+
return {error:null, version:dec.data[0], program:res};
74106
}
75-
if (dec.data[0] > 16) {
76-
return {error:"Invalid witness version", pos:[dec.hrp.length + 1]};
107+
if (eposs.length == 1) {
108+
return {error:"Checksum error", pos:eposs[0]};
77109
}
78-
if (dec.data[0] === 0 && res.length !== 20 && res.length !== 32) {
79-
return {error:"Invalid witness program length for v0", pos:null};
110+
if (eposs.length > 1) {
111+
eposs.sort(function (a,b) { return a.length - b.length; });
112+
if (eposs[0].length < eposs[1].length) {
113+
return {error:"Checksum error", pos:eposs[0]};
114+
}
80115
}
81-
return {error:null, version:dec.data[0], program:res};
116+
return {error:"Checksum error", pos:null};
82117
}

0 commit comments

Comments
 (0)