Support address de/serialization from/to future bech32m outputs

This commit is contained in:
Andrew Toth 2021-02-16 16:48:05 -05:00
parent 40e73b4898
commit 2f7c83b286
2 changed files with 115 additions and 10 deletions

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

@ -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,
]);
} }
} }