segregated - goddamn - witness... and docs update.

This commit is contained in:
Christopher Jeffrey 2016-02-26 03:21:39 -08:00
parent 57ceb627d2
commit 4c653683b7
17 changed files with 1140 additions and 203 deletions

125
README.md
View File

@ -1,6 +1,29 @@
# BCoin
**BCoin** is a bitcoin node which can act as an SPV node or a (semi-)fullnode.
**BCoin** is a bitcoin node which can act as an SPV node or a fully validating
fullnode. Bcoin runs in node.js, but it can also be browserified.
## Features
- SPV mode
- HD Wallets (using BIP44 (or optionally BIP45) derivation)
- Fully browserifiable
- Full block validation
- Full block database (leveldb + regular file i/o)
- Mempool (still a WIP - not completely accurate to the bitcoind mempool)
- Wallet database (leveldb)
- HTTP server which acts as a wallet server and can also serve:
blocks, txs (by hash/address), and utxos (by id/address).
- Fast UTXO retrieval by address for wallets (10000 utxos from 10000 different
addresses in ~700ms, 50000+ utxos from 10-100 addresses in ~400ms)
- Experimental segregated witness support (witness programs still need to be
implemented for tx signing process and wallet, but bcoin should be able to
validate witness blocks and txs on segnet properly)
## Todo
- Pruning
- Improve mempool
## Install
@ -8,6 +31,11 @@
$ npm install bcoin
```
## NOTE
__The docs below are out of date. Bcoin is under heavy development. They will be
updated as soon as I get the chance.__
## Example Usage
### Doing a full blockchain sync
@ -342,7 +370,7 @@ tx.addOutput({
```
Opcodes are in the form of their symbolic names, in lowercase, with the `OP_`
prefixes removed. Pushdata ops are represented with Arrays.
prefixes removed. Pushdata ops are represented with Buffers.
The above script could be redeemed with:
@ -352,8 +380,8 @@ tx2.addInput({
output: bcoin.coin(tx, 0),
sequence: 0xffffffff,
script: [
signature, // Byte Array
publicKey // Byte Array
signature, // Buffer
publicKey // Buffer
]
});
```
@ -362,11 +390,11 @@ Executing a script by itself is also possible:
``` js
var stack = [];
bcoin.script.execute([[1], 'dup'], stack);
bcoin.script.execute([new Buffer([1]), 'dup'], stack);
console.log(stack);
Output:
[[1], [1]]
[<Buffer 01>, <Buffer 01>]
```
#### Pushdata OPs
@ -375,23 +403,19 @@ Note that with bcoins deserialized script format, you do not get to decide
pushdata on ops. Bcoin will always serialize to `minimaldata` format scripts in
terms of `OP_PUSHDATA0-OP_PUSHDATA4`.
`OP_0` is represented with an empty array (which is appropriate because this is
what gets pushed onto the stack). While `OP_1-16` are actually represented with
numbers. `OP_1NEGATE` is just '1negate'.
So a script making use of all pushdata ops would look like:
``` js
script: [
[], // OP_0 / OP_FALSE
1, // OP_1 / OP_TRUE
2, 3, 4, 5, 6, 7, 8, 9, 10, // OP_2-10
11, 12, 13, 14, 15, 16, // OP_11-16
'1negate', // OP_1NEGATE
new Array(0x4b), // PUSHDATA0 (direct push)
new Array(0xff), // PUSHDATA1
new Array(0xffff), // PUSHDATA2
new Array(0xffffffff) // PUSHDATA4
0, // OP_0 / OP_FALSE
1, // OP_1 / OP_TRUE
2, 3, 4, 5, 6, 7, 8, 9, 10, // OP_2-10
11, 12, 13, 14, 15, 16, // OP_11-16
'1negate', // OP_1NEGATE
new Buffer(0x4b), // PUSHDATA0 (direct push)
new Buffer(0xff), // PUSHDATA1
new Buffer(0xffff), // PUSHDATA2
new Buffer(0xffffffff) // PUSHDATA4
];
```
@ -471,14 +495,6 @@ console.log(tx.rhash);
console.log(block.rhash);
```
### Arrays vs. Buffers
Every piece of binary data in bcoin that is user-facing in bcoin is an Array of
bytes. For example, `block.hash()` with no encoding passed in will return a
byte array. Bcoin does use Buffers behind the scenes to speed up parsing of
blocks and transactions coming in through the network, but every piece of data
a programmer using bcoin will deal with is going to be a byte array.
### Saving transactions to a wallet
Most of the time, you won't need all transactions in the blockchain if you're
@ -486,7 +502,7 @@ only building a wallet. When a transaction comes in pertaining to your wallet,
it's best to called `wallet.addTX(tx)` and save the wallet afterwards.
``` js
pool.on('watched', function(tx) {
pool.on('tx', function(tx) {
wallet.addTX(tx);
});
@ -497,59 +513,6 @@ pool.on('full', function() {
});
```
### Saving the blockchain
At the moment, bcoin does not save any full blocks or make any assumptions
about how the programmer wants to do it. It only saves the blockchain (block
headers and chainwork). The programmer simply needs to hook into block events
and save the blocks.
``` js
pool.on('block', function(block) {
// A simple key-value store:
db.save(block.hash('hex'), utils.toHex(block.render()), function(err) {
if (err)
return console.error(err.message);
console.log('Block %s saved.', block.rhash);
// Could also save transactions individually here for quick lookups
});
});
```
#### Handling Blockchain Forks
Bcoin handles blockchain forks like an SPV client. If it sees an alternate tip,
it will reset to the last non-forked block and kill the current peer while
emitting a `fork` event (see Pool events). It will repeat this process until
the network eventually chooses the best chain.
Bcoin essentially backs off and waits to see which fork wins. This means bcoin
plays no part in protecting the network by helping choose the best chain
according to the chainwork.
Note that this may _still_ cause an issue with transactions that are already
saved and considered confirmed. It's best to hook into the fork event and
remove all confirmed transactions you may have saved in your database.
``` js
pool.on('fork', function(tip1, tip2) {
// Keep deleting everything until
// the fork is resolved:
db.get(tip1, function(err, block) {
block.txs.forEach(function(tx) {
db.del(tx.hash('hex'));
});
db.del(tip1);
});
db.get(tip2, function(err, block) {
block.txs.forEach(function(tx) {
db.del(tx.hash('hex'));
});
db.del(tip2);
});
});
```
### Transaction Building
Transaction building happens in 4 stages:

View File

@ -50,7 +50,7 @@ function Address(options) {
assert(this.type === 'pubkeyhash' || this.type === 'multisig');
this.prefixType = this.type === 'multisig' ? 'scripthash' : 'pubkeyhash';
if (network.prefixes[this.prefixType] == null)
if (network.address.prefixes[this.prefixType] == null)
throw new Error('Unknown prefix: ' + this.prefixType);
if (this.m < 1 || this.m > this.n)
@ -221,7 +221,7 @@ Address.prototype.getScriptAddress = function getScriptAddress() {
if (this._scriptAddress)
return this._scriptAddress;
this._scriptAddress = Address.toAddress(this.getScriptHash(), this.prefixType);
this._scriptAddress = Address.compileHash(this.getScriptHash(), this.prefixType);
return this._scriptAddress;
};
@ -252,7 +252,7 @@ Address.prototype.getKeyAddress = function getKeyAddress() {
if (this._address)
return this._address;
this._address = Address.toAddress(this.getKeyHash(), 'pubkeyhash');
this._address = Address.compileHash(this.getKeyHash(), 'pubkeyhash');
return this._address;
};
@ -468,87 +468,136 @@ Address.hash160 = function hash160(key) {
return utils.ripesha(key);
};
Address.toAddress = function toAddress(hash, prefix) {
var addr;
Address.sha256 = function sha256(key) {
key = utils.ensureBuffer(key);
return utils.sha256(key);
};
Address.compileHash = function compileHash(hash, prefixType) {
var prefix, version, size, off, addr;
if (!Buffer.isBuffer(hash))
hash = new Buffer(hash, 'hex');
addr = new Buffer(1 + hash.length + 4);
if (!prefixType)
prefixType = 'pubkeyhash';
prefix = network.prefixes[prefix || 'pubkeyhash'];
prefix = network.address.prefixes[prefixType];
version = network.address.versions[prefixType];
utils.writeU8(addr, prefix, 0);
utils.copy(hash, addr, 1);
utils.copy(utils.checksum(addr.slice(0, 21)), addr, 21);
assert(prefix != null);
assert(hash.length === 20 || hash.length === 32);
size = 1 + hash.length + 4;
if (version != null)
size += 2;
addr = new Buffer(size);
off = 0;
off += utils.writeU8(addr, prefix, off);
if (version != null) {
off += utils.writeU8(addr, version, off);
off += utils.writeU8(addr, 0, off);
}
off += utils.copy(hash, addr, off);
off += utils.copy(utils.checksum(addr.slice(0, off)), addr, off);
return utils.toBase58(addr);
};
Address.compile = function compile(key, prefix) {
return Address.toAddress(Address.hash160(key), prefix);
Address.compileData = function compileData(key, prefix) {
if (prefix === 'witnessscripthash')
key = Address.sha256(key);
else
key = Address.hash160(key);
return Address.compileHash(key, prefix);
};
Address.toHash = function toHash(addr, prefix) {
var chk;
if (prefix == null && typeof addr === 'string')
prefix = Address.prefixes[addr[0]];
Address.parse = function parse(addr, prefixType) {
var chk, prefix, version, size, hash;
if (!Buffer.isBuffer(addr))
addr = utils.fromBase58(addr);
prefix = network.prefixes[prefix || 'pubkeyhash'];
if (prefixType == null)
prefixType = network.address.prefixesByVal[addr[0]];
if (addr.length !== 25) {
if (!prefixType)
prefixType = 'pubkeyhash';
prefix = network.address.prefixes[prefixType];
version = network.address.versions[prefixType];
assert(prefix != null);
// prefix
size = 1;
// version + nul byte
if (version != null)
size += 2;
hash = addr.slice(size, -4);
// hash
if (prefixType === 'witnessscripthash')
size += 32;
else
size += 20;
if (addr.length !== size + 4) {
utils.debug('Address is not the right length.');
return new Buffer([]);
return;
}
if (addr[0] !== prefix) {
utils.debug('Address is not the right prefix.');
return new Buffer([]);
return;
}
if (version != null && (addr[1] !== version || addr[2] !== 0)) {
utils.debug('Address is not the right program version.');
return;
}
chk = utils.checksum(addr.slice(0, -4));
if (utils.readU32(chk, 0) !== utils.readU32(addr, 21)) {
if (utils.readU32(chk, 0) !== utils.readU32(addr, size)) {
utils.debug('Address checksum failed.');
return new Buffer([]);
return;
}
return addr.slice(1, -4);
return {
type: prefixType,
hash: hash,
version: version == null ? -1 : version
};
};
Address.__defineGetter__('prefixes', function() {
if (Address._prefixes)
return Address._prefixes;
Address._prefixes = ['pubkeyhash', 'scripthash'].reduce(function(out, prefix) {
var ch = Address.compile(new Buffer([]), prefix)[0];
out[ch] = prefix;
return out;
}, {});
return Address._prefixes;
});
Address.validate = function validate(addr, prefix) {
if (!addr || typeof addr !== 'string')
if (!addr)
return false;
var p = Address.toHash(addr, prefix);
if (!Address.parse(addr, prefix))
return false;
return p.length !== 0;
return true;
};
Address.getType = function getType(addr) {
var prefix;
if (!addr || typeof addr !== 'string')
if (!addr)
return 'unknown';
prefix = Address.prefixes[addr[0]];
if (!Buffer.isBuffer(addr))
addr = utils.fromBase58(addr);
prefix = network.address.prefixes[addr[0]];
if (!Address.validate(addr, prefix))
return 'unknown';

View File

@ -24,6 +24,7 @@ function Block(data) {
bcoin.abstractblock.call(this, data);
this.type = 'block';
this.witness = data.witness || false;
this.txs = data.txs || [];
@ -36,29 +37,84 @@ function Block(data) {
return bcoin.tx(data, self, i);
});
if (this.witness) {
this._wsize = this._raw.length;
this._wraw = this._raw;
this._raw = null;
this._size = 0;
}
if (!this._raw)
this._raw = this.render();
if (!this._size)
this._size = this._raw.length;
this._cost = data._cost || 0;
}
utils.inherits(Block, bcoin.abstractblock);
Block.prototype.render = function render() {
if (this._raw)
return this._raw;
return bcoin.protocol.framer.block(this);
if (!this._raw) {
this._raw = bcoin.protocol.framer.block(this);
this._size = this._raw.length;
}
return this._raw;
};
Block.prototype.renderWitness = function renderWitness() {
if (!this._wraw) {
this._wraw = bcoin.protocol.framer.witnessBlock(this);
this._wsize = this._wraw.length;
}
return this._wraw;
};
Block.prototype.getBlockSize = function getBlockSize() {
return this.render().length;
};
Block.prototype.getBlockCost = function getBlockCost() {
if (!this._cost)
this._cost = this.renderWitness()._cost;
return this._cost;
};
Block.prototype.getSigopsCost = function getSigopCost(scriptHash) {
var cost = 0;
var i;
for (i = 0; i < this.txs.length; i++)
cost += this.txs[i].getSigopsCost(scriptHash);
return cost;
};
Block.prototype.getMerkleRoot = function getMerkleRoot() {
var hashes = [];
var leaves = [];
var i, root;
for (i = 0; i < this.txs.length; i++)
hashes.push(this.txs[i].hash());
leaves.push(this.txs[i].hash());
root = utils.getMerkleRoot(hashes);
root = utils.getMerkleRoot(leaves);
if (!root)
return utils.toHex(constants.zeroHash);
return utils.toHex(root);
};
Block.prototype.getWitnessRoot = function getWitnessRoot() {
var leaves = [];
var i, root;
for (i = 0; i < this.txs.length; i++)
leaves.push(this.txs[i].witnessHash());
root = utils.getMerkleRoot(leaves);
if (!root)
return utils.toHex(constants.zeroHash);

View File

@ -454,7 +454,8 @@ Chain.prototype._verifyContext = function _verifyContext(block, prev, callback)
Chain.prototype._verify = function _verify(block, prev) {
var flags = constants.flags.MANDATORY_VERIFY_FLAGS;
var height, ts, i, tx, cb, coinbaseHeight, medianTime, locktimeMedian;
var height, ts, i, tx, cb, coinbaseHeight, medianTime;
var locktimeMedian, coinbase, commitment, segwit, witnessRoot;
if (!block.verify())
return flags;
@ -516,6 +517,15 @@ Chain.prototype._verify = function _verify(block, prev) {
return false;
}
// Only allow version 5 blocks (segwit)
// once the majority of blocks are using it.
if (height >= network.segwitHeight) {
if (block.version < 5 && prev.isOutdated(5)) {
utils.debug('Block is outdated (v5): %s', block.rhash);
return false;
}
}
// Only allow version 8 blocks (locktime median past)
// once the majority of blocks are using it.
// if (block.version < 8 && prev.isOutdated(8)) {
@ -535,6 +545,14 @@ Chain.prototype._verify = function _verify(block, prev) {
if (block.version >= 4 && prev.isUpgraded(4))
flags |= constants.flags.VERIFY_CHECKLOCKTIMEVERIFY;
// Segregrated witness is now usable (the-bip-that-really-needs-to-be-rewritten)
if (height >= network.segwitHeight) {
if (block.witness && block.version >= 5 && prev.isUpgraded(5) ) {
flags |= constants.flags.VERIFY_WITNESS;
segwit = true;
}
}
// Use nLockTime median past (bip113)
// https://github.com/btcdrak/bips/blob/d4c9a236ecb947866c61aefb868b284498489c2b/bip-0113.mediawiki
// Support version bits:
@ -555,6 +573,22 @@ Chain.prototype._verify = function _verify(block, prev) {
}
}
// Find the fucking commitment for segregated shitness
if (segwit && block.witness) {
coinbase = block.txs[0];
for (i = 0; i < coinbase.outputs.length; i++) {
commitment = coinbase.outputs[i].script;
if (bcoin.script.isCommitment(commitment)) {
witnessRoot = bcoin.script.getWitnessRoot(commitment);
if (utils.toHex(witnessRoot) !== block.getWitnessRoot()) {
utils.debug('Block failed witnessroot test: %s', block.rhash);
return false;
}
break;
}
}
}
// Get timestamp for tx.isFinal().
ts = locktimeMedian ? medianTime : block.ts;

View File

@ -212,7 +212,7 @@ HDPrivateKey.prototype.scan44 = function scan44(options, txByAddress, callback)
// respect the gap limit described below
return (function next() {
var address = chain.derive(addressIndex++);
var addr = bcoin.address.compile(address.publicKey);
var addr = bcoin.address.compileData(address.publicKey);
return txByAddress(addr, function(err, txs) {
var result;
@ -363,7 +363,7 @@ HDPrivateKey.prototype.scan45 = function scan45(options, txByAddress, callback)
return (function next() {
var address = chain.derive(addressIndex++);
var addr = bcoin.address.compile(address.publicKey);
var addr = bcoin.address.compileData(address.publicKey);
return txByAddress(addr, function(err, txs) {
var result;

View File

@ -23,8 +23,11 @@ function Input(options, tx) {
this.prevout = options.prevout;
this.script = options.script || [];
this.sequence = options.sequence == null ? 0xffffffff : options.sequence;
this.witness = options.witness || [];
this._size = options._size || 0;
this._offset = options._offset || 0;
this._witnessSize = options._witnessSize || 0;
this._witnessOffset = options._witnessOffset || 0;
this._mutable = !tx || (tx instanceof bcoin.mtx);
if (options.output)
@ -54,7 +57,11 @@ Input.prototype.getType = function getType() {
if (this._type)
return this._type;
type = bcoin.script.getInputType(this.script);
if (this.witness.length > 0)
type = bcoin.script.getInputType(this.witness, null, true);
if (type === 'unknown')
type = bcoin.script.getInputType(this.script);
if (!this._mutable)
this._type = type;
@ -74,7 +81,11 @@ Input.prototype.getAddress = function getAddress() {
if (this._address)
return this._address;
address = bcoin.script.getInputAddress(this.script);
if (this.witness.length > 0)
address = bcoin.script.getInputAddress(this.witness, null, true);
if (!address)
address = bcoin.script.getInputAddress(this.script);
if (!this._mutable)
this._address = address;
@ -214,6 +225,9 @@ Input.prototype.toJSON = function toJSON() {
},
output: this.output ? this.output.toJSON() : null,
script: utils.toHex(bcoin.script.encode(this.script)),
witness: this.witness.map(function(chunk) {
return utils.toHex(chunk);
}),
sequence: this.sequence
};
};
@ -226,6 +240,9 @@ Input._fromJSON = function _fromJSON(json) {
},
output: json.output ? bcoin.coin._fromJSON(json.output) : null,
script: bcoin.script.decode(new Buffer(json.script, 'hex')),
witness: json.witness.map(function(chunk) {
return new Buffer(chunk, 'hex');
}),
sequence: json.sequence
};
};
@ -237,18 +254,26 @@ Input.fromJSON = function fromJSON(json) {
Input.prototype.toCompact = function toCompact() {
return {
type: 'input',
input: this.toRaw('hex')
input: this.toRaw('hex'),
witness: this.witness.map(function(chunk) {
return utils.toHex(chunk);
}),
};
};
Input._fromCompact = function _fromCompact(json) {
return Input._fromRaw(json.input, 'hex');
json = Input._fromRaw(json.input, 'hex');
json.witness = json.witness.map(function(chunk) {
return new Buffer(chunk, 'hex');
});
return json;
};
Input.fromCompact = function fromCompact(json) {
return new Input(Input._fromCompact(json));
};
// NOTE: We cannot encode the witness here.
Input.prototype.toRaw = function toRaw(enc) {
var data = bcoin.protocol.framer.input(this);

View File

@ -81,10 +81,19 @@ MTX.prototype.hash = function hash(enc) {
return enc === 'hex' ? utils.toHex(hash) : hash;
};
MTX.prototype.witnessHash = function hash(enc) {
var hash = utils.dsha256(this.renderWitness());
return enc === 'hex' ? utils.toHex(hash) : hash;
};
MTX.prototype.render = function render() {
return bcoin.protocol.framer.tx(this);
};
MTX.prototype.renderWitness = function renderWitness() {
return bcoin.protocol.framer.witnessTX(this);
};
MTX.prototype.getSize = function getSize() {
return this.render().length;
};
@ -570,7 +579,7 @@ MTX.prototype.addOutput = function addOutput(obj, value) {
};
MTX.prototype.scriptOutput = function scriptOutput(index, options) {
var output, script, keys, m, n, hash, flags;
var output, script, keys, m, n, hash, flags, address;
if (options instanceof bcoin.output)
return;
@ -601,19 +610,20 @@ MTX.prototype.scriptOutput = function scriptOutput(index, options) {
return;
script = bcoin.script.createMultisig(keys, m, n);
} else if (bcoin.address.getType(options.address) === 'scripthash') {
// P2SH Transaction
// https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki
// hash160 [20-byte-redeemscript-hash] equal
script = bcoin.script.createScripthash(
bcoin.address.toHash(options.address, 'scripthash')
);
} else if (options.address) {
// P2PKH Transaction
// dup hash160 [pubkey-hash] equalverify checksig
script = bcoin.script.createPubkeyhash(
bcoin.address.toHash(options.address, 'pubkeyhash')
);
address = bcoin.address.parse(options.address);
if (!address)
throw new Error(options.address + ' is not a valid address.');
if (address.type === 'pubkeyhash')
script = bcoin.script.createPubkeyhash(address.hash);
else if (address.type === 'scripthash')
script = bcoin.script.createScripthash(address.hash);
else if (address.version !== -1)
script = bcoin.script.createWitnessProgram(address.version, address.hash);
else
throw new Error('Cannot parse address: ' + options.address);
} else if (options.key) {
// P2PK Transaction
// [pubkey] checksig

View File

@ -21,7 +21,10 @@ exports.inv = {
error: 0,
tx: 1,
block: 2,
filtered: 3
filtered: 3,
witnesstx: 1 | (1 << 30),
witnessblock: 2 | (1 << 30),
witnessfiltered: 3 | (1 << 30)
};
exports.invByVal = {
@ -197,7 +200,9 @@ exports.hashTypeByVal = Object.keys(exports.hashType).reduce(function(out, type)
exports.block = {
maxSize: 1000000,
maxCost: 4000000,
maxSigops: 1000000 / 50,
maxSigopsCost: 4000000 / 50,
maxOrphanTx: 1000000 / 100,
medianTimeSpan: 11,
bip16time: 1333238400
@ -286,7 +291,9 @@ exports.flags = {
VERIFY_MINIMALDATA: (1 << 6),
VERIFY_DISCOURAGE_UPGRADABLE_NOPS: (1 << 7),
VERIFY_CLEANSTACK: (1 << 8),
VERIFY_CHECKLOCKTIMEVERIFY: (1 << 9)
VERIFY_CHECKLOCKTIMEVERIFY: (1 << 9),
VERIFY_WITNESS: (1 << 10),
VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM: (1 << 9)
};
// Block validation
@ -303,3 +310,5 @@ exports.flags.STANDARD_VERIFY_FLAGS =
| exports.flags.VERIFY_CLEANSTACK
| exports.flags.VERIFY_CHECKLOCKTIMEVERIFY
| exports.flags.VERIFY_LOW_S;
// | exports.flags.VERIFY_WITNESS
// | exports.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM;

View File

@ -465,18 +465,119 @@ Framer.tx = function _tx(tx) {
off += utils.writeU32(p, tx.locktime, off);
p._cost = p.length * 4;
return p;
};
Framer.witnessTX = function _witnessTX(tx) {
var inputs = [];
var outputs = [];
var witnesses = [];
var inputSize = 0;
var outputSize = 0;
var witnessSize = 0;
var off = 0;
var p, i, input, output, witness;
for (i = 0; i < tx.inputs.length; i++) {
input = Framer.input(tx.inputs[i]);
inputs.push(input);
inputSize += input.length;
witness = Framer.witness(tx.inputs[i].witness);
witnesses.push(witness);
witnessSize += witness.length;
}
for (i = 0; i < tx.outputs.length; i++) {
output = Framer.output(tx.outputs[i]);
outputs.push(output);
outputSize += output.length;
}
p = new Buffer(4
+ 2
+ utils.sizeIntv(tx.inputs.length) + inputSize
+ utils.sizeIntv(tx.outputs.length) + outputSize
+ 4);
off += utils.write32(p, tx.version, off);
// marker
off += utils.writeU8(p, 0, off);
// flag
off += utils.writeU8(p, tx.flag || 1, off);
off += utils.writeIntv(p, tx.inputs.length, off);
for (i = 0; i < inputs.length; i++) {
input = inputs[i];
off += utils.copy(input, p, off);
}
off += utils.writeIntv(p, tx.outputs.length, off);
for (i = 0; i < outputs.length; i++) {
output = outputs[i];
off += utils.copy(output, p, off);
}
p._cost = (off - 2) * 4;
// NOTE: No varint item count here.
for (i = 0; i < witnesses.length; i++)
off += utils.copy(witnesses[i], p, off);
off += utils.writeU32(p, tx.locktime, off);
p._cost += 4 * 4;
p._cost += witnessSize;
return p;
};
Framer.witness = function _witness(witness) {
var off = 0;
var size = 0;
var p, chunk;
if (!witness)
return new Buffer([0]);
size += utils.writeIntv(witness.length);
for (i = 0; i < witness.length; i++) {
chunk = witness[i];
size += utils.sizeIntv(chunk.length) + chunk.length;
}
p = new Buffer(size);
for (i = 0; i < witness.length; i++) {
chunk = witness[i];
off += utils.writeIntv(p, chunk.length, off);
off += utils.copy(chunk, p, off);
}
return p;
};
Framer.block = function _block(block) {
return Framer._block(block, false);
};
Framer.witnessBlock = function _witnessBlock(block) {
return Framer._block(block, true);
};
Framer._block = function _block(block, witness) {
var off = 0;
var txSize = 0;
var txs = [];
var i, tx, p;
for (i = 0; i < block.txs.length; i++) {
tx = block.txs[i].render
? block.txs[i].render()
tx = witness && block.txs[i].witness
? Framer.witnessTX(block.txs[i])
: Framer.tx(block.txs[i]);
txs.push(tx);
txSize += tx.length;

View File

@ -14,7 +14,7 @@ var assert = utils.assert;
*/
var network = exports;
var main, testnet, regtest;
var main, testnet, regtest, segnet;
network.set = function set(type) {
var net = network[type];
@ -28,13 +28,34 @@ network.set = function set(type) {
main = network.main = {};
main.prefixes = {
pubkeyhash: 0,
scripthash: 5,
privkey: 128,
xpubkey: 0x0488b21e,
xprivkey: 0x0488ade4
};
main.address = {
prefixes: {
pubkeyhash: 0,
scripthash: 5,
witnesspubkeyhash: 6,
witnessscripthash: 10
},
versions: {
witnesspubkeyhash: 0,
witnessscripthash: 0
}
};
main.address.prefixesByVal = Object.keys(main.address.prefixes).reduce(function(out, name) {
out[main.address.prefixes[name]] = name;
return out;
}, {});
main.address.versionsByVal = Object.keys(main.address.versions).reduce(function(out, name) {
out[main.address.versions[name]] = name;
return out;
}, {});
main.type = 'main';
main.seeds = [
@ -121,6 +142,8 @@ main.block = {
majorityWindow: 1000
};
main.segwitHeight = 2000000000;
main.genesisBlock = '0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000';
/**
@ -133,13 +156,34 @@ testnet = network.testnet = {};
testnet.type = 'testnet';
testnet.prefixes = {
pubkeyhash: 111,
scripthash: 196,
privkey: 239,
xpubkey: 0x043587cf,
xprivkey: 0x04358394
};
testnet.address = {
prefixes: {
pubkeyhash: 111,
scripthash: 196,
witnesspubkeyhash: 3,
witnessscripthash: 40
},
versions: {
witnesspubkeyhash: 0,
witnessscripthash: 0
}
};
testnet.address.prefixesByVal = Object.keys(testnet.address.prefixes).reduce(function(out, name) {
out[testnet.address.prefixes[name]] = name;
return out;
}, {});
testnet.address.versionsByVal = Object.keys(testnet.address.versions).reduce(function(out, name) {
out[testnet.address.versions[name]] = name;
return out;
}, {});
testnet.seeds = [
'testnet-seed.alexykot.me',
'testnet-seed.bitcoin.petertodd.org',
@ -210,6 +254,8 @@ testnet.block = {
majorityWindow: 100
};
testnet.segwitHeight = 2000000000;
testnet.genesisBlock = '0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff001d1aa4ae180101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000';
/**
@ -221,13 +267,34 @@ regtest = network.regtest = {};
regtest.type = 'testnet';
regtest.prefixes = {
pubkeyhash: 111,
scripthash: 196,
privkey: 239,
xpubkey: 0x043587cf,
xprivkey: 0x04358394
};
regtest.address = {
prefixes: {
pubkeyhash: 111,
scripthash: 196,
witnesspubkeyhash: 3,
witnessscripthash: 40
},
versions: {
witnesspubkeyhash: 0,
witnessscripthash: 0
}
};
regtest.address.prefixesByVal = Object.keys(regtest.address.prefixes).reduce(function(out, name) {
out[regtest.address.prefixes[name]] = name;
return out;
}, {});
regtest.address.versionsByVal = Object.keys(regtest.address.versions).reduce(function(out, name) {
out[regtest.address.versions[name]] = name;
return out;
}, {});
regtest.seeds = [
'127.0.0.1'
];
@ -281,4 +348,113 @@ regtest.block = {
majorityWindow: 1000
};
regtest.segwitHeight = 0;
regtest.genesisBlock = '0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000';
/**
* Segnet
*/
segnet = network.segnet = {};
segnet.type = 'segnet';
segnet.prefixes = {
privkey: 158,
xpubkey: 0x053587cf,
xprivkey: 0x05358394
};
segnet.address = {
prefixes: {
pubkeyhash: 30,
scripthash: 50,
witnesspubkeyhash: 3,
witnessscripthash: 40
},
versions: {
witnesspubkeyhash: 0,
witnessscripthash: 0
}
};
segnet.address.prefixesByVal = Object.keys(segnet.address.prefixes).reduce(function(out, name) {
out[segnet.address.prefixes[name]] = name;
return out;
}, {});
segnet.address.versionsByVal = Object.keys(segnet.address.versions).reduce(function(out, name) {
out[segnet.address.versions[name]] = name;
return out;
}, {});
segnet.seeds = [
'104.243.38.34',
'104.155.1.158',
'119.246.245.241',
'46.101.235.82'
];
segnet.port = 28333;
segnet.alertKey = new Buffer(''
+ '04302390343f91cc401d56d68b123028bf52e5f'
+ 'ca1939df127f63c6467cdf9c8e2c14b61104cf8'
+ '17d0b780da337893ecc4aaff1309e536162dabb'
+ 'db45200ca2b0a',
'hex');
segnet.checkpoints = [];
segnet.checkpoints = segnet.checkpoints.reduce(function(out, block) {
out[block.height] = utils.revHex(block.hash);
return block;
}, {});
segnet.checkpoints.tsLastCheckpoint = 0;
segnet.checkpoints.txsLastCheckpoint = 0;
segnet.checkpoints.txsPerDay = 300;
segnet.checkpoints.lastHeight = 0;
segnet.halvingInterval = 210000;
segnet.genesis = {
version: 1,
hash: utils.revHex(
'ead13e4b1d0164b21128523156f729373d7a11bc9b6a1ee2e6e883ab9d9a728c'
),
prevBlock: utils.toHex(
[ 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0 ]),
merkleRoot: utils.revHex(
'4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b'
),
ts: 1452368293,
bits: 0x1d00ffff,
nonce: 0
};
segnet.magic = 0x99f57166;
segnet.powLimit = new bn(
'00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'hex'
);
segnet.powTargetTimespan = 14 * 24 * 60 * 60; // two weeks
segnet.powTargetSpacing = 10 * 60;
segnet.powDiffInterval = segnet.powTargetTimespan / segnet.powTargetSpacing | 0;
segnet.powAllowMinDifficultyBlocks = true;
segnet.powNoRetargeting = false;
segnet.block = {
majorityEnforceUpgrade: 7,
majorityRejectOutdated: 9,
majorityWindow: 10
};
segnet.segwitHeight = 0;
segnet.genesisBlock = '0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4aa5619156ffff001d000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000';

View File

@ -128,9 +128,6 @@ Parser.prototype.parsePayload = function parsePayload(cmd, p) {
if (cmd === 'headers')
return Parser.parseHeaders(p);
// if (cmd === 'block')
// return Parser.parseBlock(p);
if (cmd === 'block')
return Parser.parseBlockCompact(p);
@ -334,7 +331,9 @@ Parser.parseHeaders = function parseHeaders(p) {
Parser.parseBlock = function parseBlock(p) {
var txs = [];
var cost = 0;
var i, result, off, totalTX, tx;
var witness;
if (p.length < 81)
throw new Error('Invalid block size');
@ -343,16 +342,22 @@ Parser.parseBlock = function parseBlock(p) {
off = result.off;
totalTX = result.r;
cost += off * 4;
for (i = 0; i < totalTX; i++) {
tx = Parser.parseTX(p.slice(off));
if (!tx)
throw new Error('Invalid tx count for block');
if (tx.witness)
witness = true;
tx._offset = off;
off += tx._size;
cost += tx._cost;
txs.push(tx);
}
return {
witness: witness,
version: utils.read32(p, 0),
prevBlock: p.slice(4, 36),
merkleRoot: p.slice(36, 68),
@ -362,7 +367,8 @@ Parser.parseBlock = function parseBlock(p) {
totalTX: totalTX,
txs: txs,
_raw: p,
_size: p.length
_size: p.length,
_cost: cost
};
};
@ -522,6 +528,9 @@ Parser.parseTX = function parseTX(p) {
if (p.length < 10)
throw new Error('Invalid tx size');
if (Parser.isWitnessTX(p))
return Parser.parseWitnessTX(p);
inCount = utils.readIntv(p, 4);
off = inCount.off;
inCount = inCount.r;
@ -575,11 +584,143 @@ Parser.parseTX = function parseTX(p) {
inputs: txIn,
outputs: txOut,
locktime: utils.readU32(p, off),
_cost: (off + 4) * 4,
_raw: p.slice(0, off + 4),
_size: off + 4
};
};
Parser.isWitnessTX = function isWitnessTX(p) {
if (p.length < 12)
return false;
return p[4] === 0 && p[5] !== 0;
};
Parser.parseWitnessTX = function parseWitnessTX(p) {
var cost = 0;
var inCount, off, txIn, tx;
var outCount, txOut;
var marker, flag;
var i;
if (p.length < 12)
throw new Error('Invalid witness tx size');
marker = utils.readU8(p, 4);
flag = utils.readU8(p, 5);
if (marker !== 0)
throw new Error('Invalid witness tx (marker != 0)');
if (flag === 0)
throw new Error('Invalid witness tx (flag == 0)');
inCount = utils.readIntv(p, 6);
off = inCount.off;
inCount = inCount.r;
if (inCount < 0)
throw new Error('Invalid witness tx_in count (negative)');
if (off + 41 * inCount + 5 > p.length)
throw new Error('Invalid witness tx_in count (too big)');
txIn = new Array(inCount);
for (i = 0; i < inCount; i++) {
tx = Parser.parseInput(p.slice(off));
if (!tx)
return;
txIn[i] = tx;
tx._offset = off;
off += tx._size;
if (off + 5 > p.length)
throw new Error('Invalid witness tx_in offset');
}
outCount = utils.readIntv(p, off);
off = outCount.off;
outCount = outCount.r;
if (outCount < 0)
throw new Error('Invalid witness tx_out count (negative)');
if (off + 9 * outCount + 4 > p.length)
throw new Error('Invalid witness tx_out count (too big)');
txOut = new Array(outCount);
for (i = 0; i < outCount; i++) {
tx = Parser.parseOutput(p.slice(off));
if (!tx)
return;
txOut[i] = tx;
tx._offset = off;
off += tx._size;
if (off + 4 > p.length)
throw new Error('Invalid tx_out offset');
}
cost += (off - 2) * 4;
for (i = 0; i < inCount; i++) {
tx = Parser.parseWitness(p.slice(off));
if (!tx)
return;
txIn[i].witness = tx.witness;
txIn[i]._witnessSize = tx._size;
txIn[i]._witnessOffset = off;
off += tx._size;
cost += tx._size;
if (off + 4 > p.length)
throw new Error('Invalid witness offset');
}
return {
witness: true,
version: utils.read32(p, 0),
marker: marker,
flag: flag,
inputs: txIn,
outputs: txOut,
locktime: utils.readU32(p, off),
_raw: p.slice(0, off + 4),
_size: off + 4,
_cost: cost
};
};
Parser.parseWitness = function parseWitness(p) {
var witness = [];
var off, chunkCount, chunkSize, item, i;
chunkCount = utils.readIntv(p, 0);
off = chunkCount.off;
chunkCount = chunkCount.r;
for (i = 0; i < chunkCount; i++) {
chunkSize = utils.readIntv(p, off);
off = chunkSize.off;
chunkSize = chunkSize.r;
item = p.slice(off, off + chunkSize);
off += chunkSize;
witness.push(item);
if (off > p.length)
throw new Error('Invalid witness offset');
}
return {
_size: off,
witness: witness
};
};
Parser.parseReject = function parseReject(p) {
var messageLen, off, message, ccode, reasonLen, reason, data;

View File

@ -203,6 +203,49 @@ script.encode = function encode(s) {
return res;
};
// Witnesses aren't scripts, but we still
// want to convert [0] to OP_0, [0xff] to 1negate, etc.
script.decodeWitness = function decodeWitness(witness) {
var chunk, i, op;
var script = [];
for (i = 0; i < witness.length; i++) {
chunk = witness[i];
op = chunk;
if (chunk.length === 1) {
if (chunk[0] === 0xff)
op = '1negate';
else if (chunk[0] >= 0 && chunk <= 16)
op = chunk[0];
}
script.push(op);
}
return script;
};
script.encodeWitness = function encodeWitness(script) {
var chunk, i, chunk;
var witness = [];
for (i = 0; i < script.length; i++) {
chunk = script[i];
if (chunk === '1negate')
chunk = new Buffer([0xff]);
else if (chunk >= 0 && chunk <= 16)
chunk = new Buffer([op]);
assert(Buffer.isBuffer(op));
witness.push(op);
}
return witness;
};
script.normalize = function normalize(s) {
var i, op;
@ -244,7 +287,7 @@ script.normalize = function normalize(s) {
return s;
};
script.verify = function verify(input, output, tx, i, flags) {
script.verify = function verify(input, witness, output, tx, i, flags) {
var copy, res, redeem;
var stack = [];
@ -270,6 +313,11 @@ script.verify = function verify(input, output, tx, i, flags) {
if (!res || stack.length === 0 || !script.bool(stack.pop()))
return false;
if ((flags & constants.flags.VERIFY_WITNESS) && script.isWitnessProgram(output)) {
if (!script.verifyProgram(input, witness, output, tx, i, flags))
return false;
}
// If the script is P2SH, execute the real output script
if ((flags & constants.flags.VERIFY_P2SH) && script.isScripthash(output)) {
// P2SH can only have push ops in the scriptSig
@ -297,10 +345,17 @@ script.verify = function verify(input, output, tx, i, flags) {
// Verify the script did not fail as well as the stack values
if (!res || stack.length === 0 || !script.bool(stack.pop()))
return false;
if ((flags & constants.flags.VERIFY_WITNESS) && script.isWitnessProgram(redeem)) {
if (!script.verifyProgram(input, witness, redeem, tx, i, flags))
return false;
}
}
// Ensure there is nothing left on the stack
if (flags & constants.flags.VERIFY_CLEANSTACK) {
assert((flags & constants.flags.VERIFY_P2SH) !== 0);
// assert((flags & constants.flags.VERIFY_WITNESS) !== 0);
if (stack.length !== 0)
return false;
}
@ -308,6 +363,85 @@ script.verify = function verify(input, output, tx, i, flags) {
return true;
};
script.verifyProgram = function verifyProgram(input, witness, output, tx, i, flags) {
var program, witnessScript, script, stack, j;
if (!(flags & constants.flags.VERIFY_WITNESS) || !script.isWitnessProgram(output))
return true;
program = script.getWitnessProgram(output);
// Failure on version=0 (bad program data length)
if (!program.type) {
utils.debug('Malformed witness program.');
return false;
}
if (program.version > 0) {
utils.debug('Unknown witness program version: %s', program.version);
// Anyone can spend (we can return true here
// if we want to always relay these transactions).
// Otherwise, if we want to act like an "old"
// implementation and only accept them in blocks,
// we can use the regalar output script which will
// succeed in a block, but fail in the mempool
// due to VERIFY_CLEANSTACK.
if (flags & constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
return false;
return true;
}
stack = witness.slice();
if (program.type === 'witnesspubkeyhash') {
if (input.length !== 0)
return false;
if (stack.length !== 2)
return false;
if (!Buffer.isBuffer(stack[0]) || !Buffer.isBuffer(stack[1]))
return false;
// Why the fuck are these allowed to be so big?
if (stack[0].length > 520 || stack[1].length > 520)
return false;
script = ['dup', 'hash160', program.data, 'equalverify', 'checksig'];
} else if (program.type === 'witnessscripthash') {
if (stack.length === 0)
return false;
witnessScript = stack.pop();
if (!Buffer.isBuffer(witnessScript))
return false;
if (witnessScript.length > constants.script.maxSize)
return false;
if (!utils.isEqual(utils.sha256(witnessScript), program.data))
return false;
script = script.decode(witnessScript);
} else {
assert(false);
}
for (j = 0; j < stack.length; j++) {
if (stack[j].length > constants.script.maxSize)
return false;
}
res = script.execute(output, stack, tx, i, flags);
// Verify the script did not fail as well as the stack values
if (!res || stack.length === 0 || !script.bool(stack.pop()))
return false;
return true;
};
script.getSubscript = function getSubscript(s, lastSep) {
var i, res;
@ -1272,6 +1406,19 @@ script.getRedeem = function getRedeem(s) {
script.getType =
script.getOutputType = function getOutputType(s) {
var program;
if (script.isCommitment(s))
return 'commitment';
if (script.isWitnessProgram(s)) {
if (script.isWitnessPubkeyhash(s))
return 'witnesspubkeyhash';
if (script.isWitnessScripthash(s))
return 'witnessscripthash';
return 'unknown';
}
return (script.isPubkey(s) && 'pubkey')
|| (script.isPubkeyhash(s) && 'pubkeyhash')
|| (script.isMultisig(s) && 'multisig')
@ -1443,7 +1590,7 @@ script._getInputData = function _getInputData(s, type) {
sig = new Buffer([]);
key = s[1];
hash = bcoin.address.hash160(key);
address = bcoin.address.toAddress(hash, 'pubkeyhash');
address = bcoin.address.compileHash(hash, 'pubkeyhash');
return {
type: 'pubkeyhash',
side: 'input',
@ -1473,7 +1620,7 @@ script._getInputData = function _getInputData(s, type) {
redeem = script.decode(raw);
locktime = script.getLocktime(redeem);
hash = bcoin.address.hash160(raw);
address = bcoin.address.toAddress(hash, 'scripthash');
address = bcoin.address.compileHash(hash, 'scripthash');
output = script.getOutputData(redeem, true);
input = script._getInputData(s.slice(0, -1), output.type);
delete input.none;
@ -1499,7 +1646,7 @@ script.getOutputData = function getOutputData(s, inScriptHash) {
key = s[0];
hash = bcoin.address.hash160(key);
// Convert p2pk to p2pkh addresses
address = bcoin.address.toAddress(hash, 'pubkeyhash');
address = bcoin.address.compileHash(hash, 'pubkeyhash');
return {
type: 'pubkey',
side: 'output',
@ -1512,7 +1659,7 @@ script.getOutputData = function getOutputData(s, inScriptHash) {
if (script.isPubkeyhash(s)) {
hash = s[2];
address = bcoin.address.toAddress(hash, 'pubkeyhash');
address = bcoin.address.compileHash(hash, 'pubkeyhash');
return {
type: 'pubkeyhash',
side: 'output',
@ -1529,12 +1676,12 @@ script.getOutputData = function getOutputData(s, inScriptHash) {
});
// Convert bare multisig to p2pkh addresses
address = hash.map(function(hash) {
return bcoin.address.toAddress(hash, 'pubkeyhash');
return bcoin.address.compileHash(hash, 'pubkeyhash');
});
// Convert bare multisig script to scripthash address
if (!inScriptHash) {
mhash = bcoin.address.hash160(s._raw || script.encode(s));
maddress = bcoin.address.toAddress(mhash, 'scripthash');
maddress = bcoin.address.compileHash(mhash, 'scripthash');
}
return {
type: 'multisig',
@ -1550,7 +1697,7 @@ script.getOutputData = function getOutputData(s, inScriptHash) {
if (script.isScripthash(s)) {
hash = s[1];
address = bcoin.address.toAddress(hash, 'scripthash');
address = bcoin.address.compileHash(hash, 'scripthash');
return {
type: 'scripthash',
side: 'output',
@ -1589,7 +1736,7 @@ script.getUnknownData = function getUnknownData(s) {
});
address = hash.map(function(hash) {
return bcoin.address.toAddress(hash, 'pubkeyhash');
return bcoin.address.compileHash(hash, 'pubkeyhash');
});
return {
@ -1681,31 +1828,40 @@ script.getInputAddress = function getInputAddress(s, prev) {
return;
if (script.isPubkeyhashInput(s))
return bcoin.address.compile(s[1], 'pubkeyhash');
return bcoin.address.compileData(s[1], 'pubkeyhash');
if (script.isMultisigInput(s))
return;
if (script.isScripthashInput(s))
return bcoin.address.compile(s[s.length - 1], 'scripthash');
return bcoin.address.compileData(s[s.length - 1], 'scripthash');
};
script.getOutputAddress = function getOutputAddress(s) {
var program;
if (script.isWitnessProgram(s)) {
program = script.getWitnessProgram(s);
if (!program.type || program.type === 'witnessunknown')
return;
return bcoin.address.compileHash(program.data, program.type);
}
// Convert p2pk to p2pkh addresses
if (script.isPubkey(s))
return bcoin.address.compile(s[0], 'pubkeyhash');
return bcoin.address.compileData(s[0], 'pubkeyhash');
if (script.isPubkeyhash(s))
return bcoin.address.toAddress(s[2], 'pubkeyhash')
return bcoin.address.compileHash(s[2], 'pubkeyhash')
// Convert bare multisig to scripthash address
if (script.isMultisig(s)) {
s = s._raw || script.encode(s);
return bcoin.address.compile(s, 'scripthash');
return bcoin.address.compileData(s, 'scripthash');
}
if (script.isScripthash(s))
return bcoin.address.toAddress(s[1], 'scripthash');
return bcoin.address.compileHash(s[1], 'scripthash');
};
script.getInputMN = function getInputMN(s, prev) {
@ -1888,15 +2044,105 @@ script.isNulldata = function isNulldata(s) {
return true;
};
script.getInputType = function getInputType(s, prev) {
script.isCommitment = function isCommitment(s) {
return s.length >= 2
&& s[0] === 'return'
&& Buffer.isBuffer(s[1])
&& s[1].length === 36
&& utils.readU32(s[1], 0) === 0xeda921aa24;
};
script.getWitnessRoot = function getWitnessRoot(s) {
if (!script.isCommitment(s))
return;
return s[1].slice(4, 36);
};
script.isWitnessProgram = function isWitnessProgram(s) {
if (s.length !== 2)
return false;
if (typeof s[0] !== 'number')
return false;
if (!Buffer.isBuffer(s[1]))
return false;
return s[0] >= 0 && s[0] <= 16
&& s[1].length >= 2 && s[1].length <= 32;
};
script.getWitnessProgram = function getWitnessProgram(s) {
var version, data, type;
if (!script.isWitnessProgram(s))
return;
version = s[0];
data = s[1];
if (version > 0) {
// No interpretation of script (anyone can spend)
type = 'witnessunknown';
} else if (version === 0 && data.length === 20) {
type = 'witnesspubkeyhash';
} else if (version === 0 && data.length === 32) {
type = 'witnessscripthash';
} else {
// Fail on bad version=0
type = null;
}
return {
version: version,
type: type,
data: data
};
};
script.isWitnessPubkeyhash = function isWitnessPubkeyhash(s) {
if (!script.isWitnessProgram(s))
return false;
return s[0] === 0 && s[1].length === 20;
};
script.isWitnessScripthash = function isWitnessScripthash(s) {
if (!script.isWitnessProgram(s))
return false;
return s[0] === 0 && s[1].length === 32;
};
script.createWitnessProgram = function createWitnessProgram(version, data) {
assert(typeof version === 'number' && version >= 0 && version <= 16);
assert(Buffer.isBuffer(data));
assert(data.length === 20 || data.length === 32);
return [version, data];
};
script.getInputType = function getInputType(s, prev, isWitness) {
var type;
if (prev)
return script.getOutputType(prev);
return (script.isPubkeyInput(s) && 'pubkey')
type = (script.isPubkeyInput(s) && 'pubkey')
|| (script.isPubkeyhashInput(s) && 'pubkeyhash')
|| (script.isMultisigInput(s) && 'multisig')
|| (script.isScripthashInput(s) && 'scripthash')
|| null;
|| 'unknown';
if (isWitness) {
if (type === 'pubkeyhash')
return 'witnesspubkeyhash';
if (type === 'scripthash')
return 'witnessscripthash';
return 'unknown';
}
return type;
};
script.isPubkeyInput = function isPubkeyInput(s, key, tx, index) {
@ -1956,7 +2202,7 @@ script.isMultisigInput = function isMultisigInput(s, keys, tx, index) {
if (s.length < 3)
return false;
if (!Buffer.isBuffer(s[0]) || s[0].length !== 0)
if (s[0] !== 0 && !script.isDummy(s[0]))
return false;
for (i = 1; i < s.length; i++) {
@ -1976,7 +2222,7 @@ script.isMultisigInput = function isMultisigInput(s, keys, tx, index) {
return false;
}
// We also also try to recover the keys from the signatures.
// We can also try to recover the keys from the signatures.
// if (keys) {
// var prev, recovered, j, total;
// recovered = [];
@ -2007,8 +2253,9 @@ script.isScripthashInput = function isScripthashInput(s, redeem) {
raw = s[s.length - 1];
// Need at least one data element with
// the redeem script.
if (s.length < 2)
// the redeem script. NOTE: NOT THE CASE FOR SEGWIT!
// if (s.length < 2)
if (s.length < 1)
return false;
// Last data element should be an array
@ -2392,6 +2639,31 @@ script.getScripthashSigops = function getScripthashSigops(s, prev) {
s = script.getRedeem(s);
// Need to do this too
// if (script.isWitnessProgram(s)) {
// call witness sigops
// }
return script.getSigops(s, true);
};
script.getWitnessSigops = function getWitnessSigops(s) {
if (script.isWitnessPubkeyhash(s))
return 1;
return 0;
};
script.getWitnessScripthashSigops = function getWitnessScripthashSigops(witness, prev) {
var redeem;
if (!prev)
return 0;
if (!script.isWitnessScripthash(prev))
return 0;
redeem = script.getRedeem(witness);
return script.getSigops(s, true);
};

View File

@ -33,9 +33,12 @@ function TX(data, block, index) {
this._hash = null;
this._raw = data._raw || null;
this._wraw = data._wraw || null;
this._size = data._size || 0;
this._offset = data._offset || 0;
this.witness = data.witness || false;
this.height = data.height != null ? data.height : -1;
this._chain = data.chain;
@ -60,6 +63,13 @@ function TX(data, block, index) {
}
}
if (this.witness) {
this._wsize = this._raw.length;
this._wraw = this._raw;
this._raw = null;
this._size = 0;
}
if (!this._raw)
this._raw = this.render();
@ -99,6 +109,12 @@ TX.prototype.clone = function clone() {
return tx;
};
TX.prototype.txid = function txid(enc) {
if (this.hasWitness())
return this.witnessHash(enc);
return this.hash(enc);
};
TX.prototype.hash = function hash(enc) {
if (!this._hash)
this._hash = utils.dsha256(this.render());
@ -106,10 +122,36 @@ TX.prototype.hash = function hash(enc) {
return enc === 'hex' ? utils.toHex(this._hash) : this._hash;
};
TX.prototype.witnessHash = function witnessHash(enc) {
if (!this._whash)
this._whash = utils.dsha256(this.renderWitness());
return enc === 'hex' ? utils.toHex(this._whash) : this._whash;
};
TX.prototype.hasWitness = function hasWitness() {
if (this.inputs.length === 0)
return false;
return this.inputs.every(function(input) {
return input.witness.length > 0;
});
};
TX.prototype.render = function render() {
if (this._raw)
return this._raw;
return bcoin.protocol.framer.tx(this);
if (!this._raw) {
this._raw = bcoin.protocol.framer.tx(this);
this._size = this._raw.length;
}
return this._raw;
};
TX.prototype.renderWitness = function renderWitness() {
if (!this._wraw) {
this._wraw = bcoin.protocol.framer.witnessTX(this);
this._wsize = this._wraw.length;
}
return this._wraw;
};
TX.prototype.getSize = function getSize() {
@ -217,7 +259,7 @@ TX.prototype.signatureHash = function signatureHash(index, s, type) {
return hash;
};
TX.prototype.tbsHash = function tbsHash(enc, force) {
TX.prototype.normalizedHash = function normalizedHash(enc, force) {
var copy = this.clone();
var i;
@ -265,7 +307,14 @@ TX.prototype.verify = function verify(index, force, flags) {
return false;
}
return bcoin.script.verify(input.script, input.output.script, this, i, flags);
return bcoin.script.verify(
input.script,
input.witness,
input.output.script,
this,
i,
flags
);
}, this);
};
@ -489,6 +538,24 @@ TX.prototype.getSigops = function getSigops(scriptHash, accurate) {
return n;
};
TX.prototype.getSigopsCost = function getSigopsCost(scriptHash, accurate) {
var n = 0;
this.inputs.forEach(function(input) {
var prev;
n += bcoin.script.getSigops(input.script, accurate) * 4;
if (scriptHash && !this.isCoinbase()) {
prev = input.output ? input.output.script : null;
n += bcoin.script.getScripthashSigops(input.script, prev) * 4;
}
n += bcoin.script.getWitnessScripthashSigops(input.witness, prev);
}, this);
this.outputs.forEach(function(output) {
n += bcoin.script.getSigops(output.script, accurate) * 4;
n += bcoin.script.getWitnessSigops(output.script);
}, this);
return n;
};
TX.prototype.isStandard = function isStandard(flags) {
var i, input, output, type;
var nulldata = 0;

View File

@ -1593,12 +1593,12 @@ utils.once = function once(callback) {
return onceFn;
};
utils.buildMerkleTree = function buildMerkleTree(items) {
var tree = items.slice();
utils.buildMerkleTree = function buildMerkleTree(leaves) {
var tree = leaves.slice();
var i, j, size, i2, hash;
j = 0;
size = items.length;
size = leaves.length;
for (; size > 1; size = ((size + 1) / 2) | 0) {
for (i = 0; i < size; i += 2) {
@ -1620,16 +1620,16 @@ utils.buildMerkleTree = function buildMerkleTree(items) {
return tree;
};
utils.getMerkleRoot = function getMerkleRoot(items) {
var tree = utils.buildMerkleTree(items);
utils.getMerkleRoot = function getMerkleRoot(leaves) {
var tree = utils.buildMerkleTree(leaves);
if (!tree)
return;
return tree[tree.length - 1];
};
utils.getMerkleBranch = function getMerkleBranch(index, hashes) {
var tree = utils.buildMerkleTree(hashes);
utils.getMerkleBranch = function getMerkleBranch(index, leaves) {
var tree = utils.buildMerkleTree(leaves);
var branch = [];
var size = this.totalTX;
var j = 0;

View File

@ -72,7 +72,7 @@ function Wallet(options) {
assert(this.type === 'pubkeyhash' || this.type === 'multisig');
this.prefixType = this.type === 'multisig' ? 'scripthash' : 'pubkeyhash';
if (network.prefixes[this.prefixType] == null)
if (network.address.prefixes[this.prefixType] == null)
throw new Error('Unknown prefix: ' + this.prefixType);
if (this.m < 1 || this.m > this.n)
@ -281,7 +281,7 @@ Wallet.prototype.getID = function getID() {
if (this.options.id)
return this.options.id;
return bcoin.address.compile(this.accountKey.publicKey);
return bcoin.address.compileData(this.accountKey.publicKey);
};
Wallet.prototype.createReceive = function createReceive() {

View File

@ -4,7 +4,7 @@ var utils = bcoin.utils;
var bn = bcoin.bn;
function createGenesisBlock(options) {
var parser = new bcoin.protocol.parser();
var parser = bcoin.protocol.parser;
var tx, block;
if (!options.flags) {
@ -98,9 +98,17 @@ var regtest = createGenesisBlock({
nonce: 2
});
var segnet = createGenesisBlock({
version: 1,
ts: 1452368293,
bits: 0x1d00ffff,
nonce: 0
});
utils.print(main);
utils.print(testnet);
utils.print(regtest);
utils.print(segnet);
utils.print('main hash: %s', main.hash);
utils.print('main raw: %s', utils.toHex(main._raw));
utils.print('');
@ -109,3 +117,5 @@ utils.print('testnet raw: %s', utils.toHex(testnet._raw));
utils.print('');
utils.print('regtest hash: %s', regtest.hash);
utils.print('regtest raw: %s', utils.toHex(regtest._raw));
utils.print('segnet hash: %s', segnet.hash);
utils.print('segnet raw: %s', utils.toHex(segnet._raw));

View File

@ -3,6 +3,24 @@ var bn = require('bn.js');
var bcoin = require('../');
var constants = bcoin.protocol.constants;
var dummyInput = {
prevout: {
hash: constants.zeroHash,
index: 0
},
output: {
version: 1,
height: 0,
value: constants.maxMoney.clone(),
script: [],
hash: constants.zeroHash,
index: 0,
spent: false
},
script: [],
sequence: 0xffffffff
};
describe('Wallet', function() {
it('should generate new key and address', function() {
var w = bcoin.wallet();
@ -29,9 +47,10 @@ describe('Wallet', function() {
address: w.getAddress()
}, {
value: 5460 * 2,
address: w.getAddress() + 'x'
address: bcoin.address.compileData(new Buffer([]))
}]
});
src.addInput(dummyInput);
assert(w.ownOutput(src));
assert.equal(w.ownOutput(src).reduce(function(acc, out) {
return acc.iadd(out.value);
@ -63,9 +82,10 @@ describe('Wallet', function() {
keys: [ w.getPublicKey(), k2.derive('m/0/0').publicKey ]
}, {
value: 5460 * 2,
address: w.getAddress() + 'x'
address: bcoin.address.compileData(new Buffer([]))
}]
});
src.addInput(dummyInput);
assert(w.ownOutput(src));
assert.equal(w.ownOutput(src).reduce(function(acc, out) {
return acc.iadd(out.value);
@ -87,6 +107,7 @@ describe('Wallet', function() {
// Coinbase
var t1 = bcoin.mtx().addOutput(w, 50000).addOutput(w, 1000);
t1.addInput(dummyInput);
// balance: 51000
w.sign(t1);
var t2 = bcoin.mtx().addInput(t1, 0) // 50000
@ -191,10 +212,12 @@ describe('Wallet', function() {
// Coinbase
var t1 = bcoin.mtx().addOutput(w1, 5460).addOutput(w1, 5460).addOutput(w1, 5460).addOutput(w1, 5460);
t1.addInput(dummyInput);
// Fake TX should temporarly change output
w1.addTX(t1);
// Coinbase
var t2 = bcoin.mtx().addOutput(w2, 5460).addOutput(w2, 5460).addOutput(w2, 5460).addOutput(w2, 5460);
t2.addInput(dummyInput);
// Fake TX should temporarly change output
w2.addTX(t2);
@ -290,6 +313,7 @@ describe('Wallet', function() {
// Add a shared unspent transaction to our wallets
var utx = bcoin.mtx();
utx.addOutput({ address: addr, value: 5460 * 10 });
utx.addInput(dummyInput);
// Simulate a confirmation
utx.ps = 0;