Merge pull request #1676 from andrewtoth/bech32m

Support BIP350 bech32m address serialization and future segwit versions
This commit is contained in:
Jonathan Underwood 2021-02-19 21:05:28 +09:00 committed by GitHub
commit 239711bf4e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 242 additions and 37 deletions

View file

@ -146,7 +146,7 @@ npm run-script coverage
- [BIP69](https://github.com/bitcoinjs/bip69) - Lexicographical Indexing of Transaction Inputs and Outputs - [BIP69](https://github.com/bitcoinjs/bip69) - Lexicographical Indexing of Transaction Inputs and Outputs
- [Base58](https://github.com/cryptocoinjs/bs58) - Base58 encoding/decoding - [Base58](https://github.com/cryptocoinjs/bs58) - Base58 encoding/decoding
- [Base58 Check](https://github.com/bitcoinjs/bs58check) - Base58 check encoding/decoding - [Base58 Check](https://github.com/bitcoinjs/bs58check) - Base58 check encoding/decoding
- [Bech32](https://github.com/bitcoinjs/bech32) - A BIP173 compliant Bech32 encoding library - [Bech32](https://github.com/bitcoinjs/bech32) - A BIP173/BIP350 compliant Bech32/Bech32m encoding library
- [coinselect](https://github.com/bitcoinjs/coinselect) - A fee-optimizing, transaction input selection module for bitcoinjs-lib. - [coinselect](https://github.com/bitcoinjs/coinselect) - A fee-optimizing, transaction input selection module for bitcoinjs-lib.
- [merkle-lib](https://github.com/bitcoinjs/merkle-lib) - A performance conscious library for merkle root and tree calculations. - [merkle-lib](https://github.com/bitcoinjs/merkle-lib) - A performance conscious library for merkle root and tree calculations.
- [minimaldata](https://github.com/bitcoinjs/minimaldata) - A module to check bitcoin policy: SCRIPT_VERIFY_MINIMALDATA - [minimaldata](https://github.com/bitcoinjs/minimaldata) - A module to check bitcoin policy: SCRIPT_VERIFY_MINIMALDATA

6
package-lock.json generated
View file

@ -348,9 +348,9 @@
} }
}, },
"bech32": { "bech32": {
"version": "1.1.3", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.3.tgz", "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz",
"integrity": "sha512-yuVFUvrNcoJi0sv5phmqc6P+Fl1HjRDRNOOkHY2X/3LBy2bIGNSFx4fZ95HMaXHupuS7cZR15AsvtmCIF4UEyg==" "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg=="
}, },
"binary-extensions": { "binary-extensions": {
"version": "2.0.0", "version": "2.0.0",

View file

@ -49,7 +49,7 @@
"types" "types"
], ],
"dependencies": { "dependencies": {
"bech32": "^1.1.2", "bech32": "^2.0.0",
"bip174": "^2.0.1", "bip174": "^2.0.1",
"bip32": "^2.0.4", "bip32": "^2.0.4",
"bip66": "^1.1.0", "bip66": "^1.1.0",

View file

@ -4,9 +4,31 @@ const networks = require('./networks');
const payments = require('./payments'); const payments = require('./payments');
const bscript = require('./script'); const bscript = require('./script');
const types = require('./types'); const types = require('./types');
const bech32 = require('bech32'); const { bech32, bech32m } = require('bech32');
const bs58check = require('bs58check'); const bs58check = require('bs58check');
const typeforce = require('typeforce'); const typeforce = require('typeforce');
const FUTURE_SEGWIT_MAX_SIZE = 40;
const FUTURE_SEGWIT_MIN_SIZE = 2;
const FUTURE_SEGWIT_MAX_VERSION = 16;
const FUTURE_SEGWIT_MIN_VERSION = 1;
const FUTURE_SEGWIT_VERSION_DIFF = 0x50;
function _toFutureSegwitAddress(output, network) {
const data = output.slice(2);
if (
data.length < FUTURE_SEGWIT_MIN_SIZE ||
data.length > FUTURE_SEGWIT_MAX_SIZE
)
throw new TypeError('Invalid program length for segwit address');
const version = output[0] - FUTURE_SEGWIT_VERSION_DIFF;
if (
version < FUTURE_SEGWIT_MIN_VERSION ||
version > FUTURE_SEGWIT_MAX_VERSION
)
throw new TypeError('Invalid version for segwit address');
if (output[1] !== data.length)
throw new TypeError('Invalid script for segwit address');
return toBech32(data, version, network.bech32);
}
function fromBase58Check(address) { function fromBase58Check(address) {
const payload = bs58check.decode(address); const payload = bs58check.decode(address);
// TODO: 4.0.0, move to "toOutputScript" // TODO: 4.0.0, move to "toOutputScript"
@ -18,10 +40,22 @@ function fromBase58Check(address) {
} }
exports.fromBase58Check = fromBase58Check; exports.fromBase58Check = fromBase58Check;
function fromBech32(address) { function fromBech32(address) {
const result = bech32.decode(address); let result;
let version;
try {
result = bech32.decode(address);
} catch (e) {}
if (result) {
version = result.words[0];
if (version !== 0) throw new TypeError(address + ' uses wrong encoding');
} else {
result = bech32m.decode(address);
version = result.words[0];
if (version === 0) throw new TypeError(address + ' uses wrong encoding');
}
const data = bech32.fromWords(result.words.slice(1)); const data = bech32.fromWords(result.words.slice(1));
return { return {
version: result.words[0], version,
prefix: result.prefix, prefix: result.prefix,
data: Buffer.from(data), data: Buffer.from(data),
}; };
@ -38,7 +72,9 @@ exports.toBase58Check = toBase58Check;
function toBech32(data, version, prefix) { function toBech32(data, version, prefix) {
const words = bech32.toWords(data); const words = bech32.toWords(data);
words.unshift(version); words.unshift(version);
return bech32.encode(prefix, words); return version === 0
? bech32.encode(prefix, words)
: bech32m.encode(prefix, words);
} }
exports.toBech32 = toBech32; exports.toBech32 = toBech32;
function fromOutputScript(output, network) { function fromOutputScript(output, network) {
@ -56,6 +92,9 @@ function fromOutputScript(output, network) {
try { try {
return payments.p2wsh({ output, network }).address; return payments.p2wsh({ output, network }).address;
} catch (e) {} } catch (e) {}
try {
return _toFutureSegwitAddress(output, network);
} catch (e) {}
throw new Error(bscript.toASM(output) + ' has no matching Address'); throw new Error(bscript.toASM(output) + ' has no matching Address');
} }
exports.fromOutputScript = fromOutputScript; exports.fromOutputScript = fromOutputScript;
@ -83,7 +122,16 @@ function toOutputScript(address, network) {
return payments.p2wpkh({ hash: decodeBech32.data }).output; return payments.p2wpkh({ hash: decodeBech32.data }).output;
if (decodeBech32.data.length === 32) if (decodeBech32.data.length === 32)
return payments.p2wsh({ hash: decodeBech32.data }).output; return payments.p2wsh({ hash: decodeBech32.data }).output;
} } else if (
decodeBech32.version >= FUTURE_SEGWIT_MIN_VERSION &&
decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION &&
decodeBech32.data.length >= FUTURE_SEGWIT_MIN_SIZE &&
decodeBech32.data.length <= FUTURE_SEGWIT_MAX_SIZE
)
return bscript.compile([
decodeBech32.version + FUTURE_SEGWIT_VERSION_DIFF,
decodeBech32.data,
]);
} }
} }
throw new Error(address + ' has no matching Script'); throw new Error(address + ' has no matching Script');

View file

@ -7,7 +7,7 @@ const lazy = require('./lazy');
const typef = require('typeforce'); const typef = require('typeforce');
const OPS = bscript.OPS; const OPS = bscript.OPS;
const ecc = require('tiny-secp256k1'); const ecc = require('tiny-secp256k1');
const bech32 = require('bech32'); const { bech32 } = require('bech32');
const EMPTY_BUFFER = Buffer.alloc(0); const EMPTY_BUFFER = Buffer.alloc(0);
// witness: {signature} {pubKey} // witness: {signature} {pubKey}
// input: <> // input: <>

View file

@ -7,7 +7,7 @@ const lazy = require('./lazy');
const typef = require('typeforce'); const typef = require('typeforce');
const OPS = bscript.OPS; const OPS = bscript.OPS;
const ecc = require('tiny-secp256k1'); const ecc = require('tiny-secp256k1');
const bech32 = require('bech32'); const { bech32 } = require('bech32');
const EMPTY_BUFFER = Buffer.alloc(0); const EMPTY_BUFFER = Buffer.alloc(0);
function stacksEqual(a, b) { function stacksEqual(a, b) {
if (a.length !== b.length) return false; if (a.length !== b.length) return false;

View file

@ -77,23 +77,58 @@
"bech32": "bcrt1qqqqqqqqqqqqqqahrwf6d62emdxmpq8gu3xe9au9fjwc9sxxn4k2qujfh7u", "bech32": "bcrt1qqqqqqqqqqqqqqahrwf6d62emdxmpq8gu3xe9au9fjwc9sxxn4k2qujfh7u",
"data": "000000000000000076e37274dd2b3b69b6101d1c89b25ef0a993b05818d3ad94", "data": "000000000000000076e37274dd2b3b69b6101d1c89b25ef0a993b05818d3ad94",
"script": "OP_0 000000000000000076e37274dd2b3b69b6101d1c89b25ef0a993b05818d3ad94" "script": "OP_0 000000000000000076e37274dd2b3b69b6101d1c89b25ef0a993b05818d3ad94"
},
{
"network": "bitcoin",
"bech32": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y",
"version": 1,
"data": "751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6",
"script": "OP_1 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"
},
{
"network": "bitcoin",
"bech32": "BC1SW50QGDZ25J",
"version": 16,
"data": "751e",
"script": "OP_16 751e"
},
{
"network": "bitcoin",
"bech32": "bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs",
"version": 2,
"data": "751e76e8199196d454941c45d1b3a323",
"script": "OP_2 751e76e8199196d454941c45d1b3a323"
},
{
"network": "testnet",
"bech32": "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c",
"version": 1,
"data": "000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
"script": "OP_1 000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"
},
{
"network": "bitcoin",
"bech32": "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0",
"version": 1,
"data": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"script": "OP_1 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
} }
], ],
"bech32": [ "bech32": [
{ {
"address": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", "address": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y",
"version": 1, "version": 1,
"prefix": "bc", "prefix": "bc",
"data": "751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6" "data": "751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"
}, },
{ {
"address": "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", "address": "bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs",
"version": 2, "version": 2,
"prefix": "bc", "prefix": "bc",
"data": "751e76e8199196d454941c45d1b3a323" "data": "751e76e8199196d454941c45d1b3a323"
}, },
{ {
"address": "BC1SW50QA3JX3S", "address": "BC1SW50QGDZ25J",
"version": 16, "version": 16,
"prefix": "bc", "prefix": "bc",
"data": "751e" "data": "751e"
@ -110,16 +145,24 @@
"exception": "Mixed-case string" "exception": "Mixed-case string"
}, },
{ {
"address": "tb1pw508d6qejxtdg4y5r3zarqfsj6c3", "address": "tb1pw508d6qejxtdg4y5r3zarquvzkan",
"exception": "Excess padding" "exception": "Excess padding"
}, },
{ {
"address": "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", "address": "bc1zw508d6qejxtdg4y5r3zarvaryvq37eag7",
"exception": "Excess padding" "exception": "Excess padding"
}, },
{ {
"address": "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", "address": "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv",
"exception": "Non-zero padding" "exception": "Non-zero padding"
},
{
"address": "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du",
"exception": "uses wrong encoding"
},
{
"address": "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd",
"exception": "uses wrong encoding"
} }
], ],
"fromBase58Check": [ "fromBase58Check": [
@ -161,7 +204,7 @@
}, },
{ {
"exception": "has an invalid prefix", "exception": "has an invalid prefix",
"address": "BC1SW50QA3JX3S", "address": "BC1SW50QGDZ25J",
"network": { "network": {
"bech32": "foo" "bech32": "foo"
} }
@ -170,18 +213,6 @@
"exception": "has no matching Script", "exception": "has no matching Script",
"address": "bc1rw5uspcuh" "address": "bc1rw5uspcuh"
}, },
{
"exception": "has no matching Script",
"address": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx"
},
{
"exception": "has no matching Script",
"address": "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj"
},
{
"exception": "has no matching Script",
"address": "BC1SW50QA3JX3S"
},
{ {
"exception": "has no matching Script", "exception": "has no matching Script",
"address": "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90" "address": "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90"
@ -197,6 +228,75 @@
{ {
"exception": "has no matching Script", "exception": "has no matching Script",
"address": "bc1qqqqqqqqqqv9qus" "address": "bc1qqqqqqqqqqv9qus"
},
{
"address": "tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut",
"exception": "has an invalid prefix"
},
{
"address": "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd",
"exception": "has no matching Script"
},
{
"address": "tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf",
"exception": "has no matching Script",
"network": {
"bech32": "tb"
}
},
{
"address": "BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL",
"exception": "has no matching Script"
},
{
"address": "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh",
"exception": "has no matching Script"
},
{
"address": "tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47",
"exception": "has no matching Script",
"network": {
"bech32": "tb"
}
},
{
"address": "bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4",
"exception": "has no matching Script"
},
{
"address": "BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R",
"exception": "has no matching Script"
},
{
"address": "bc1pw5dgrnzv",
"exception": "has no matching Script"
},
{
"address": "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav",
"exception": "has no matching Script"
},
{
"address": "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P",
"exception": "has no matching Script"
},
{
"address": "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq",
"exception": "has no matching Script",
"network": {
"bech32": "tb"
}
},
{
"address": "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf",
"exception": "has no matching Script"
},
{
"address": "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j",
"exception": "has no matching Script"
},
{
"address": "bc1gmk9yu",
"exception": "has no matching Script"
} }
] ]
} }

View file

@ -4,7 +4,7 @@ import * as payments from './payments';
import * as bscript from './script'; import * as bscript from './script';
import * as types from './types'; import * as types from './types';
const bech32 = require('bech32'); const { bech32, bech32m } = require('bech32');
const bs58check = require('bs58check'); const bs58check = require('bs58check');
const typeforce = require('typeforce'); const typeforce = require('typeforce');
@ -19,6 +19,35 @@ export interface Bech32Result {
data: Buffer; data: Buffer;
} }
const FUTURE_SEGWIT_MAX_SIZE: number = 40;
const FUTURE_SEGWIT_MIN_SIZE: number = 2;
const FUTURE_SEGWIT_MAX_VERSION: number = 16;
const FUTURE_SEGWIT_MIN_VERSION: number = 1;
const FUTURE_SEGWIT_VERSION_DIFF: number = 0x50;
function _toFutureSegwitAddress(output: Buffer, network: Network): string {
const data = output.slice(2);
if (
data.length < FUTURE_SEGWIT_MIN_SIZE ||
data.length > FUTURE_SEGWIT_MAX_SIZE
)
throw new TypeError('Invalid program length for segwit address');
const version = output[0] - FUTURE_SEGWIT_VERSION_DIFF;
if (
version < FUTURE_SEGWIT_MIN_VERSION ||
version > FUTURE_SEGWIT_MAX_VERSION
)
throw new TypeError('Invalid version for segwit address');
if (output[1] !== data.length)
throw new TypeError('Invalid script for segwit address');
return toBech32(data, version, network.bech32);
}
export function fromBase58Check(address: string): Base58CheckResult { export function fromBase58Check(address: string): Base58CheckResult {
const payload = bs58check.decode(address); const payload = bs58check.decode(address);
@ -33,11 +62,25 @@ export function fromBase58Check(address: string): Base58CheckResult {
} }
export function fromBech32(address: string): Bech32Result { export function fromBech32(address: string): Bech32Result {
const result = bech32.decode(address); let result;
let version;
try {
result = bech32.decode(address);
} catch (e) {}
if (result) {
version = result.words[0];
if (version !== 0) throw new TypeError(address + ' uses wrong encoding');
} else {
result = bech32m.decode(address);
version = result.words[0];
if (version === 0) throw new TypeError(address + ' uses wrong encoding');
}
const data = bech32.fromWords(result.words.slice(1)); const data = bech32.fromWords(result.words.slice(1));
return { return {
version: result.words[0], version,
prefix: result.prefix, prefix: result.prefix,
data: Buffer.from(data), data: Buffer.from(data),
}; };
@ -61,7 +104,9 @@ export function toBech32(
const words = bech32.toWords(data); const words = bech32.toWords(data);
words.unshift(version); words.unshift(version);
return bech32.encode(prefix, words); return version === 0
? bech32.encode(prefix, words)
: bech32m.encode(prefix, words);
} }
export function fromOutputScript(output: Buffer, network?: Network): string { export function fromOutputScript(output: Buffer, network?: Network): string {
@ -80,6 +125,9 @@ export function fromOutputScript(output: Buffer, network?: Network): string {
try { try {
return payments.p2wsh({ output, network }).address as string; return payments.p2wsh({ output, network }).address as string;
} catch (e) {} } catch (e) {}
try {
return _toFutureSegwitAddress(output, network);
} catch (e) {}
throw new Error(bscript.toASM(output) + ' has no matching Address'); throw new Error(bscript.toASM(output) + ' has no matching Address');
} }
@ -111,7 +159,16 @@ export function toOutputScript(address: string, network?: Network): Buffer {
return payments.p2wpkh({ hash: decodeBech32.data }).output as Buffer; return payments.p2wpkh({ hash: decodeBech32.data }).output as Buffer;
if (decodeBech32.data.length === 32) if (decodeBech32.data.length === 32)
return payments.p2wsh({ hash: decodeBech32.data }).output as Buffer; return payments.p2wsh({ hash: decodeBech32.data }).output as Buffer;
} } else if (
decodeBech32.version >= FUTURE_SEGWIT_MIN_VERSION &&
decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION &&
decodeBech32.data.length >= FUTURE_SEGWIT_MIN_SIZE &&
decodeBech32.data.length <= FUTURE_SEGWIT_MAX_SIZE
)
return bscript.compile([
decodeBech32.version + FUTURE_SEGWIT_VERSION_DIFF,
decodeBech32.data,
]);
} }
} }

View file

@ -7,7 +7,7 @@ const typef = require('typeforce');
const OPS = bscript.OPS; const OPS = bscript.OPS;
const ecc = require('tiny-secp256k1'); const ecc = require('tiny-secp256k1');
const bech32 = require('bech32'); const { bech32 } = require('bech32');
const EMPTY_BUFFER = Buffer.alloc(0); const EMPTY_BUFFER = Buffer.alloc(0);

View file

@ -7,7 +7,7 @@ const typef = require('typeforce');
const OPS = bscript.OPS; const OPS = bscript.OPS;
const ecc = require('tiny-secp256k1'); const ecc = require('tiny-secp256k1');
const bech32 = require('bech32'); const { bech32 } = require('bech32');
const EMPTY_BUFFER = Buffer.alloc(0); const EMPTY_BUFFER = Buffer.alloc(0);