Skip to content

Commit

Permalink
network, address: add prefix values for all witness versions > 0
Browse files Browse the repository at this point in the history
  • Loading branch information
pinheadmz committed Oct 14, 2021
1 parent 06a5b2f commit cca5243
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 29 deletions.
29 changes: 22 additions & 7 deletions lib/primitives/address.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,16 +151,28 @@ class Address {
case Address.types.WITNESS:
if (this.version === 0) {
if (this.hash.length === 20)
return prefixes.witnesspubkeyhash;
return prefixes.witnesspubkeyhash;

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

// BIP141 defines any version 0 data lengths besides 20 and 32
// as invalid. Note that this is not the case with version 1
// (and presumably, all other future versions) where specific
// lengths are defined as valid and trigger extra rule sets,
// but all other lengths remain unencumbered (ANYONECANSPEND).
return -1;
}
if (this.version === 1) {
if (this.hash.length === 32)
return prefixes.taproot;
}
break;

// Since all segwit address strings have the same prefix
// for each network, there really isn't any reason why version 0
// script/pubkey should have been explicitly identified (above).
// We keep them for backwards comptability but from witness version 1
// onward, we only need to retun the witness version number.
// Unlike legacy addresses, the prefix is not actually included in the
// address string. bcoin uses it internally to keep Address objects
// consistent (the addrIndexer expects it for each entry, for example).
return prefixes.witnessVersionMask + this.version;
}

return -1;
Expand Down Expand Up @@ -901,14 +913,17 @@ class Address {
static getType(prefix, network) {
const prefixes = network.addressPrefix;

// For witness versions > 0
if ((prefix & prefixes.witnessVersionMask) === prefixes.witnessVersionMask)
return Address.types.WITNESS;

switch (prefix) {
case prefixes.pubkeyhash:
return Address.types.PUBKEYHASH;
case prefixes.scripthash:
return Address.types.SCRIPTHASH;
case prefixes.witnesspubkeyhash:
case prefixes.witnessscripthash:
case prefixes.taproot:
return Address.types.WITNESS;
default:
throw new Error('Unknown address prefix.');
Expand Down
5 changes: 4 additions & 1 deletion lib/protocol/network.js
Original file line number Diff line number Diff line change
Expand Up @@ -431,12 +431,15 @@ function cmpPriv58(network, prefix) {
function cmpAddress(network, prefix) {
const prefixes = network.addressPrefix;

// For witness versions > 0
if ((prefix & prefixes.witnessVersionMask) === prefixes.witnessVersionMask)
return true;

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

Expand Down
44 changes: 40 additions & 4 deletions lib/protocol/networks.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,42 @@ function b(hash) {
return Buffer.from(hash, 'hex');
}

// To stay backwards compatible with legacy addresses, we use
// phony prefix bytes for all witness addresses which are only
// used internally and are not part of the actual address.
// Witness version 0 has two prefixes already for each network
// corresponding to the only two valid data lengths (20 and 32).
// All other data lengths are totally invalid in version 0 programs.
// Witness versions > 0 will not have any invalid data lengths
// (they will remain anyone-can-spend) so all we need to do is
// prefix them with their version number. Because values between
// 0-15 have already been assigned to other address types,
// we set the first four bits as a mask for all witness versions.
// There is no need to use different values for each network.
// Why different values were used for the witness 0 types for
// some networks is unknown, but we keep it for backwards compatability.

const WITNESS_VERSION_MASK = 0xf0;

const witnessVersionPrefixes = {
witnessVersionMask: WITNESS_VERSION_MASK,
witnessv1: WITNESS_VERSION_MASK + 1,
witnessv2: WITNESS_VERSION_MASK + 2,
witnessv3: WITNESS_VERSION_MASK + 3,
witnessv4: WITNESS_VERSION_MASK + 4,
witnessv5: WITNESS_VERSION_MASK + 5,
witnessv6: WITNESS_VERSION_MASK + 6,
witnessv7: WITNESS_VERSION_MASK + 7,
witnessv8: WITNESS_VERSION_MASK + 8,
witnessv9: WITNESS_VERSION_MASK + 9,
witnessv10: WITNESS_VERSION_MASK + 10,
witnessv11: WITNESS_VERSION_MASK + 11,
witnessv12: WITNESS_VERSION_MASK + 12,
witnessv13: WITNESS_VERSION_MASK + 13,
witnessv14: WITNESS_VERSION_MASK + 14,
witnessv15: WITNESS_VERSION_MASK + 15
};

/**
* Network type list.
* @memberof module:protocol/networks
Expand Down Expand Up @@ -436,7 +472,7 @@ main.addressPrefix = {
witnesspubkeyhash: 0x06,
witnessscripthash: 0x0a,
bech32: 'bc',
taproot: 0x0b
...witnessVersionPrefixes
};

/**
Expand Down Expand Up @@ -692,7 +728,7 @@ testnet.addressPrefix = {
witnesspubkeyhash: 0x03,
witnessscripthash: 0x28,
bech32: 'tb',
taproot: 0x29
...witnessVersionPrefixes
};

testnet.requireStandard = false;
Expand Down Expand Up @@ -855,7 +891,7 @@ regtest.addressPrefix = {
witnesspubkeyhash: 0x03,
witnessscripthash: 0x28,
bech32: 'bcrt',
taproot: 0x29
...witnessVersionPrefixes
};

regtest.requireStandard = false;
Expand Down Expand Up @@ -1026,7 +1062,7 @@ simnet.addressPrefix = {
witnesspubkeyhash: 0x19,
witnessscripthash: 0x28,
bech32: 'sb',
taproot: 0x29
...witnessVersionPrefixes
};

simnet.requireStandard = false;
Expand Down
4 changes: 2 additions & 2 deletions test/address-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ describe('Address', function() {
}
});

it('should identify taproot addresses', () => {
it('should identify witness (not version 0) address', () => {
// Generated with Bitcoin Core v22.0.0 in regtest
const addresses = [
'bcrt1pnmrmugapastum8ztvgwcn8hvq2avmcwh2j4ssru7rtyygkpqq98q4wyd6s',
Expand All @@ -244,7 +244,7 @@ describe('Address', function() {
const network = Network.get('regtest');
assert.strictEqual(
parsed.getPrefix(network),
network.addressPrefix.taproot
network.addressPrefix.witnessVersionMask + 1
);
}
});
Expand Down
125 changes: 118 additions & 7 deletions test/indexer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ const Script = require('../lib/script/script');
const Opcode = require('../lib/script/opcode');
const Address = require('../lib/primitives/address');
const Block = require('../lib/primitives/block');
const TX = require('../lib/primitives/tx');
const Output = require('../lib/primitives/output');
const Input = require('../lib/primitives/input');
const Chain = require('../lib/blockchain/chain');
const WorkerPool = require('../lib/workers/workerpool');
const Miner = require('../lib/mining/miner');
Expand Down Expand Up @@ -239,7 +242,44 @@ describe('Indexer', function() {
assert.equal(called, false);
});

it('should not index transaction w/ invalid address', async () => {
it('should not index tx w/ invalid address (witness v0)', async () => {
const indexer = new AddrIndexer({
blocks: {},
chain: {}
});

const ops = [];

indexer.put = (key, value) => ops.push([key, value]);
indexer.del = (key, value) => ops.push([key, value]);

// Create a witness program version 0 with
// 10 byte data push (BIP141 limits v0 to either 20 or 32).
const script = new Script();
script.push(Opcode.fromSmall(0));
script.push(Opcode.fromData(Buffer.alloc(10)));
script.compile();

const tx = new TX({
inputs: [
new Input()
],
outputs: [
new Output({script})
]
});;

const entry = {height: 323549};
const block = {txs: [tx]};
const view = {};

indexer.indexBlock(entry, block, view);
indexer.unindexBlock(entry, block, view);

assert.equal(ops.length, 0);
});

it('should not index tx w/ invalid address (witness v1)', async () => {
const indexer = new AddrIndexer({
blocks: {},
chain: {}
Expand All @@ -251,10 +291,47 @@ describe('Indexer', function() {
indexer.del = (key, value) => ops.push([key, value]);

// Create a witness program version 1 with
// 40 byte data push.
// 50 byte data push (40 is the BIP141 maximum).
const script = new Script();
script.push(Opcode.fromSmall(1));
script.push(Opcode.fromData(Buffer.alloc(40)));
script.push(Opcode.fromData(Buffer.alloc(50)));
script.compile();

const tx = new TX({
inputs: [
new Input()
],
outputs: [
new Output({script})
]
});;

const entry = {height: 323549};
const block = {txs: [tx]};
const view = {};

indexer.indexBlock(entry, block, view);
indexer.unindexBlock(entry, block, view);

assert.equal(ops.length, 0);
});

it('should index tx w/ valid address (witness v0)', async () => {
const indexer = new AddrIndexer({
blocks: {},
chain: {}
});

const ops = [];

indexer.put = (key, value) => ops.push([key, value]);
indexer.del = (key, value) => ops.push([key, value]);

// Create a witness program version 0 with
// 20 byte data push.
const script = new Script();
script.push(Opcode.fromSmall(0));
script.push(Opcode.fromData(Buffer.alloc(20)));
script.compile();
const addr = Address.fromScript(script);

Expand All @@ -270,10 +347,10 @@ describe('Indexer', function() {
indexer.indexBlock(entry, block, view);
indexer.unindexBlock(entry, block, view);

assert.equal(ops.length, 0);
assert.equal(ops.length, 6);
});

it('should index transaction w/ valid address', async () => {
it('should index tx w/ valid address (witness v1)', async () => {
const indexer = new AddrIndexer({
blocks: {},
chain: {}
Expand All @@ -284,10 +361,10 @@ describe('Indexer', function() {
indexer.put = (key, value) => ops.push([key, value]);
indexer.del = (key, value) => ops.push([key, value]);

// Create a witness program version 0 with
// Create a witness program version 1 with
// 20 byte data push.
const script = new Script();
script.push(Opcode.fromSmall(0));
script.push(Opcode.fromSmall(1));
script.push(Opcode.fromData(Buffer.alloc(20)));
script.compile();
const addr = Address.fromScript(script);
Expand All @@ -307,6 +384,40 @@ describe('Indexer', function() {
assert.equal(ops.length, 6);
});

it('should index tx w/ valid address (witness v1, taproot)', async () => {
const indexer = new AddrIndexer({
blocks: {},
chain: {}
});

const ops = [];

indexer.put = (key, value) => ops.push([key, value]);
indexer.del = (key, value) => ops.push([key, value]);

// Create a witness program version 1 with
// 32 byte data push.
const script = new Script();
script.push(Opcode.fromSmall(1));
script.push(Opcode.fromData(Buffer.alloc(32)));
script.compile();
const addr = Address.fromScript(script);

const tx = {
getAddresses: () => [addr],
hash: () => Buffer.alloc(32)
};

const entry = {height: 323549};
const block = {txs: [tx]};
const view = {};

indexer.indexBlock(entry, block, view);
indexer.unindexBlock(entry, block, view);

assert.equal(ops.length, 6);
});

it('should error with limits', async () => {
const indexer = new AddrIndexer({
blocks: {},
Expand Down

0 comments on commit cca5243

Please sign in to comment.