Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bech32m support #1038

Merged
merged 8 commits into from
Nov 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Bcoin release notes & changelog

## unreleased

- Support for bech32m has been added. bcoin can now validate and send BTC to
addresses for witness programs with versions > 0. The address indexer has
also been updated to retrieve the new addresses. A bug was fixed where the
indexer may return data for a witness version 0 address even if a version 1
address was queried, if the two addresses had the same data (hash). After
upgrading bcoin, a full rescan may be necessary to re-index these collisions
correctly. To accomplish this:
- Stop bcoin (`bcoin-cli rpc stop` or `ctrl-C`)
- Delete the existing addr indexer data (`rm -rf ~/.bcoin/index/addr`)
- Restart bcoin
- The address indexer will automatically begin re-indexing the chain. It may take up to 24 hours.

- The logging module `blgr` has been updated. Log files will now be rolled over
pinheadmz marked this conversation as resolved.
Show resolved Hide resolved
at around 20 MB and timestamped. Only the last 10 log files will be kept on disk
and older log files will be purged. These values can be configured by passing
`--log-max-file-size` (in MB) and `--log-max-files`.

## v2.1.2

- Fixed wallet RPC method `importprunedfunds`.
Expand Down
2 changes: 1 addition & 1 deletion bin/bcoin-cli
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class CLI {
async getTX() {
const hash = this.config.str(0, '');

if (hash.length !== 64) {
if (!/^[0-9a-f]{64}$/i.test(hash)) {
const txs = await this.client.getTXByAddress(hash);
this.log(txs);
return;
Expand Down
4 changes: 3 additions & 1 deletion lib/node/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ class Node extends EventEmitter {
: null,
level: config.str('log-level'),
console: config.bool('log-console'),
shrink: config.bool('log-shrink')
shrink: config.bool('log-shrink'),
maxFileSize: config.mb('log-max-file-size'),
maxFiles: config.uint('log-max-files')
});

this.logger = logger.context('node');
Expand Down
161 changes: 110 additions & 51 deletions lib/primitives/address.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const assert = require('bsert');
const bio = require('bufio');
const base58 = require('bcrypto/lib/encoding/base58');
const bech32 = require('bcrypto/lib/encoding/bech32');
const bech32m = require('bcrypto/lib/encoding/bech32m');
const sha256 = require('bcrypto/lib/sha256');
const hash160 = require('bcrypto/lib/hash160');
const hash256 = require('bcrypto/lib/hash256');
Expand Down Expand Up @@ -131,13 +132,30 @@ class Address {
return Address.typesByVal[this.type].toLowerCase();
}

/**
* Get prefix for indexers
* It's a single byte encoded as follows:
* 1 bit whether it's legacy or witness.
* 7 bits used for the data.
* @param {Network|String} network
* @returns {Number}
*/

getPrefix(network) {
if (this.isProgram())
return this.version;

// Note: -1 | 0x80 = -1
return 0x80 | this.getBase58Prefix(network);
}

/**
* Get a network address prefix for the address.
* @param {Network?} network
* @returns {Number}
*/

getPrefix(network) {
getBase58Prefix(network) {
network = Network.get(network);

const prefixes = network.addressPrefix;
Expand All @@ -147,14 +165,6 @@ class Address {
return prefixes.pubkeyhash;
case Address.types.SCRIPTHASH:
return prefixes.scripthash;
case Address.types.WITNESS:
if (this.hash.length === 20)
return prefixes.witnesspubkeyhash;

if (this.hash.length === 32)
return prefixes.witnessscripthash;

break;
}

return -1;
Expand Down Expand Up @@ -184,7 +194,7 @@ class Address {
toRaw(network) {
const size = this.getSize();
const bw = bio.write(size);
const prefix = this.getPrefix(network);
const prefix = this.getBase58Prefix(network);

assert(prefix !== -1, 'Not a valid address prefix.');

Expand Down Expand Up @@ -226,13 +236,40 @@ class Address {
assert(version !== -1,
'Cannot convert non-program address to bech32.');

assert(version === 0,
'Cannot convert program version > 0 to bech32 address.');

network = Network.get(network);

const hrp = network.addressPrefix.bech32;

return bech32.encode(hrp, version, hash);
}

/**
* Compile the address object to a bech32m address.
* @param {{NetworkType|Network)?} network
* @returns {String}
* @throws Error on bad hash/prefix.
*/

toBech32m(network) {
const version = this.version;
const hash = this.hash;

assert(version !== -1,
'Cannot convert non-program address to bech32m.');

assert(version !== 0,
'Cannot convert version 0 program to bech32m address.');

network = Network.get(network);

const hrp = network.addressPrefix.bech32;

return bech32m.encode(hrp, version, hash);
}

/**
* Inject properties from string.
* @private
Expand All @@ -253,7 +290,11 @@ class Address {

// Otherwise, it's most likely bech32.
try {
return this.fromBech32(addr, network);
try{
return this.fromBech32(addr, network);
} catch (e) {
return this.fromBech32m(addr, network);
}
} catch (e) {
return this.fromBase58(addr, network);
}
Expand All @@ -277,8 +318,12 @@ class Address {
*/

toString(network) {
if (this.version !== -1)
return this.toBech32(network);
if (this.version !== -1) {
if (this.version === 0)
return this.toBech32(network);

return this.toBech32m(network);
}
return this.toBase58(network);
}

Expand All @@ -296,7 +341,7 @@ class Address {
}

/**
* Inject properties from serialized data.
* Decode base58.
* @private
* @param {Buffer} data
* @throws Parse error
Expand All @@ -306,29 +351,18 @@ class Address {
const br = bio.read(data, true);
const prefix = br.readU8();

network = Network.fromAddress(prefix, network);
network = Network.fromBase58(prefix, network);

const type = Address.getType(prefix, network);

let version = -1;
if (type === Address.types.WITNESS) {
if (data.length > 38)
throw new Error('Address is too long.');

version = br.readU8();

if (br.readU8() !== 0)
throw new Error('Address version padding is non-zero.');
} else {
if (data.length !== 25)
throw new Error('Address is too long.');
}
if (data.length !== 25)
throw new Error('Address is too long.');

const hash = br.readBytes(br.left() - 4);

br.verifyChecksum(hash256.digest);

return this.fromHash(hash, type, version);
return this.fromHash(hash, type);
}

/**
Expand Down Expand Up @@ -386,6 +420,12 @@ class Address {

const [hrp, version, hash] = bech32.decode(data);

assert(version !== -1,
'Cannot convert non-program address to bech32');

assert(version === 0,
'Cannot convert program version > 0 to bech32');

// make sure HRP is correct.
Network.fromBech32(hrp, network);

Expand All @@ -404,6 +444,45 @@ class Address {
return new this().fromBech32(data, network);
}

/**
* Inject properties from bech32m address.
* @private
* @param {String} data
* @param {Network?} network
* @throws Parse error
*/

fromBech32m(data, network) {
const type = Address.types.WITNESS;

assert(typeof data === 'string');

const [hrp, version, hash] = bech32m.decode(data);

assert(version !== -1,
'Cannot convert non-program address to bech32m');

assert(version > 0,
'Cannot convert program version 0 to bech32m.');

// make sure HRP is correct.
Network.fromBech32m(hrp, network);

return this.fromHash(hash, type, version);
}

/**
* Create an address object from a bech32m address.
* @param {String} data
* @param {Network?} network
* @returns {Address}
* @throws Parse error.
*/

static fromBech32m(data, network) {
return new this().fromBech32m(data, network);
}

/**
* Inject properties from output script.
* @private
Expand Down Expand Up @@ -587,9 +666,9 @@ class Address {
} else {
assert(type === Address.types.WITNESS, 'Wrong version (non-witness).');
assert(version >= 0 && version <= 16, 'Bad program version.');
if (version === 0 && type === Address.types.WITNESS) {
if (version === 0) {
assert(hash.length === 20 || hash.length === 32,
'Witness program hash is the wrong size.');
'Witness version 0 program hash is the wrong size.');
}
assert(hash.length >= 2 && hash.length <= 40, 'Hash is the wrong size.');
}
Expand Down Expand Up @@ -778,21 +857,6 @@ class Address {
return this.version !== -1;
}

/**
* Test whether the address is an unknown witness program.
* @returns {Boolean}
*/

isUnknown() {
if (this.version === -1)
return false;

if (this.version > 0)
return true;

return this.hash.length !== 20 && this.hash.length !== 32;
}

/**
* Get the hash of a base58 address or address-related object.
* @param {String|Address|Hash} data
Expand All @@ -807,8 +871,6 @@ class Address {
let hash;

if (Buffer.isBuffer(data)) {
if (data.length !== 20 && data.length !== 32)
throw new Error('Object is not an address.');
hash = data;
} else if (data instanceof Address) {
hash = data.hash;
Expand Down Expand Up @@ -837,9 +899,6 @@ class Address {
return Address.types.PUBKEYHASH;
case prefixes.scripthash:
return Address.types.SCRIPTHASH;
case prefixes.witnesspubkeyhash:
case prefixes.witnessscripthash:
return Address.types.WITNESS;
default:
throw new Error('Unknown address prefix.');
}
Expand Down
19 changes: 14 additions & 5 deletions lib/protocol/network.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,8 +308,8 @@ class Network {
* @returns {Network}
*/

static fromAddress(prefix, network) {
return Network.by(prefix, cmpAddress, network, 'base58 address');
static fromBase58(prefix, network) {
return Network.by(prefix, cmpBase58, network, 'base58 address');
}

/**
Expand All @@ -323,6 +323,17 @@ class Network {
return Network.by(hrp, cmpBech32, network, 'bech32 address');
}

/**
* Get a network by its bech32m address prefix.
* @param {String} hrp
* @param {Network?} network
* @returns {Network}
*/

static fromBech32m(hrp, network) {
return Network.by(hrp, cmpBech32, network, 'bech32m address');
}

/**
* Convert the network to a string.
* @returns {String}
Expand Down Expand Up @@ -417,14 +428,12 @@ function cmpPriv58(network, prefix) {
return network.keyPrefix.xprivkey58 === prefix;
}

function cmpAddress(network, prefix) {
function cmpBase58(network, prefix) {
const prefixes = network.addressPrefix;

switch (prefix) {
case prefixes.pubkeyhash:
case prefixes.scripthash:
case prefixes.witnesspubkeyhash:
case prefixes.witnessscripthash:
return true;
}

Expand Down