segregated - goddamn - witness... and docs update.
This commit is contained in:
parent
57ceb627d2
commit
4c653683b7
125
README.md
125
README.md
@ -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:
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user