Support address de/serialization from/to future bech32m outputs
This commit is contained in:
parent
40e73b4898
commit
2f7c83b286
2 changed files with 115 additions and 10 deletions
|
@ -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');
|
||||||
|
|
|
@ -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,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue