Merge pull request #1676 from andrewtoth/bech32m
Support BIP350 bech32m address serialization and future segwit versions
This commit is contained in:
commit
239711bf4e
10 changed files with 242 additions and 37 deletions
|
@ -146,7 +146,7 @@ npm run-script coverage
|
|||
- [BIP69](https://github.com/bitcoinjs/bip69) - Lexicographical Indexing of Transaction Inputs and Outputs
|
||||
- [Base58](https://github.com/cryptocoinjs/bs58) - Base58 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.
|
||||
- [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
|
||||
|
|
6
package-lock.json
generated
6
package-lock.json
generated
|
@ -348,9 +348,9 @@
|
|||
}
|
||||
},
|
||||
"bech32": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.3.tgz",
|
||||
"integrity": "sha512-yuVFUvrNcoJi0sv5phmqc6P+Fl1HjRDRNOOkHY2X/3LBy2bIGNSFx4fZ95HMaXHupuS7cZR15AsvtmCIF4UEyg=="
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz",
|
||||
"integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg=="
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.0.0",
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
"types"
|
||||
],
|
||||
"dependencies": {
|
||||
"bech32": "^1.1.2",
|
||||
"bech32": "^2.0.0",
|
||||
"bip174": "^2.0.1",
|
||||
"bip32": "^2.0.4",
|
||||
"bip66": "^1.1.0",
|
||||
|
|
|
@ -4,9 +4,31 @@ const networks = require('./networks');
|
|||
const payments = require('./payments');
|
||||
const bscript = require('./script');
|
||||
const types = require('./types');
|
||||
const bech32 = require('bech32');
|
||||
const { bech32, bech32m } = require('bech32');
|
||||
const bs58check = require('bs58check');
|
||||
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) {
|
||||
const payload = bs58check.decode(address);
|
||||
// TODO: 4.0.0, move to "toOutputScript"
|
||||
|
@ -18,10 +40,22 @@ function fromBase58Check(address) {
|
|||
}
|
||||
exports.fromBase58Check = fromBase58Check;
|
||||
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));
|
||||
return {
|
||||
version: result.words[0],
|
||||
version,
|
||||
prefix: result.prefix,
|
||||
data: Buffer.from(data),
|
||||
};
|
||||
|
@ -38,7 +72,9 @@ exports.toBase58Check = toBase58Check;
|
|||
function toBech32(data, version, prefix) {
|
||||
const words = bech32.toWords(data);
|
||||
words.unshift(version);
|
||||
return bech32.encode(prefix, words);
|
||||
return version === 0
|
||||
? bech32.encode(prefix, words)
|
||||
: bech32m.encode(prefix, words);
|
||||
}
|
||||
exports.toBech32 = toBech32;
|
||||
function fromOutputScript(output, network) {
|
||||
|
@ -56,6 +92,9 @@ function fromOutputScript(output, network) {
|
|||
try {
|
||||
return payments.p2wsh({ output, network }).address;
|
||||
} catch (e) {}
|
||||
try {
|
||||
return _toFutureSegwitAddress(output, network);
|
||||
} catch (e) {}
|
||||
throw new Error(bscript.toASM(output) + ' has no matching Address');
|
||||
}
|
||||
exports.fromOutputScript = fromOutputScript;
|
||||
|
@ -83,7 +122,16 @@ function toOutputScript(address, network) {
|
|||
return payments.p2wpkh({ hash: decodeBech32.data }).output;
|
||||
if (decodeBech32.data.length === 32)
|
||||
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');
|
||||
|
|
|
@ -7,7 +7,7 @@ const lazy = require('./lazy');
|
|||
const typef = require('typeforce');
|
||||
const OPS = bscript.OPS;
|
||||
const ecc = require('tiny-secp256k1');
|
||||
const bech32 = require('bech32');
|
||||
const { bech32 } = require('bech32');
|
||||
const EMPTY_BUFFER = Buffer.alloc(0);
|
||||
// witness: {signature} {pubKey}
|
||||
// input: <>
|
||||
|
|
|
@ -7,7 +7,7 @@ const lazy = require('./lazy');
|
|||
const typef = require('typeforce');
|
||||
const OPS = bscript.OPS;
|
||||
const ecc = require('tiny-secp256k1');
|
||||
const bech32 = require('bech32');
|
||||
const { bech32 } = require('bech32');
|
||||
const EMPTY_BUFFER = Buffer.alloc(0);
|
||||
function stacksEqual(a, b) {
|
||||
if (a.length !== b.length) return false;
|
||||
|
|
136
test/fixtures/address.json
vendored
136
test/fixtures/address.json
vendored
|
@ -77,23 +77,58 @@
|
|||
"bech32": "bcrt1qqqqqqqqqqqqqqahrwf6d62emdxmpq8gu3xe9au9fjwc9sxxn4k2qujfh7u",
|
||||
"data": "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": [
|
||||
{
|
||||
"address": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx",
|
||||
"address": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y",
|
||||
"version": 1,
|
||||
"prefix": "bc",
|
||||
"data": "751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"
|
||||
},
|
||||
{
|
||||
"address": "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj",
|
||||
"address": "bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs",
|
||||
"version": 2,
|
||||
"prefix": "bc",
|
||||
"data": "751e76e8199196d454941c45d1b3a323"
|
||||
},
|
||||
{
|
||||
"address": "BC1SW50QA3JX3S",
|
||||
"address": "BC1SW50QGDZ25J",
|
||||
"version": 16,
|
||||
"prefix": "bc",
|
||||
"data": "751e"
|
||||
|
@ -110,16 +145,24 @@
|
|||
"exception": "Mixed-case string"
|
||||
},
|
||||
{
|
||||
"address": "tb1pw508d6qejxtdg4y5r3zarqfsj6c3",
|
||||
"address": "tb1pw508d6qejxtdg4y5r3zarquvzkan",
|
||||
"exception": "Excess padding"
|
||||
},
|
||||
{
|
||||
"address": "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du",
|
||||
"address": "bc1zw508d6qejxtdg4y5r3zarvaryvq37eag7",
|
||||
"exception": "Excess padding"
|
||||
},
|
||||
{
|
||||
"address": "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv",
|
||||
"exception": "Non-zero padding"
|
||||
},
|
||||
{
|
||||
"address": "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du",
|
||||
"exception": "uses wrong encoding"
|
||||
},
|
||||
{
|
||||
"address": "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd",
|
||||
"exception": "uses wrong encoding"
|
||||
}
|
||||
],
|
||||
"fromBase58Check": [
|
||||
|
@ -161,7 +204,7 @@
|
|||
},
|
||||
{
|
||||
"exception": "has an invalid prefix",
|
||||
"address": "BC1SW50QA3JX3S",
|
||||
"address": "BC1SW50QGDZ25J",
|
||||
"network": {
|
||||
"bech32": "foo"
|
||||
}
|
||||
|
@ -170,18 +213,6 @@
|
|||
"exception": "has no matching Script",
|
||||
"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",
|
||||
"address": "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90"
|
||||
|
@ -197,6 +228,75 @@
|
|||
{
|
||||
"exception": "has no matching Script",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import * as payments from './payments';
|
|||
import * as bscript from './script';
|
||||
import * as types from './types';
|
||||
|
||||
const bech32 = require('bech32');
|
||||
const { bech32, bech32m } = require('bech32');
|
||||
const bs58check = require('bs58check');
|
||||
const typeforce = require('typeforce');
|
||||
|
||||
|
@ -19,6 +19,35 @@ export interface Bech32Result {
|
|||
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 {
|
||||
const payload = bs58check.decode(address);
|
||||
|
||||
|
@ -33,11 +62,25 @@ export function fromBase58Check(address: string): Base58CheckResult {
|
|||
}
|
||||
|
||||
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));
|
||||
|
||||
return {
|
||||
version: result.words[0],
|
||||
version,
|
||||
prefix: result.prefix,
|
||||
data: Buffer.from(data),
|
||||
};
|
||||
|
@ -61,7 +104,9 @@ export function toBech32(
|
|||
const words = bech32.toWords(data);
|
||||
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 {
|
||||
|
@ -80,6 +125,9 @@ export function fromOutputScript(output: Buffer, network?: Network): string {
|
|||
try {
|
||||
return payments.p2wsh({ output, network }).address as string;
|
||||
} catch (e) {}
|
||||
try {
|
||||
return _toFutureSegwitAddress(output, network);
|
||||
} catch (e) {}
|
||||
|
||||
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;
|
||||
if (decodeBech32.data.length === 32)
|
||||
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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ const typef = require('typeforce');
|
|||
const OPS = bscript.OPS;
|
||||
const ecc = require('tiny-secp256k1');
|
||||
|
||||
const bech32 = require('bech32');
|
||||
const { bech32 } = require('bech32');
|
||||
|
||||
const EMPTY_BUFFER = Buffer.alloc(0);
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ const typef = require('typeforce');
|
|||
const OPS = bscript.OPS;
|
||||
const ecc = require('tiny-secp256k1');
|
||||
|
||||
const bech32 = require('bech32');
|
||||
const { bech32 } = require('bech32');
|
||||
|
||||
const EMPTY_BUFFER = Buffer.alloc(0);
|
||||
|
||||
|
|
Loading…
Reference in a new issue