script: refactor script and stack mutation.

This allows more reasonable signing behavior and eliminates all polymorphism.
This commit is contained in:
Christopher Jeffrey 2017-08-21 04:32:06 -07:00
parent 9d74c837e8
commit d6ce66bce9
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
33 changed files with 2875 additions and 2040 deletions

View File

@ -27,10 +27,8 @@ const tx10 = common.readTX('tx10');
const [tx, view] = tx5.getTX();
const end = bench('sigops');
let sigops = 0;
for (let i = 0; i < 100000; i++)
sigops += tx.getSigopsCost(view);
tx.getSigopsCost(view);
end(100000);
}
@ -161,10 +159,10 @@ for (let i = 0; i < 100; i++) {
hash: encoding.NULL_HASH,
index: 0
},
script: [
Buffer.allocUnsafe(9),
random.randomBytes(33)
]
script: new Script()
.pushData(Buffer.allocUnsafe(9))
.pushData(random.randomBytes(33))
.compile()
});
mtx.addOutput({
address: Address.fromHash(random.randomBytes(20)),

View File

@ -1,37 +1,38 @@
Scripts are array-like objects with some helper functions.
``` js
var bcoin = require('bcoin');
var assert = require('assert');
var ScriptNum = bcoin.scriptnum;
var opcodes = bcoin.script.opcodes;
const bcoin = require('bcoin');
const assert = require('assert');
const Script = bcoin.script;
const Witness = bcoin.witness;
const Stack = bcoin.stack;
var output = new bcoin.script();
output.push(opcodes.OP_DROP);
output.push(opcodes.OP_ADD);
output.push(new ScriptNum(7));
output.push(opcodes.OP_NUMEQUAL);
const output = new Script();
output.pushSym('OP_DROP');
output.pushSym('OP_ADD');
output.pushInt(7);
output.pushSym('OP_NUMEQUAL');
// Compile the script to its binary representation
// (you must do this if you change something!).
output.compile();
assert(output.getSmall(2) === 7); // compiled as OP_7
output.compile();
var input = new bcoin.script();
input.set(0, 'hello world'); // add some metadata
input.push(new ScriptNum(2));
input.push(new ScriptNum(5));
const input = new Script();
input.setString(0, 'hello world'); // add some metadata
input.pushInt(2);
input.pushInt(5);
input.push(input.shift());
assert(input.getString(2) === 'hello world');
input.compile();
// A stack is another array-like object which contains
// only Buffers (whereas scripts contain Opcode objects).
var stack = new bcoin.stack();
const stack = new Stack();
input.execute(stack);
output.execute(stack);
// Verify the script was successful in its execution:
assert(stack.length === 1);
assert(bcoin.script.bool(stack.pop()) === true);
assert(stack.getBool(-1) === true);
```
Using a witness would be similar, but witnesses do not get executed, they
@ -39,11 +40,11 @@ simply _become_ the stack. The witness object itself is very similar to the
Stack object (an array-like object containing Buffers).
``` js
var witness = new bcoin.witness();
witness.push(new ScriptNum(2));
witness.push(new ScriptNum(5));
witness.push('hello world');
const witness = new Witness();
witness.pushInt(2);
witness.pushInt(5);
witness.pushString('hello world');
var stack = witness.toStack();
const stack = witness.toStack();
output.execute(stack);
```

View File

@ -38,29 +38,29 @@ function compressScript(script, bw) {
// P2PKH -> 0 | key-hash
// Saves 5 bytes.
if (script.isPubkeyhash(true)) {
const hash = script.code[2].data;
const pkh = script.getPubkeyhash(true);
if (pkh) {
bw.writeU8(0);
bw.writeBytes(hash);
bw.writeBytes(pkh);
return bw;
}
// P2SH -> 1 | script-hash
// Saves 3 bytes.
if (script.isScripthash()) {
const hash = script.code[1].data;
const sh = script.getScripthash();
if (sh) {
bw.writeU8(1);
bw.writeBytes(hash);
bw.writeBytes(sh);
return bw;
}
// P2PK -> 2-5 | compressed-key
// Only works if the key is valid.
// Saves up to 35 bytes.
if (script.isPubkey(true)) {
const data = script.code[0].data;
if (publicKeyVerify(data)) {
const key = compressKey(data);
const pk = script.getPubkey(true);
if (pk) {
if (publicKeyVerify(pk)) {
const key = compressKey(pk);
bw.writeBytes(key);
return bw;
}
@ -135,9 +135,9 @@ function sizeScript(script) {
if (script.isScripthash())
return 21;
if (script.isPubkey(true)) {
const key = script.code[0].data;
if (publicKeyVerify(key))
const pk = script.getPubkey(true);
if (pk) {
if (publicKeyVerify(pk))
return 33;
}

View File

@ -1408,10 +1408,11 @@ RPC.prototype._createTemplate = async function _createTemplate(maxVersion, coinb
// instead of a coinbasevalue.
if (coinbase) {
const tx = attempt.toCoinbase();
const input = tx.inputs[0];
// Pop off the nonces.
tx.inputs[0].script.code.pop();
tx.inputs[0].script.compile();
input.script.pop();
input.script.compile();
if (attempt.witness) {
// We don't include the commitment
@ -1420,12 +1421,11 @@ RPC.prototype._createTemplate = async function _createTemplate(maxVersion, coinb
assert(output.script.isCommitment());
// Also not including the witness nonce.
tx.inputs[0].witness.length = 0;
tx.inputs[0].witness.compile();
tx.refresh();
input.witness.clear();
}
tx.refresh();
json.coinbasetxn = {
data: tx.toRaw().toString('hex'),
txid: tx.txid(),
@ -2334,13 +2334,14 @@ RPC.prototype._addBlock = async function _addBlock(block) {
// Fix eloipool bug (witness nonce is not present).
if (state.hasWitness() && block.getCommitmentHash()) {
const tx = block.txs[0];
const input = tx.inputs[0];
if (!tx.hasWitness()) {
this.logger.warning('Submitted block had no witness nonce.');
this.logger.debug(tx);
// Recreate witness nonce (all zeroes).
tx.inputs[0].witness.set(0, encoding.ZERO_HASH);
tx.inputs[0].witness.compile();
input.witness.push(encoding.ZERO_HASH);
input.witness.compile();
tx.refresh();
block.refresh();

View File

@ -22,7 +22,6 @@ const policy = require('../protocol/policy');
const encoding = require('../utils/encoding');
const CoinView = require('../coins/coinview');
const Script = require('../script/script');
const ScriptNum = require('../script/scriptnum');
const common = require('./common');
const DUMMY = Buffer.alloc(0);
@ -241,24 +240,23 @@ BlockTemplate.prototype.createCoinbase = function createCoinbase(hash) {
const input = new Input();
// Height (required in v2+ blocks)
const height = ScriptNum.fromNumber(this.height);
input.script.push(height);
input.script.pushInt(this.height);
// Coinbase flags.
input.script.push(encoding.ZERO_HASH160);
input.script.pushData(encoding.ZERO_HASH160);
// Smaller nonce for good measure.
input.script.push(util.nonce(4));
input.script.pushData(util.nonce(4));
// Extra nonce: incremented when
// the nonce overflows.
input.script.push(encoding.ZERO_U64);
input.script.pushData(encoding.ZERO_U64);
input.script.compile();
// Set up the witness nonce.
if (this.witness) {
input.witness.set(0, encoding.ZERO_HASH);
input.witness.push(encoding.ZERO_HASH);
input.witness.compile();
}
@ -281,7 +279,9 @@ BlockTemplate.prototype.createCoinbase = function createCoinbase(hash) {
}
// Padding for the CB height (constant size).
const padding = 5 - input.script.code[0].getSize();
const op = input.script.get(0);
assert(op);
const padding = 5 - op.getSize();
assert(padding >= 0);
// Reserved size.
@ -306,11 +306,10 @@ BlockTemplate.prototype.createCoinbase = function createCoinbase(hash) {
}
// Setup coinbase flags (variable size).
input.script.set(1, this.coinbaseFlags);
input.script.setData(1, this.coinbaseFlags);
input.script.compile();
// Setup output script (variable size).
output.script.clear();
output.script.fromAddress(this.address);
cb.refresh();

View File

@ -426,45 +426,39 @@ Address.fromBech32 = function fromBech32(data, network) {
*/
Address.prototype.fromScript = function fromScript(script) {
if (script.isPubkey()) {
this.hash = digest.hash160(script.get(0));
const pk = script.getPubkey();
if (pk) {
this.hash = digest.hash160(pk);
this.type = Address.types.PUBKEYHASH;
this.version = -1;
return this;
}
if (script.isPubkeyhash()) {
this.hash = script.get(2);
const pkh = script.getPubkeyhash();
if (pkh) {
this.hash = pkh;
this.type = Address.types.PUBKEYHASH;
this.version = -1;
return this;
}
if (script.isScripthash()) {
this.hash = script.get(1);
const sh = script.getScripthash();
if (sh) {
this.hash = sh;
this.type = Address.types.SCRIPTHASH;
this.version = -1;
return this;
}
if (script.isWitnessPubkeyhash()) {
this.hash = script.get(1);
this.type = Address.types.WITNESS;
this.version = 0;
return this;
}
const program = script.getProgram();
if (script.isWitnessScripthash()) {
this.hash = script.get(1);
if (program && !program.isMalformed()) {
this.hash = program.data;
this.type = Address.types.WITNESS;
this.version = 0;
return this;
}
if (script.isWitnessMasthash()) {
this.hash = script.get(1);
this.type = Address.types.WITNESS;
this.version = 1;
this.version = program.version;
return this;
}
@ -486,17 +480,21 @@ Address.prototype.fromScript = function fromScript(script) {
*/
Address.prototype.fromWitness = function fromWitness(witness) {
const [, pk] = witness.getPubkeyhashInput();
// We're pretty much screwed here
// since we can't get the version.
if (witness.isPubkeyhashInput()) {
this.hash = digest.hash160(witness.get(1));
if (pk) {
this.hash = digest.hash160(pk);
this.type = Address.types.WITNESS;
this.version = 0;
return this;
}
if (witness.isScripthashInput()) {
this.hash = digest.sha256(witness.get(witness.length - 1));
const redeem = witness.getScripthashInput();
if (redeem) {
this.hash = digest.sha256(redeem);
this.type = Address.types.WITNESS;
this.version = 0;
return this;
@ -512,15 +510,19 @@ Address.prototype.fromWitness = function fromWitness(witness) {
*/
Address.prototype.fromInputScript = function fromInputScript(script) {
if (script.isPubkeyhashInput()) {
this.hash = digest.hash160(script.get(1));
const [, pk] = script.getPubkeyhashInput();
if (pk) {
this.hash = digest.hash160(pk);
this.type = Address.types.PUBKEYHASH;
this.version = -1;
return this;
}
if (script.isScripthashInput()) {
this.hash = digest.hash160(script.get(script.length - 1));
const redeem = script.getScripthashInput();
if (redeem) {
this.hash = digest.hash160(redeem);
this.type = Address.types.SCRIPTHASH;
this.version = -1;
return this;

View File

@ -384,7 +384,7 @@ Block.prototype.getCommitmentHash = function getCommitmentHash(enc) {
for (let i = coinbase.outputs.length - 1; i >= 0; i--) {
const output = coinbase.outputs[i];
if (output.script.isCommitment()) {
hash = output.script.getCommitmentHash();
hash = output.script.getCommitment();
break;
}
}

View File

@ -89,6 +89,28 @@ Input.prototype.clone = function clone() {
return input;
};
/**
* Test equality against another input.
* @param {Input} input
* @returns {Boolean}
*/
Input.prototype.equals = function equals(input) {
assert(Input.isInput(input));
return this.prevout.equals(input.prevout);
};
/**
* Compare against another input (BIP69).
* @param {Input} input
* @returns {Number}
*/
Input.prototype.compare = function compare(input) {
assert(Input.isInput(input));
return this.prevout.compare(input.prevout);
};
/**
* Get the previous output script type as a string.
* Will "guess" based on the input script and/or
@ -488,11 +510,7 @@ Input.fromTX = function fromTX(tx, index) {
*/
Input.isInput = function isInput(obj) {
return obj
&& typeof obj.prevout === 'object'
&& typeof obj.script === 'object'
&& typeof obj.witness === 'object'
&& typeof obj.getAddress === 'function';
return obj instanceof Input;
};
/*

View File

@ -21,7 +21,7 @@ const encoding = require('../utils/encoding');
const consensus = require('../protocol/consensus');
const policy = require('../protocol/policy');
const Amount = require('../btc/amount');
const opcodes = Script.opcodes;
const Stack = require('../script/stack');
/**
* A mutable transaction object.
@ -436,20 +436,22 @@ MTX.prototype.scriptInput = function scriptInput(index, coin, ring) {
// Don't bother with any below calculation
// if the output is already templated.
if (input.script.length !== 0
|| input.witness.length !== 0) {
if (input.script.raw.length !== 0
|| input.witness.items.length !== 0) {
return true;
}
// Get the previous output's script
let prev = coin.script;
const prev = coin.script;
// This is easily the hardest part about
// building a transaction with segwit:
// figuring out where the redeem script
// and witness redeem scripts go.
if (prev.isScripthash()) {
const redeem = ring.getRedeem(prev.get(1));
const sh = prev.getScripthash();
if (sh) {
const redeem = ring.getRedeem(sh);
if (!redeem)
return false;
@ -457,33 +459,37 @@ MTX.prototype.scriptInput = function scriptInput(index, coin, ring) {
// Witness program nested in regular P2SH.
if (redeem.isProgram()) {
// P2WSH nested within pay-to-scripthash.
if (redeem.isWitnessScripthash()) {
prev = ring.getRedeem(redeem.get(1));
const wsh = redeem.getWitnessScripthash();
if (wsh) {
const wredeem = ring.getRedeem(wsh);
if (!prev)
if (!wredeem)
return false;
if (!this.scriptVector(prev, input.witness, ring))
const witness = this.scriptVector(wredeem, ring);
if (!witness)
return false;
input.witness.push(prev.toRaw());
input.witness.compile();
witness.push(wredeem.toRaw());
input.script.push(redeem.toRaw());
input.script.compile();
input.witness.fromStack(witness);
input.script.fromItems([redeem.toRaw()]);
return true;
}
// P2WPKH nested within pay-to-scripthash.
if (redeem.isWitnessPubkeyhash()) {
prev = Script.fromPubkeyhash(ring.getKeyHash());
const wpkh = redeem.getWitnessPubkeyhash();
if (wpkh) {
const pkh = Script.fromPubkeyhash(wpkh);
const witness = this.scriptVector(pkh, ring);
if (!this.scriptVector(prev, input.witness, ring))
if (!witness)
return false;
input.script.push(redeem.toRaw());
input.script.compile();
input.witness.fromStack(witness);
input.script.fromItems([redeem.toRaw()]);
return true;
}
@ -493,11 +499,14 @@ MTX.prototype.scriptInput = function scriptInput(index, coin, ring) {
}
// Regular P2SH.
if (!this.scriptVector(redeem, input.script, ring))
const vector = this.scriptVector(redeem, ring);
if (!vector)
return false;
input.script.push(redeem.toRaw());
input.script.compile();
vector.push(redeem.toRaw());
input.script.fromStack(vector);
return true;
}
@ -505,29 +514,35 @@ MTX.prototype.scriptInput = function scriptInput(index, coin, ring) {
// Witness program.
if (prev.isProgram()) {
// Bare P2WSH.
if (prev.isWitnessScripthash()) {
const redeem = ring.getRedeem(prev.get(1));
const wsh = prev.getWitnessScripthash();
if (wsh) {
const wredeem = ring.getRedeem(wsh);
if (!redeem)
if (!wredeem)
return false;
if (!this.scriptVector(redeem, input.witness, ring))
const vector = this.scriptVector(wredeem, ring);
if (!vector)
return false;
input.witness.push(redeem.toRaw());
input.witness.compile();
vector.push(wredeem.toRaw());
input.witness.fromStack(vector);
return true;
}
// Bare P2WPKH.
if (prev.isWitnessPubkeyhash()) {
prev = Script.fromPubkeyhash(prev.get(1));
const wpkh = prev.getWitnessPubkeyhash();
if (wpkh) {
const pkh = Script.fromPubkeyhash(wpkh);
const vector = this.scriptVector(pkh, ring);
if (!this.scriptVector(prev, input.witness, ring))
if (!vector)
return false;
input.witness.compile();
input.witness.fromStack(vector);
return true;
}
@ -537,61 +552,73 @@ MTX.prototype.scriptInput = function scriptInput(index, coin, ring) {
}
// Wow, a normal output! Praise be to Jengus and Gord.
return this.scriptVector(prev, input.script, ring);
const vector = this.scriptVector(prev, ring);
if (!vector)
return false;
input.script.fromStack(vector);
return true;
};
/**
* Build script for a single vector
* based on a previous script.
* @param {Script} prev
* @param {Witness|Script} vector
* @param {Buffer} ring
* @return {Boolean}
*/
MTX.prototype.scriptVector = function scriptVector(prev, vector, ring) {
MTX.prototype.scriptVector = function scriptVector(prev, ring) {
// P2PK
if (prev.isPubkey()) {
if (!prev.get(0).equals(ring.publicKey))
return false;
const pk = prev.getPubkey();
if (pk) {
if (!pk.equals(ring.publicKey))
return null;
vector.set(0, opcodes.OP_0);
const stack = new Stack();
return true;
stack.pushInt(0);
return stack;
}
// P2PKH
if (prev.isPubkeyhash()) {
if (!prev.get(2).equals(ring.getKeyHash()))
return false;
const pkh = prev.getPubkeyhash();
if (pkh) {
if (!pkh.equals(ring.getKeyHash()))
return null;
vector.set(0, opcodes.OP_0);
vector.set(1, ring.publicKey);
const stack = new Stack();
return true;
stack.pushInt(0);
stack.pushData(ring.publicKey);
return stack;
}
// Multisig
if (prev.isMultisig()) {
const [, n] = prev.getMultisig();
if (n !== -1) {
if (prev.indexOf(ring.publicKey) === -1)
return false;
return null;
// Technically we should create m signature slots,
// but we create n signature slots so we can order
// the signatures properly.
vector.set(0, opcodes.OP_0);
const stack = new Stack();
// Grab `n` value (number of keys).
const n = prev.getSmall(prev.length - 2);
stack.pushInt(0);
// Fill script with `n` signature slots.
for (let i = 0; i < n; i++)
vector.set(i + 1, opcodes.OP_0);
stack.pushInt(0);
return true;
return stack;
}
return false;
return null;
};
/**
@ -605,11 +632,11 @@ MTX.prototype.scriptVector = function scriptVector(prev, vector, ring) {
* @returns {Promise}
*/
MTX.prototype.signInputAsync = function signInputAsync(index, coin, ring, type, pool) {
MTX.prototype.signInputAsync = async function signInputAsync(index, coin, ring, type, pool) {
if (!pool)
return this.signInput(index, coin, ring, type);
return pool.signInput(this, index, coin, ring, type, pool);
return await pool.signInput(this, index, coin, ring, type, pool);
};
/**
@ -656,32 +683,51 @@ MTX.prototype.signInput = function signInput(index, coin, ring, type) {
vector = input.witness;
redeem = true;
version = 1;
} else if (prev.isWitnessPubkeyhash()) {
prev = Script.fromPubkeyhash(prev.get(1));
vector = input.witness;
redeem = false;
version = 1;
} else {
const wpkh = prev.getWitnessPubkeyhash();
if (wpkh) {
prev = Script.fromPubkeyhash(wpkh);
vector = input.witness;
redeem = false;
version = 1;
}
}
// Create our signature.
const sig = this.signature(index, prev, value, key, type, version);
if (redeem) {
const redeem = vector.pop();
const result = this.signVector(prev, vector, sig, ring);
vector.push(redeem);
vector.compile();
return result;
const stack = vector.toStack();
const redeem = stack.pop();
const result = this.signVector(prev, stack, sig, ring);
if (!result)
return false;
result.push(redeem);
vector.fromStack(result);
return true;
}
return this.signVector(prev, vector, sig, ring);
const stack = vector.toStack();
const result = this.signVector(prev, stack, sig, ring);
if (!result)
return false;
vector.fromStack(result);
return true;
};
/**
* Add a signature to a vector
* based on a previous script.
* @param {Script} prev
* @param {Witness|Script} vector
* @param {Stack} vector
* @param {Buffer} sig
* @param {KeyRing} ring
* @return {Boolean}
@ -689,79 +735,83 @@ MTX.prototype.signInput = function signInput(index, coin, ring, type) {
MTX.prototype.signVector = function signVector(prev, vector, sig, ring) {
// P2PK
if (prev.isPubkey()) {
const pk = prev.getPubkey();
if (pk) {
// Make sure the pubkey is ours.
if (!ring.publicKey.equals(prev.get(0)))
return false;
if (!ring.publicKey.equals(pk))
return null;
// Already signed.
if (Script.isSignature(vector.get(0)))
return true;
if (vector.getSmall(0) !== 0)
if (vector.length === 0)
throw new Error('Input has not been templated.');
vector.set(0, sig);
vector.compile();
// Already signed.
if (vector.get(0).length > 0)
return vector;
return true;
vector.set(0, sig);
return vector;
}
// P2PKH
if (prev.isPubkeyhash()) {
const pkh = prev.getPubkeyhash();
if (pkh) {
// Make sure the pubkey hash is ours.
if (!ring.getKeyHash().equals(prev.get(2)))
return false;
if (!ring.getKeyHash().equals(pkh))
return null;
// Already signed.
if (Script.isSignature(vector.get(0)))
return true;
if (!Script.isKey(vector.get(1)))
if (vector.length !== 2)
throw new Error('Input has not been templated.');
vector.set(0, sig);
vector.compile();
if (vector.get(1).length === 0)
throw new Error('Input has not been templated.');
return true;
// Already signed.
if (vector.get(0).length > 0)
return vector;
vector.set(0, sig);
return vector;
}
// Multisig
if (prev.isMultisig()) {
if (vector.getSmall(0) !== 0)
const [m, n] = prev.getMultisig();
if (m !== -1) {
if (vector.length < 2)
throw new Error('Input has not been templated.');
// Grab `n` value (number of keys).
const n = prev.getSmall(prev.length - 2);
if (vector.get(0).length !== 0)
throw new Error('Input has not been templated.');
// Too many signature slots. Abort.
if (vector.length - 1 > n)
return false;
throw new Error('Input has not been templated.');
// Count the number of current signatures.
let total = 0;
for (let i = 1; i < vector.length; i++) {
if (Script.isSignature(vector.get(i)))
const item = vector.get(i);
if (item.length > 0)
total++;
}
// Grab `m` value (number of sigs required).
const m = prev.getSmall(0);
// Signatures are already finalized.
if (total === m && vector.length - 1 === m)
return true;
return vector;
// Add some signature slots for us to use if
// there was for some reason not enough.
while (vector.length - 1 < n)
vector.push(opcodes.OP_0);
vector.pushInt(0);
// Grab the redeem script's keys to figure
// out where our key should go.
const keys = [];
for (let i = 1; i < prev.length - 2; i++)
keys.push(prev.get(i));
for (const op of prev.code) {
if (op.data)
keys.push(op.data);
}
// Find the key index so we can place
// the signature in the same index.
@ -771,7 +821,7 @@ MTX.prototype.signVector = function signVector(prev, vector, sig, ring) {
// script. We tried to sign a transaction
// that is not redeemable by us.
if (keyIndex === -1)
return false;
return null;
// Offset key index by one to turn it into
// "sig index". Accounts for OP_0 byte at
@ -782,7 +832,7 @@ MTX.prototype.signVector = function signVector(prev, vector, sig, ring) {
// and increment the total number of
// signatures.
if (keyIndex < vector.length && total < m) {
if (vector.getSmall(keyIndex) === 0) {
if (vector.get(keyIndex).length === 0) {
vector.set(keyIndex, sig);
total++;
}
@ -792,7 +842,8 @@ MTX.prototype.signVector = function signVector(prev, vector, sig, ring) {
if (total >= m) {
// Remove empty slots left over.
for (let i = vector.length - 1; i >= 1; i--) {
if (vector.getSmall(i) === 0)
const item = vector.get(i);
if (item.length === 0)
vector.remove(i);
}
@ -808,15 +859,10 @@ MTX.prototype.signVector = function signVector(prev, vector, sig, ring) {
assert(vector.length - 1 === m);
}
vector.compile();
if (total !== m)
return false;
return true;
return vector;
}
return false;
return null;
};
/**
@ -873,46 +919,57 @@ MTX.prototype.isInputSigned = function isInputSigned(index, coin) {
return false;
vector = input.witness;
redeem = true;
} else if (prev.isWitnessPubkeyhash()) {
prev = Script.fromPubkeyhash(prev.get(1));
vector = input.witness;
redeem = false;
} else {
const wpkh = prev.getWitnessPubkeyhash();
if (wpkh) {
prev = Script.fromPubkeyhash(wpkh);
vector = input.witness;
redeem = false;
}
}
if (redeem) {
const redeem = vector.pop();
const result = this.isVectorSigned(prev, vector);
vector.push(redeem);
return result;
}
const stack = vector.toStack();
return this.isVectorSigned(prev, vector);
if (redeem)
stack.pop();
return this.isVectorSigned(prev, stack);
};
/**
* Test whether a vector is fully-signed.
* @param {Script} prev
* @param {Script|Witness} vector
* @param {Stack} vector
* @returns {Boolean}
*/
MTX.prototype.isVectorSigned = function isVectorSigned(prev, vector) {
if (prev.isPubkey()) {
if (!Script.isSignature(vector.get(0)))
if (vector.length !== 1)
return false;
if (vector.get(0).length === 0)
return false;
return true;
}
if (prev.isPubkeyhash()) {
if (!Script.isSignature(vector.get(0)))
if (vector.length !== 2)
return false;
if (vector.get(0).length === 0)
return false;
if (vector.get(1).length === 0)
return false;
return true;
}
if (prev.isMultisig()) {
// Grab `m` value (number of required sigs).
const m = prev.getSmall(0);
const [m] = prev.getMultisig();
if (m !== -1) {
// Ensure we have the correct number
// of required signatures.
if (vector.length - 1 !== m)
@ -920,7 +977,8 @@ MTX.prototype.isVectorSigned = function isVectorSigned(prev, vector) {
// Ensure all members are signatures.
for (let i = 1; i < vector.length; i++) {
if (!Script.isSignature(vector.get(i)))
const item = vector.get(i);
if (item.length === 0)
return false;
}
@ -1020,11 +1078,11 @@ MTX.prototype.sign = function sign(ring, type) {
* @returns {Promise}
*/
MTX.prototype.signAsync = function signAsync(ring, type, pool) {
MTX.prototype.signAsync = async function signAsync(ring, type, pool) {
if (!pool)
return this.sign(ring, type);
return pool.sign(this, ring, type);
return await pool.sign(this, ring, type);
};
/**
@ -1035,6 +1093,7 @@ MTX.prototype.signAsync = function signAsync(ring, type, pool) {
MTX.prototype.estimateSize = async function estimateSize(estimate) {
const scale = consensus.WITNESS_SCALE_FACTOR;
let total = 0;
// Calculate the size, minus the input scripts.
@ -1083,13 +1142,14 @@ MTX.prototype.estimateSize = async function estimateSize(estimate) {
continue;
}
if (prev.isMultisig()) {
const [m] = prev.getMultisig();
if (m !== -1) {
let size = 0;
// Bare Multisig
// OP_0
size += 1;
// OP_PUSHDATA0 [signature] ...
size += (1 + 73) * prev.getSmall(0);
size += (1 + 73) * m;
// varint len
size += encoding.sizeVarint(size);
total += size;
@ -1427,10 +1487,7 @@ MTX.fromTX = function fromTX(tx) {
*/
MTX.isMTX = function isMTX(obj) {
return obj
&& Array.isArray(obj.inputs)
&& typeof obj.locktime === 'number'
&& typeof obj.scriptInput === 'function';
return obj instanceof MTX;
};
/**
@ -1849,23 +1906,11 @@ function sortValue(a, b) {
}
function sortInputs(a, b) {
const ahash = a.prevout.txid();
const bhash = b.prevout.txid();
const cmp = util.strcmp(ahash, bhash);
if (cmp !== 0)
return cmp;
return a.prevout.index - b.prevout.index;
return a.compare(b);
}
function sortOutputs(a, b) {
const cmp = a.value - b.value;
if (cmp !== 0)
return cmp;
return a.script.raw.compare(b.script.raw);
return a.compare(b);
}
/*

View File

@ -6,8 +6,8 @@
'use strict';
const util = require('../utils/util');
const assert = require('assert');
const util = require('../utils/util');
const StaticWriter = require('../utils/writer');
const BufferReader = require('../utils/reader');
const encoding = require('../utils/encoding');
@ -62,6 +62,47 @@ Outpoint.fromOptions = function fromOptions(options) {
return new Outpoint().fromOptions(options);
};
/**
* Clone the outpoint.
* @returns {Outpoint}
*/
Outpoint.prototype.clone = function clone() {
const outpoint = new Outpoint();
outpoint.hash = this.value;
outpoint.index = this.index;
return outpoint;
};
/**
* Test equality against another outpoint.
* @param {Outpoint} prevout
* @returns {Boolean}
*/
Outpoint.prototype.equals = function equals(prevout) {
assert(Outpoint.isOutpoint(prevout));
return this.hash === prevout.hash
&& this.index === prevout.index;
};
/**
* Compare against another outpoint (BIP69).
* @param {Outpoint} prevout
* @returns {Number}
*/
Outpoint.prototype.compare = function compare(prevout) {
assert(Outpoint.isOutpoint(prevout));
const cmp = util.strcmp(this.txid(), prevout.txid());
if (cmp !== 0)
return cmp;
return this.index - prevout.index;
};
/**
* Test whether the outpoint is null (hash of zeroes
* with max-u32 index). Used to detect coinbases.
@ -293,10 +334,7 @@ Outpoint.prototype.inspect = function inspect() {
*/
Outpoint.isOutpoint = function isOutpoint(obj) {
return obj
&& typeof obj.hash === 'string'
&& typeof obj.index === 'number'
&& typeof obj.toKey === 'function';
return obj instanceof Outpoint;
};
/*

View File

@ -118,6 +118,35 @@ Output.prototype.clone = function clone() {
return output;
};
/**
* Test equality against another output.
* @param {Output} output
* @returns {Boolean}
*/
Output.prototype.equals = function equals(output) {
assert(Output.isOutput(output));
return this.value === output.value
&& this.script.equals(output.script);
};
/**
* Compare against another output (BIP69).
* @param {Output} output
* @returns {Number}
*/
Output.prototype.compare = function compare(output) {
assert(Output.isOutput(output));
const cmp = this.value - output.value;
if (cmp !== 0)
return cmp;
return this.script.compare(output.script);
};
/**
* Get the script type as a string.
* @returns {ScriptType} type
@ -339,10 +368,7 @@ Output.fromRaw = function fromRaw(data, enc) {
*/
Output.isOutput = function isOutput(obj) {
return obj
&& typeof obj.value === 'number'
&& typeof obj.script === 'object'
&& typeof obj.getAddress === 'function';
return obj instanceof Output;
};
/*

View File

@ -24,7 +24,7 @@ const InvItem = require('./invitem');
const Bloom = require('../utils/bloom');
const consensus = require('../protocol/consensus');
const policy = require('../protocol/policy');
const {ScriptError} = require('../script/common');
const ScriptError = require('../script/scripterror');
const hashType = Script.hashType;
/**
@ -60,6 +60,7 @@ function TX(options) {
this._raw = null;
this._size = -1;
this._witness = -1;
this._sigops = -1;
this._hashPrevouts = null;
this._hashSequence = null;
@ -156,6 +157,7 @@ TX.prototype.refresh = function refresh() {
this._raw = null;
this._size = -1;
this._witness = -1;
this._sigops = -1;
this._hashPrevouts = null;
this._hashSequence = null;
@ -1295,6 +1297,9 @@ TX.prototype.verifySequence = function verifySequence(index, locktime) {
*/
TX.prototype.getLegacySigops = function getLegacySigops() {
if (this._sigops !== -1)
return this._sigops;
let total = 0;
for (const input of this.inputs)
@ -1303,6 +1308,9 @@ TX.prototype.getLegacySigops = function getLegacySigops() {
for (const output of this.outputs)
total += output.script.getSigops(false);
if (!this.mutable)
this._sigops = total;
return total;
};
@ -1313,11 +1321,11 @@ TX.prototype.getLegacySigops = function getLegacySigops() {
*/
TX.prototype.getScripthashSigops = function getScripthashSigops(view) {
let total = 0;
if (this.isCoinbase())
return 0;
let total = 0;
for (const input of this.inputs) {
const coin = view.getOutputFor(input);
@ -1334,27 +1342,16 @@ TX.prototype.getScripthashSigops = function getScripthashSigops(view) {
};
/**
* Calculate sigops cost, taking into account witness programs.
* Calculate accurate sigop count, taking into account redeem scripts.
* @param {CoinView} view
* @param {VerifyFlags?} flags
* @returns {Number} sigop weight
* @returns {Number} sigop count
*/
TX.prototype.getSigopsCost = function getSigopsCost(view, flags) {
const scale = consensus.WITNESS_SCALE_FACTOR;
let cost = this.getLegacySigops() * scale;
if (flags == null)
flags = Script.flags.STANDARD_VERIFY_FLAGS;
TX.prototype.getWitnessSigops = function getWitnessSigops(view) {
if (this.isCoinbase())
return cost;
return 0;
if (flags & Script.flags.VERIFY_P2SH)
cost += this.getScripthashSigops(view) * scale;
if (!(flags & Script.flags.VERIFY_WITNESS))
return cost;
let total = 0;
for (const input of this.inputs) {
const coin = view.getOutputFor(input);
@ -1362,9 +1359,33 @@ TX.prototype.getSigopsCost = function getSigopsCost(view, flags) {
if (!coin)
continue;
cost += coin.script.getWitnessSigops(input.script, input.witness);
total += coin.script.getWitnessSigops(input.script, input.witness);
}
return total;
};
/**
* Calculate sigops cost, taking into account witness programs.
* @param {CoinView} view
* @param {VerifyFlags?} flags
* @returns {Number} sigop weight
*/
TX.prototype.getSigopsCost = function getSigopsCost(view, flags) {
if (flags == null)
flags = Script.flags.STANDARD_VERIFY_FLAGS;
const scale = consensus.WITNESS_SCALE_FACTOR;
let cost = this.getLegacySigops() * scale;
if (flags & Script.flags.VERIFY_P2SH)
cost += this.getScripthashSigops(view) * scale;
if (flags & Script.flags.VERIFY_WITNESS)
cost += this.getWitnessSigops(view);
return cost;
};
@ -1627,7 +1648,7 @@ TX.prototype.hasStandardWitness = function hasStandardWitness(view) {
}
if (prev.isPubkeyhash()) {
if (input.witness.length - 1 !== 2)
if (input.witness.items.length - 1 !== 2)
return false;
if (witness.items[0].length > 73)
@ -2513,10 +2534,7 @@ TX.isWitness = function isWitness(br) {
*/
TX.isTX = function isTX(obj) {
return obj
&& Array.isArray(obj.inputs)
&& typeof obj.locktime === 'number'
&& typeof obj.witnessHash === 'function';
return obj instanceof TX;
};
/*

View File

@ -14,6 +14,7 @@
const assert = require('assert');
const util = require('../utils/util');
const secp256k1 = require('../crypto/secp256k1');
const ScriptNum = require('./scriptnum');
/**
* Script opcodes.
@ -163,6 +164,32 @@ exports.opcodes = {
exports.opcodesByVal = util.reverse(exports.opcodes);
/**
* Small ints (1 indexed, 1==0).
* @const {Buffer[]}
*/
exports.small = [
Buffer.from([0x81]),
Buffer.from([]),
Buffer.from([0x01]),
Buffer.from([0x02]),
Buffer.from([0x03]),
Buffer.from([0x04]),
Buffer.from([0x05]),
Buffer.from([0x06]),
Buffer.from([0x07]),
Buffer.from([0x08]),
Buffer.from([0x09]),
Buffer.from([0x0a]),
Buffer.from([0x0b]),
Buffer.from([0x0c]),
Buffer.from([0x0d]),
Buffer.from([0x0e]),
Buffer.from([0x0f]),
Buffer.from([0x10])
];
/**
* Script and locktime flags. See {@link VerifyFlags}.
* @enum {Number}
@ -293,27 +320,6 @@ exports.types = {
exports.typesByVal = util.reverse(exports.types);
/**
* False stack return value.
* @const {Buffer}
*/
exports.STACK_FALSE = Buffer.from([]);
/**
* True stack return value.
* @const {Buffer}
*/
exports.STACK_TRUE = Buffer.from([0x01]);
/**
* -1 stack return value.
* @const {Buffer}
*/
exports.STACK_NEGATE = Buffer.from([0x81]);
/**
* Test a signature to see whether it contains a valid sighash type.
* @param {Buffer} sig
@ -347,56 +353,6 @@ exports.isLowDER = function isLowDER(sig) {
return secp256k1.isLowS(sig.slice(0, -1));
};
/**
* Get a small integer from an opcode (OP_0-OP_16).
* @param {Number} index
* @returns {Number}
*/
exports.getSmall = function getSmall(op) {
assert(typeof op === 'number');
if (op === exports.opcodes.OP_0)
return 0;
if (op >= exports.opcodes.OP_1 && op <= exports.opcodes.OP_16)
return op - 0x50;
return -1;
};
/**
* Test whether the data element is a ripemd160 hash.
* @param {Buffer?} hash
* @returns {Boolean}
*/
exports.isHash = function isHash(hash) {
return Buffer.isBuffer(hash) && hash.length === 20;
};
/**
* Test whether the data element is a public key. Note that
* this does not verify the format of the key, only the length.
* @param {Buffer?} key
* @returns {Boolean}
*/
exports.isKey = function isKey(key) {
return Buffer.isBuffer(key) && key.length >= 33 && key.length <= 65;
};
/**
* Test whether the data element is a signature. Note that
* this does not verify the format of the signature, only the length.
* @param {Buffer?} sig
* @returns {Boolean}
*/
exports.isSignature = function isSignature(sig) {
return Buffer.isBuffer(sig) && sig.length >= 9 && sig.length <= 73;
};
/**
* Test whether the data element is a valid key.
* @param {Buffer} key
@ -532,224 +488,31 @@ exports.isSignatureEncoding = function isSignatureEncoding(sig) {
};
/**
* Cast a big number or Buffer to a bool.
* @see CastToBool
* @param {Buffer} value
* @returns {Boolean}
* Format stack item into bitcoind asm format.
* @param {Buffer} item
* @param {Boolean?} decode - Attempt to decode hash types.
* @returns {String} Human-readable string.
*/
exports.toBool = function toBool(value) {
assert(Buffer.isBuffer(value));
for (let i = 0; i < value.length; i++) {
if (value[i] !== 0) {
// Cannot be negative zero
if (i === value.length - 1 && value[i] === 0x80)
return false;
return true;
}
exports.toASM = function toASM(item, decode) {
if (item.length <= 4) {
const num = ScriptNum.decode(item, false, 4);
return num.toString(10);
}
return false;
};
if (decode && exports.isSignatureEncoding(item)) {
const type = item[item.length - 1];
/**
* Format script code into a human readable-string.
* @param {Array} code
* @returns {String} Human-readable string.
*/
let symbol = exports.hashTypeByVal[type & 0x1f] || '';
exports.formatStack = function formatStack(items) {
const out = [];
for (const item of items)
out.push(item.toString('hex'));
return out.join(' ');
};
/**
* Format script code into a human readable-string.
* @param {Array} code
* @returns {String} Human-readable string.
*/
exports.formatCode = function formatCode(code) {
const out = [];
for (const op of code) {
// Bad push
if (op.value === -1) {
out.push('OP_INVALIDOPCODE');
break;
}
if (op.data) {
const symbol = exports.opcodesByVal[op.value];
// Direct push
if (!symbol) {
let size = op.value.toString(16);
if (size.length < 2)
size = '0' + size;
out.push(`0x${size} 0x${op.data.toString('hex')}`);
continue;
}
// Pushdatas
let size = op.data.length.toString(16);
while (size.length % 2 !== 0)
size = '0' + size;
out.push(`${symbol} 0x${size} 0x${op.data.toString('hex')}`);
continue;
}
// Opcodes
let symbol = exports.opcodesByVal[op.value];
if (symbol) {
out.push(symbol);
continue;
if (type & exports.hashType.ANYONECANPAY)
symbol += '|ANYONECANPAY';
symbol = `[${symbol}]`;
}
// Unknown opcodes
symbol = op.value.toString(16);
if (symbol.length < 2)
symbol = '0' + symbol;
out.push(`0x${symbol}`);
return item.slice(0, -1).toString('hex') + symbol;
}
return out.join(' ');
return item.toString('hex');
};
/**
* Format script code into bitcoind asm format.
* @param {Array} code
* @param {Boolean?} decode - Attempt to decode hash types.
* @returns {String} Human-readable string.
*/
exports.formatItem = function formatItem(data, decode) {
if (data.length <= 4) {
data = exports.num(data, exports.flags.VERIFY_NONE);
return data.toString(10);
}
if (decode) {
let symbol = '';
if (exports.isSignatureEncoding(data)) {
const type = data[data.length - 1];
symbol = exports.hashTypeByVal[type & 0x1f] || '';
if (symbol) {
if (type & exports.hashType.ANYONECANPAY)
symbol += '|ANYONECANPAY';
symbol = `[${symbol}]`;
}
data = data.slice(0, -1);
}
return data.toString('hex') + symbol;
}
return data.toString('hex');
};
/**
* Format script code into bitcoind asm format.
* @param {Array} code
* @param {Boolean?} decode - Attempt to decode hash types.
* @returns {String} Human-readable string.
*/
exports.formatASM = function formatASM(code, decode) {
if (code.length > 0) {
if (code[0].value === exports.opcodes.OP_RETURN)
decode = false;
}
const out = [];
for (const op of code) {
if (op.value === -1) {
out.push('[error]');
break;
}
if (op.data) {
const data = exports.formatItem(op.data, decode);
out.push(data);
continue;
}
const symbol = exports.opcodesByVal[op.value] || 'OP_UNKNOWN';
out.push(symbol);
}
return out.join(' ');
};
/**
* Format script code into bitcoind asm format.
* @param {Array} code
* @param {Boolean?} decode - Attempt to decode hash types.
* @returns {String} Human-readable string.
*/
exports.formatStackASM = function formatStackASM(items, decode) {
const out = [];
for (const item of items) {
const data = exports.formatItem(item, decode);
out.push(data);
}
return out.join(' ');
};
/**
* An error thrown from the scripting system,
* potentially pertaining to Script execution.
* @alias module:script.ScriptError
* @constructor
* @extends Error
* @param {String} code - Error code.
* @param {Opcode} op - Opcode.
* @param {Number?} ip - Instruction pointer.
* @property {String} message - Error message.
* @property {String} code - Original code passed in.
* @property {Number} op - Opcode.
* @property {Number} ip - Instruction pointer.
*/
exports.ScriptError = function ScriptError(code, op, ip) {
if (!(this instanceof ScriptError))
return new ScriptError(code, op, ip);
Error.call(this);
this.type = 'ScriptError';
this.code = code;
this.message = code;
this.op = -1;
this.ip = -1;
if (typeof op === 'string') {
this.message = op;
} else if (op) {
this.message = `${code} (op=${op.toSymbol()}, ip=${ip})`;
this.op = op.value;
this.ip = ip;
}
if (Error.captureStackTrace)
Error.captureStackTrace(this, ScriptError);
};
Object.setPrototypeOf(exports.ScriptError.prototype, Error.prototype);

View File

@ -14,6 +14,7 @@ exports.common = require('./common');
exports.Opcode = require('./opcode');
exports.Program = require('./program');
exports.Script = require('./script');
exports.ScriptError = require('./scripterror');
exports.ScriptNum = require('./scriptnum');
exports.sigcache = require('./sigcache');
exports.Stack = require('./stack');

View File

@ -101,6 +101,194 @@ Opcode.prototype.isBranch = function isBranch() {
return this.value >= opcodes.OP_IF && this.value <= opcodes.OP_ENDIF;
};
/**
* Test opcode equality.
* @param {Opcode} op
* @returns {Boolean}
*/
Opcode.prototype.equals = function equals(op) {
assert(Opcode.isOpcode(op));
if (this.value !== op.value)
return false;
if (!this.data) {
assert(!op.data);
return true;
}
assert(op.data);
return this.data.equals(op.data);
};
/**
* Convert Opcode to opcode value.
* @returns {Number}
*/
Opcode.prototype.toOp = function toOp() {
return this.value;
};
/**
* Covert opcode to data push.
* @returns {Buffer|null}
*/
Opcode.prototype.toData = function toData() {
return this.data;
};
/**
* Covert opcode to data length.
* @returns {Number}
*/
Opcode.prototype.toLength = function toLength() {
return this.data ? this.data.length : -1;
};
/**
* Covert and _cast_ opcode to data push.
* @returns {Buffer|null}
*/
Opcode.prototype.toPush = function toPush() {
if (this.data)
return this.data;
if (this.value === opcodes.OP_1NEGATE)
return common.small[-1 + 1];
if (this.value === opcodes.OP_0)
return common.small[0 + 1];
if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16)
return common.small[this.value - 0x50 + 1];
return null;
};
/**
* Get string for opcode.
* @param {String?} enc
* @returns {Buffer|null}
*/
Opcode.prototype.toString = function toString(enc) {
const data = this.toPush();
if (!data)
return null;
return data.toString(enc || 'utf8');
};
/**
* Convert opcode to small integer.
* @returns {Number}
*/
Opcode.prototype.toSmall = function toSmall() {
if (this.value === opcodes.OP_0)
return 0;
if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16)
return this.value - 0x50;
return -1;
};
/**
* Convert opcode to script number.
* @returns {ScriptNum|null}
*/
Opcode.prototype.toNum = function toNum(minimal, limit) {
const smi = this.toSmall();
if (smi !== -1)
return ScriptNum.fromInt(smi);
if (!this.data)
return null;
return ScriptNum.decode(this.data, minimal, limit);
};
/**
* Convert opcode to integer.
* @returns {Number}
*/
Opcode.prototype.toInt = function toInt() {
const num = this.toNum();
if (!num)
return -1;
return num.getInt();
};
/**
* Convert opcode to boolean.
* @returns {Boolean}
*/
Opcode.prototype.toBool = function toBool() {
const smi = this.toSmall();
if (smi === -1)
return false;
return smi === 1;
};
/**
* Convert opcode to its symbolic representation.
* @returns {String}
*/
Opcode.prototype.toSymbol = function toSymbol() {
let op = this.value;
if (op === -1)
op = 0xff;
let symbol = common.opcodesByVal[op];
if (symbol == null)
symbol = `0x${util.hex8(op)}`;
return symbol;
};
/**
* Calculate opcode size.
* @returns {Number}
*/
Opcode.prototype.getSize = function getSize() {
if (!this.data)
return 1;
if (this.value <= 0x4b)
return 1 + this.data.length;
switch (this.value) {
case opcodes.OP_PUSHDATA1:
return 2 + this.data.length;
case opcodes.OP_PUSHDATA2:
return 3 + this.data.length;
case opcodes.OP_PUSHDATA4:
return 5 + this.data.length;
default:
throw new Error('Unknown pushdata opcode.');
}
};
/**
* Encode the opcode to a buffer writer.
* @param {BufferWriter} bw
@ -156,27 +344,225 @@ Opcode.prototype.toRaw = function toRaw() {
};
/**
* Calculate opcode size.
* @returns {Number}
* Convert the opcode to a bitcoind test string.
* @returns {String} Human-readable script code.
*/
Opcode.prototype.getSize = function getSize() {
if (!this.data)
return 1;
Opcode.prototype.toFormat = function toFormat() {
// Bad push
if (this.value === -1)
return 'OP_INVALIDOPCODE';
if (this.value <= 0x4b)
return 1 + this.data.length;
if (this.data) {
const symbol = common.opcodesByVal[this.value];
const data = this.data.toString('hex');
switch (this.value) {
case opcodes.OP_PUSHDATA1:
return 2 + this.data.length;
case opcodes.OP_PUSHDATA2:
return 3 + this.data.length;
case opcodes.OP_PUSHDATA4:
return 5 + this.data.length;
default:
throw new Error('Unknown pushdata opcode.');
// Direct push
if (!symbol) {
const size = util.hex8(this.value);
return `0x${size} 0x${data}`;
}
// Pushdatas
let size = this.data.length.toString(16);
while (size.length % 2 !== 0)
size = '0' + size;
return `${symbol} 0x${size} 0x${data}`;
}
// Opcodes
const symbol = common.opcodesByVal[this.value];
if (symbol)
return symbol;
// Unknown opcodes
const value = util.hex8(this.value);
return `0x${value}`;
};
/**
* Format the opcode as bitcoind asm.
* @param {Boolean?} decode - Attempt to decode hash types.
* @returns {String} Human-readable script.
*/
Opcode.prototype.toASM = function toASM(decode) {
if (this.value === -1)
return '[error]';
if (this.data)
return common.toASM(this.data, decode);
return common.opcodesByVal[this.value] || 'OP_UNKNOWN';
};
/**
* Instantiate an opcode from a number opcode.
* @param {Number} op
* @returns {Opcode}
*/
Opcode.fromOp = function fromOp(op) {
assert(typeof op === 'number');
const cached = opCache[op];
if (cached)
return cached;
return new Opcode(op, null);
};
/**
* Instantiate a pushdata opcode from
* a buffer (will encode minimaldata).
* @param {Buffer} data
* @returns {Opcode}
*/
Opcode.fromData = function fromData(data) {
assert(Buffer.isBuffer(data));
if (data.length === 0)
return Opcode.fromOp(opcodes.OP_0);
if (data.length === 1) {
if (data[0] >= 1 && data[0] <= 16)
return Opcode.fromOp(data[0] + 0x50);
if (data[0] === 0x81)
return Opcode.fromOp(opcodes.OP_1NEGATE);
}
return Opcode.fromPush(data);
};
/**
* Instantiate a pushdata opcode from a
* buffer (this differs from fromData in
* that it will _always_ be a pushdata op).
* @param {Buffer} data
* @returns {Opcode}
*/
Opcode.fromPush = function fromPush(data) {
assert(Buffer.isBuffer(data));
if (data.length <= 0x4b)
return new Opcode(data.length, data);
if (data.length <= 0xff)
return new Opcode(opcodes.OP_PUSHDATA1, data);
if (data.length <= 0xffff)
return new Opcode(opcodes.OP_PUSHDATA2, data);
if (data.length <= 0xffffffff)
return new Opcode(opcodes.OP_PUSHDATA4, data);
throw new Error('Pushdata size too large.');
};
/**
* Instantiate a pushdata opcode from a string.
* @param {String} str
* @param {String} [enc=utf8]
* @returns {Opcode}
*/
Opcode.fromString = function fromString(str, enc) {
assert(typeof str === 'string');
const data = Buffer.from(str, enc || 'utf8');
return Opcode.fromData(data);
};
/**
* Instantiate an opcode from a small number.
* @param {Number} num
* @returns {Opcode}
*/
Opcode.fromSmall = function fromSmall(num) {
assert(util.isU8(num) && num >= 0 && num <= 16);
return Opcode.fromOp(num === 0 ? 0 : num + 0x50);
};
/**
* Instantiate an opcode from a ScriptNum.
* @param {ScriptNumber} num
* @returns {Opcode}
*/
Opcode.fromNum = function fromNum(num) {
assert(ScriptNum.isScriptNum(num));
return Opcode.fromData(num.encode());
};
/**
* Instantiate an opcode from a Number.
* @param {Number} num
* @returns {Opcode}
*/
Opcode.fromInt = function fromInt(num) {
assert(util.isInt(num));
if (num === -1)
return Opcode.fromOp(opcodes.OP_1NEGATE);
if (num === 0)
return Opcode.fromOp(opcodes.OP_0);
if (num >= 1 && num <= 16)
return Opcode.fromOp(num + 0x50);
return Opcode.fromNum(ScriptNum.fromNumber(num));
};
/**
* Instantiate an opcode from a Number.
* @param {Boolean} value
* @returns {Opcode}
*/
Opcode.fromBool = function fromBool(value) {
assert(typeof value === 'boolean');
return Opcode.fromSmall(value ? 1 : 0);
};
/**
* Instantiate a pushdata opcode from symbolic name.
* @example
* Opcode.fromSymbol('checksequenceverify')
* @param {String} name
* @returns {Opcode}
*/
Opcode.fromSymbol = function fromSymbol(name) {
assert(typeof name === 'string');
assert(name.length > 0);
if (!util.isUpperCase(name))
name = name.toUpperCase();
if (!util.startsWith(name, 'OP_'))
name = `OP_${name}`;
let op = common.opcodes[name];
if (op == null) {
assert(util.startsWith(name, 'OP_0X'), 'Unknown opcode.');
assert(name.length === 7, 'Unknown opcode.');
op = parseInt(name.substring(5), 16);
assert(util.isU8(op), 'Unknown opcode.');
}
return Opcode.fromOp(op);
};
/**
@ -201,61 +587,67 @@ Opcode.fromReader = function fromReader(br) {
br.seek(br.left());
return op;
}
op.value = value;
op.data = br.readBytes(value);
return op;
}
let size;
switch (value) {
case opcodes.OP_PUSHDATA1:
case opcodes.OP_PUSHDATA1: {
if (br.left() < 1) {
op.value = -1;
break;
}
size = br.readU8();
const size = br.readU8();
if (br.left() < size) {
op.value = -1;
br.seek(br.left());
break;
}
op.value = value;
op.data = br.readBytes(size);
break;
case opcodes.OP_PUSHDATA2:
}
case opcodes.OP_PUSHDATA2: {
if (br.left() < 2) {
op.value = -1;
br.seek(br.left());
break;
}
size = br.readU16();
const size = br.readU16();
if (br.left() < size) {
op.value = -1;
br.seek(br.left());
break;
}
op.value = value;
op.data = br.readBytes(size);
break;
case opcodes.OP_PUSHDATA4:
}
case opcodes.OP_PUSHDATA4: {
if (br.left() < 4) {
op.value = -1;
br.seek(br.left());
break;
}
size = br.readU32();
const size = br.readU32();
if (br.left() < size) {
op.value = -1;
br.seek(br.left());
break;
}
op.value = value;
op.data = br.readBytes(size);
break;
default:
op.value = value;
break;
}
}
return op;
@ -271,183 +663,6 @@ Opcode.fromRaw = function fromRaw(data) {
return Opcode.fromReader(new BufferReader(data));
};
/**
* Instantiate an opcode from a number opcode.
* @param {Number} op
* @returns {Opcode}
*/
Opcode.fromOp = function fromOp(op) {
const cached = opCache[op];
if (cached)
return cached;
return new Opcode(op, null);
};
/**
* Instantiate a pushdata opcode from
* a buffer (will encode minimaldata).
* @param {Buffer} data
* @returns {Opcode}
*/
Opcode.fromData = function fromData(data) {
if (data.length === 0)
return Opcode.fromOp(opcodes.OP_0);
if (data.length === 1) {
if (data[0] >= 1 && data[0] <= 16)
return Opcode.fromOp(data[0] + 0x50);
if (data[0] === 0x81)
return Opcode.fromOp(opcodes.OP_1NEGATE);
}
return Opcode.fromPush(data);
};
/**
* Instantiate a pushdata opcode from a
* buffer (this differs from fromData in
* that it will _always_ be a pushdata op).
* @param {Buffer} data
* @returns {Opcode}
*/
Opcode.fromPush = function fromPush(data) {
if (data.length <= 0x4b)
return new Opcode(data.length, data);
if (data.length <= 0xff)
return new Opcode(opcodes.OP_PUSHDATA1, data);
if (data.length <= 0xffff)
return new Opcode(opcodes.OP_PUSHDATA2, data);
if (data.length <= 0xffffffff)
return new Opcode(opcodes.OP_PUSHDATA4, data);
throw new Error('Pushdata size too large.');
};
/**
* Instantiate an opcode from a Number.
* @param {Number|ScriptNum|BN} num
* @returns {Opcode}
*/
Opcode.fromNumber = function fromNumber(num) {
return Opcode.fromData(ScriptNum.encode(num));
};
/**
* Instantiate an opcode from a Number.
* @param {Boolean} value
* @returns {Opcode}
*/
Opcode.fromBool = function fromBool(value) {
assert(typeof value === 'boolean');
return Opcode.fromSmall(value ? 1 : 0);
};
/**
* Instantiate an opcode from a small number.
* @param {Number} num
* @returns {Opcode}
*/
Opcode.fromSmall = function fromSmall(num) {
assert(util.isU8(num) && num >= 0 && num <= 16);
return Opcode.fromOp(num === 0 ? 0 : num + 0x50);
};
/**
* Instantiate a pushdata opcode from a string.
* @param {String} data
* @returns {Opcode}
*/
Opcode.fromString = function fromString(data, enc) {
if (typeof data === 'string')
data = Buffer.from(data, enc);
return Opcode.fromData(data);
};
/**
* Instantiate a pushdata opcode from anything.
* @param {String|Buffer|Number|ScriptNum|Opcode} data
* @returns {Opcode}
*/
Opcode.from = function from(data) {
if (data instanceof Opcode)
return data;
if (typeof data === 'number')
return Opcode.fromOp(data);
if (Buffer.isBuffer(data))
return Opcode.fromData(data);
if (typeof data === 'string')
return Opcode.fromString(data, 'utf8');
if (typeof data === 'boolean')
return Opcode.fromBool(data);
if (ScriptNum.isEncodable(data))
return Opcode.fromNumber(data);
throw new Error('Bad data for opcode.');
};
/**
* Instantiate a pushdata opcode from symbolic name.
* @example
* Opcode.fromSymbol('checksequenceverify')
* @param {String} name
* @returns {Opcode}
*/
Opcode.fromSymbol = function fromSymbol(name) {
assert(typeof name === 'string');
assert(name.length > 0);
if (!util.isUpperCase(name))
name = name.toUpperCase();
if (!util.startsWith(name, 'OP_'))
name = `OP_${name}`;
const op = common.opcodes[name];
assert(op != null, 'Unknown opcode.');
return Opcode.fromOp(op);
};
/**
* Convert opcode to its symbolic representation.
* @returns {String}
*/
Opcode.prototype.toSymbol = function toSymbol() {
let op = this.value;
if (op === -1)
op = 0xff;
let symbol = common.opcodesByVal[op];
if (symbol == null)
symbol = util.hex8(op);
return symbol;
};
/**
* Test whether an object an Opcode.
* @param {Object} obj
@ -455,9 +670,7 @@ Opcode.prototype.toSymbol = function toSymbol() {
*/
Opcode.isOpcode = function isOpcode(obj) {
return obj
&& typeof obj.value === 'number'
&& (Buffer.isBuffer(obj.data) || obj.data === null);
return obj instanceof Opcode;
};
/*

File diff suppressed because it is too large Load Diff

50
lib/script/scripterror.js Normal file
View File

@ -0,0 +1,50 @@
/*!
* scripterror.js - script error for bcoin
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
/**
* An error thrown from the scripting system,
* potentially pertaining to Script execution.
* @alias module:script.ScriptError
* @constructor
* @extends Error
* @param {String} code - Error code.
* @param {Opcode} op - Opcode.
* @param {Number?} ip - Instruction pointer.
* @property {String} message - Error message.
* @property {String} code - Original code passed in.
* @property {Number} op - Opcode.
* @property {Number} ip - Instruction pointer.
*/
function ScriptError(code, op, ip) {
if (!(this instanceof ScriptError))
return new ScriptError(code, op, ip);
Error.call(this);
this.type = 'ScriptError';
this.code = code;
this.message = code;
this.op = -1;
this.ip = -1;
if (typeof op === 'string') {
this.message = op;
} else if (op) {
this.message = `${code} (op=${op.toSymbol()}, ip=${ip})`;
this.op = op.value;
this.ip = ip;
}
if (Error.captureStackTrace)
Error.captureStackTrace(this, ScriptError);
};
Object.setPrototypeOf(ScriptError.prototype, Error.prototype);
module.exports = ScriptError;

View File

@ -8,7 +8,7 @@
const assert = require('assert');
const {I64} = require('../utils/int64');
const {ScriptError} = require('./common');
const ScriptError = require('./scripterror');
/*
* Constants

View File

@ -10,10 +10,6 @@
const assert = require('assert');
const common = require('./common');
const ScriptNum = require('./scriptnum');
const ScriptError = common.ScriptError;
const STACK_FALSE = common.STACK_FALSE;
const STACK_TRUE = common.STACK_TRUE;
const STACK_NEGATE = common.STACK_NEGATE;
/**
* Represents the stack of a Script during execution.
@ -45,6 +41,33 @@ Object.defineProperty(Stack.prototype, 'length', {
}
});
/**
* Instantiate a key and value iterator.
* @returns {StackIterator}
*/
Stack.prototype[Symbol.iterator] = function iterator() {
return this.items[Symbol.iterator]();
};
/**
* Instantiate a value-only iterator.
* @returns {StackIterator}
*/
Stack.prototype.values = function values() {
return this.items.values();
};
/**
* Instantiate a key and value iterator.
* @returns {StackIterator}
*/
Stack.prototype.entries = function entries() {
return this.items.entries();
};
/**
* Inspect the stack.
* @returns {String} Human-readable stack.
@ -60,7 +83,12 @@ Stack.prototype.inspect = function inspect() {
*/
Stack.prototype.toString = function toString() {
return common.formatStack(this.items);
const out = [];
for (const item of this.items)
out.push(item.toString('hex'));
return out.join(' ');
};
/**
@ -70,7 +98,12 @@ Stack.prototype.toString = function toString() {
*/
Stack.prototype.toASM = function toASM(decode) {
return common.formatStackASM(this.items, decode);
const out = [];
for (const item of this.items)
out.push(common.toASM(item, decode));
return out.join(' ');
};
/**
@ -82,6 +115,94 @@ Stack.prototype.clone = function clone() {
return new Stack(this.items.slice());
};
/**
* Clear the stack.
* @returns {Stack}
*/
Stack.prototype.clear = function clear() {
this.items.length = 0;
return this;
};
/**
* Get a stack item by index.
* @param {Number} index
* @returns {Buffer|null}
*/
Stack.prototype.get = function get(index) {
if (index < 0)
index += this.items.length;
if (index < 0 || index >= this.items.length)
return null;
return this.items[index];
};
/**
* Pop a stack item.
* @see Array#pop
* @returns {Buffer|null}
*/
Stack.prototype.pop = function pop() {
const item = this.items.pop();
return item || null;
};
/**
* Shift a stack item.
* @see Array#shift
* @returns {Buffer|null}
*/
Stack.prototype.shift = function shift() {
const item = this.items.shift();
return item || null;
};
/**
* Remove an item.
* @param {Number} index
* @returns {Buffer}
*/
Stack.prototype.remove = function remove(index) {
if (index < 0)
index += this.items.length;
if (index < 0 || index >= this.items.length)
return null;
const items = this.items.splice(index, 1);
if (items.length === 0)
return null;
return items[0];
};
/**
* Set stack item at index.
* @param {Number} index
* @param {Buffer} value
* @returns {Buffer}
*/
Stack.prototype.set = function set(index, item) {
if (index < 0)
index += this.items.length;
assert(Buffer.isBuffer(item));
assert(index >= 0 && index <= this.items.length);
this.items[index] = item;
return this;
};
/**
* Push item onto stack.
* @see Array#push
@ -91,60 +212,8 @@ Stack.prototype.clone = function clone() {
Stack.prototype.push = function push(item) {
assert(Buffer.isBuffer(item));
return this.items.push(item);
};
/**
* Push boolean onto stack.
* @see Array#push
* @param {Boolean} value
* @returns {Number} Stack size.
*/
Stack.prototype.pushBool = function pushBool(value) {
assert(typeof value === 'boolean');
return this.items.push(value ? STACK_TRUE : STACK_FALSE);
};
/**
* Push script number onto stack.
* @see Array#push
* @param {ScriptNum} num
* @returns {Number} Stack size.
*/
Stack.prototype.pushNum = function pushNum(num) {
assert(ScriptNum.isScriptNum(num));
return this.items.push(num.encode());
};
/**
* Push integer onto stack.
* @see Array#push
* @param {Number} value
* @returns {Number} Stack size.
*/
Stack.prototype.pushInt = function pushInt(value) {
assert(typeof value === 'number');
if (value >= -1 && value <= 16) {
switch (value) {
case -1:
return this.items.push(STACK_NEGATE);
case 0:
return this.items.push(STACK_FALSE);
case 1:
return this.items.push(STACK_TRUE);
}
const item = Buffer.allocUnsafe(1);
item[0] = value;
return this.items.push(item);
}
const num = ScriptNum.fromNumber(value);
return this.items.push(num.encode());
this.items.push(item);
return this;
};
/**
@ -156,41 +225,27 @@ Stack.prototype.pushInt = function pushInt(value) {
Stack.prototype.unshift = function unshift(item) {
assert(Buffer.isBuffer(item));
return this.items.unshift(item);
};
/**
* Slice out part of the stack items.
* @param {Number} start
* @param {Number} end
* @see Array#slice
* @returns {Stack}
*/
Stack.prototype.slice = function slice(start, end) {
this.items = this.items.slice(start, end);
this.items.unshift(item);
return this;
};
/**
* Splice stack items.
* @see Array#splice
* Insert an item.
* @param {Number} index
* @param {Number} remove
* @param {Buffer?} insert
* @returns {Buffer[]}
* @param {Buffer} item
* @returns {Buffer}
*/
Stack.prototype.splice = function splice(i, remove, insert) {
if (i < 0)
i = this.items.length + i;
Stack.prototype.insert = function insert(index, item) {
if (index < 0)
index += this.items.length;
if (insert === undefined)
return this.items.splice(i, remove);
assert(Buffer.isBuffer(item));
assert(index >= 0 && index <= this.items.length);
assert(Buffer.isBuffer(insert));
this.items.splice(index, 0, item);
return this.items.splice(i, remove, insert);
return this;
};
/**
@ -210,167 +265,6 @@ Stack.prototype.erase = function erase(start, end) {
this.items.splice(start, end - start);
};
/**
* Insert an item.
* @param {Number} index
* @param {Buffer} item
* @returns {Buffer}
*/
Stack.prototype.insert = function insert(i, item) {
if (i < 0)
i = this.items.length + i;
assert(Buffer.isBuffer(item));
this.items.splice(i, 0, item);
};
/**
* Remove an item.
* @param {Number} index
* @returns {Buffer}
*/
Stack.prototype.remove = function remove(i) {
if (i < 0)
i = this.items.length + i;
if (i >= this.items.length)
return undefined;
return this.items.splice(i, 1)[0];
};
/**
* Pop a stack item.
* @see Array#pop
* @returns {Buffer|null}
*/
Stack.prototype.pop = function pop() {
return this.items.pop();
};
/**
* Shift a stack item.
* @see Array#shift
* @returns {Buffer|null}
*/
Stack.prototype.shift = function shift() {
return this.items.shift();
};
/**
* Get a stack item by index.
* @param {Number} index
* @returns {Buffer|null}
*/
Stack.prototype.get = function get(i) {
if (i < 0)
i = this.items.length + i;
return this.items[i];
};
/**
* Get a stack item by index
* and decode as a boolean.
* @param {Number} index
* @returns {Boolean}
* @throws on invalid stack operation
*/
Stack.prototype.bool = function bool(i) {
if (i < 0)
i = this.items.length + i;
if (i < 0 || i >= this.items.length)
throw new ScriptError('INVALID_STACK_OPERATION', -1, -1);
return common.toBool(this.items[i]);
};
/**
* Get a stack item by index
* and decode as a scriptnum.
* @param {Number} index
* @param {Boolean?} minimal
* @param {Number?} limit
* @returns {ScriptNum}
* @throws on invalid stack operation
*/
Stack.prototype.num = function num(i, minimal, limit) {
if (i < 0)
i = this.items.length + i;
if (i < 0 || i >= this.items.length)
throw new ScriptError('INVALID_STACK_OPERATION', -1, -1);
return ScriptNum.decode(this.items[i], minimal, limit);
};
/**
* Get a stack item by index
* and decode as an integer.
* @param {Number} index
* @param {Boolean?} minimal
* @returns {Number}
* @throws on invalid stack operation
*/
Stack.prototype.int = function int(i, minimal) {
if (i < 0)
i = this.items.length + i;
if (i < 0 || i >= this.items.length)
throw new ScriptError('INVALID_STACK_OPERATION', -1, -1);
return ScriptNum.decode(this.items[i], minimal).getInt();
};
/**
* Get a stack item relative to
* the top of the stack.
* @example
* stack.top(-1);
* @param {Number} index
* @returns {Buffer|null}
*/
Stack.prototype.top = function top(i) {
return this.items[this.items.length + i];
};
/**
* Clear the stack.
*/
Stack.prototype.clear = function clear() {
this.items.length = 0;
};
/**
* Set stack item at index.
* @param {Number} index
* @param {Buffer} value
* @returns {Buffer}
*/
Stack.prototype.set = function set(i, value) {
if (i < 0)
i = this.items.length + i;
assert(Buffer.isBuffer(value));
this.items[i] = value;
return value;
};
/**
* Swap stack values.
* @param {Number} i1 - Index 1.
@ -391,6 +285,211 @@ Stack.prototype.swap = function swap(i1, i2) {
this.items[i2] = v1;
};
/*
* Data
*/
Stack.prototype.getData = function getData(index) {
return this.get(index);
};
Stack.prototype.popData = function popData() {
return this.pop();
};
Stack.prototype.shiftData = function shiftData() {
return this.shift();
};
Stack.prototype.removeData = function removeData(index) {
return this.remove(index);
};
Stack.prototype.setData = function setData(index, data) {
return this.set(index, data);
};
Stack.prototype.pushData = function pushData(data) {
return this.push(data);
};
Stack.prototype.unshiftData = function unshiftData(data) {
return this.unshift(data);
};
Stack.prototype.insertData = function insertData(index, data) {
return this.insert(index, data);
};
/*
* Length
*/
Stack.prototype.getLength = function getLength(index) {
const item = this.get(index);
return item ? item.length : -1;
};
/*
* String
*/
Stack.prototype.getString = function getString(index, enc) {
const item = this.get(index);
return item ? Stack.toString(item, enc) : null;
};
Stack.prototype.popString = function popString(enc) {
const item = this.pop();
return item ? Stack.toString(item, enc) : null;
};
Stack.prototype.shiftString = function shiftString(enc) {
const item = this.shift();
return item ? Stack.toString(item, enc) : null;
};
Stack.prototype.removeString = function removeString(index, enc) {
const item = this.remove(index);
return item ? Stack.toString(item, enc) : null;
};
Stack.prototype.setString = function setString(index, str, enc) {
return this.set(index, Stack.fromString(str, enc));
};
Stack.prototype.pushString = function pushString(str, enc) {
return this.push(Stack.fromString(str, enc));
};
Stack.prototype.unshiftString = function unshiftString(str, enc) {
return this.unshift(Stack.fromString(str, enc));
};
Stack.prototype.insertString = function insertString(index, str, enc) {
return this.insert(index, Stack.fromString(str, enc));
};
/*
* Num
*/
Stack.prototype.getNum = function getNum(index, minimal, limit) {
const item = this.get(index);
return item ? Stack.toNum(item, minimal, limit) : null;
};
Stack.prototype.popNum = function popNum(minimal, limit) {
const item = this.pop();
return item ? Stack.toNum(item, minimal, limit) : null;
};
Stack.prototype.shiftNum = function shiftNum(minimal, limit) {
const item = this.shift();
return item ? Stack.toNum(item, minimal, limit) : null;
};
Stack.prototype.removeNum = function removeNum(index, minimal, limit) {
const item = this.remove(index);
return item ? Stack.toNum(item, minimal, limit) : null;
};
Stack.prototype.setNum = function setNum(index, num) {
return this.set(index, Stack.fromNum(num));
};
Stack.prototype.pushNum = function pushNum(num) {
return this.push(Stack.fromNum(num));
};
Stack.prototype.unshiftNum = function unshiftNum(num) {
return this.unshift(Stack.fromNum(num));
};
Stack.prototype.insertNum = function insertNum(index, num) {
return this.insert(index, Stack.fromNum(num));
};
/*
* Int
*/
Stack.prototype.getInt = function getInt(index, minimal, limit) {
const item = this.get(index);
return item ? Stack.toInt(item, minimal, limit) : -1;
};
Stack.prototype.popInt = function popInt(minimal, limit) {
const item = this.pop();
return item ? Stack.toInt(item, minimal, limit) : -1;
};
Stack.prototype.shiftInt = function shiftInt(minimal, limit) {
const item = this.shift();
return item ? Stack.toInt(item, minimal, limit) : -1;
};
Stack.prototype.removeInt = function removeInt(index, minimal, limit) {
const item = this.remove(index);
return item ? Stack.toInt(item, minimal, limit) : -1;
};
Stack.prototype.setInt = function setInt(index, num) {
return this.set(index, Stack.fromInt(num));
};
Stack.prototype.pushInt = function pushInt(num) {
return this.push(Stack.fromInt(num));
};
Stack.prototype.unshiftInt = function unshiftInt(num) {
return this.unshift(Stack.fromInt(num));
};
Stack.prototype.insertInt = function insertInt(index, num) {
return this.insert(index, Stack.fromInt(num));
};
/*
* Bool
*/
Stack.prototype.getBool = function getBool(index) {
const item = this.get(index);
return item ? Stack.toBool(item) : false;
};
Stack.prototype.popBool = function popBool() {
const item = this.pop();
return item ? Stack.toBool(item) : false;
};
Stack.prototype.shiftBool = function shiftBool() {
const item = this.shift();
return item ? Stack.toBool(item) : false;
};
Stack.prototype.removeBool = function removeBool(index) {
const item = this.remove(index);
return item ? Stack.toBool(item) : false;
};
Stack.prototype.setBool = function setBool(index, value) {
return this.set(index, Stack.fromBool(value));
};
Stack.prototype.pushBool = function pushBool(value) {
return this.push(Stack.fromBool(value));
};
Stack.prototype.unshiftBool = function unshiftBool(value) {
return this.unshift(Stack.fromBool(value));
};
Stack.prototype.insertBool = function insertBool(index, value) {
return this.insert(index, Stack.fromBool(value));
};
/**
* Test an object to see if it is a Stack.
* @param {Object} obj
@ -398,7 +497,73 @@ Stack.prototype.swap = function swap(i1, i2) {
*/
Stack.isStack = function isStack(obj) {
return obj && Array.isArray(obj.items) && typeof obj.swap === 'function';
return obj instanceof Stack;
};
/*
* Encoding
*/
Stack.toString = function toString(item, enc) {
assert(Buffer.isBuffer(item));
return item.toString(enc || 'utf8');
};
Stack.fromString = function fromString(str, enc) {
assert(typeof str === 'string');
return Buffer.from(str, enc || 'utf8');
};
Stack.toNum = function toNum(item, minimal, limit) {
assert(Buffer.isBuffer(item));
return ScriptNum.decode(item, minimal, limit);
};
Stack.fromNum = function fromNum(num) {
assert(ScriptNum.isScriptNum(num));
return num.encode();
};
Stack.toInt = function toInt(item, minimal, limit) {
assert(Buffer.isBuffer(item));
const num = Stack.toNum(item, minimal, limit);
if (!num)
return -1;
return num.getInt();
};
Stack.fromInt = function fromInt(int) {
assert(typeof int === 'number');
if (int >= -1 && int <= 16)
return common.small[int + 1];
const num = ScriptNum.fromNumber(int);
return Stack.fromNum(num);
};
Stack.toBool = function toBool(item) {
assert(Buffer.isBuffer(item));
for (let i = 0; i < item.length; i++) {
if (item[i] !== 0) {
// Cannot be negative zero
if (i === item.length - 1 && item[i] === 0x80)
return false;
return true;
}
}
return false;
};
Stack.fromBool = function fromBool(value) {
assert(typeof value === 'boolean');
return Stack.fromInt(value ? 1 : 0);
};
/*

View File

@ -8,21 +8,15 @@
'use strict';
const assert = require('assert');
const ScriptNum = require('./scriptnum');
const util = require('../utils/util');
const Script = require('./script');
const common = require('./common');
const encoding = require('../utils/encoding');
const Opcode = require('./opcode');
const BufferReader = require('../utils/reader');
const StaticWriter = require('../utils/staticwriter');
const Address = require('../primitives/address');
const Stack = require('./stack');
const opcodes = common.opcodes;
const scriptTypes = common.types;
const STACK_TRUE = common.STACK_TRUE;
const STACK_FALSE = common.STACK_FALSE;
const STACK_NEGATE = common.STACK_NEGATE;
/**
* Refers to the witness field of segregated witness transactions.
@ -39,25 +33,13 @@ function Witness(options) {
if (!(this instanceof Witness))
return new Witness(options);
this.items = [];
Stack.call(this, []);
if (options)
this.fromOptions(options);
}
/*
* Expose length setter and getter.
*/
Object.defineProperty(Witness.prototype, 'length', {
get() {
return this.items.length;
},
set(length) {
this.items.length = length;
return this.items.length;
}
});
Object.setPrototypeOf(Witness.prototype, Stack.prototype);
/**
* Inject properties from options object.
@ -118,6 +100,66 @@ Witness.fromArray = function fromArray(items) {
return new Witness().fromArray(items);
};
/**
* Convert witness to an array of buffers.
* @returns {Buffer[]}
*/
Witness.prototype.toItems = function toItems() {
return this.items.slice();
};
/**
* Inject properties from an array of buffers.
* @private
* @param {Buffer[]} items
*/
Witness.prototype.fromItems = function fromItems(items) {
assert(Array.isArray(items));
this.items = items;
return this;
};
/**
* Insantiate witness from an array of buffers.
* @param {Buffer[]} items
* @returns {Witness}
*/
Witness.fromItems = function fromItems(items) {
return new Witness().fromItems(items);
};
/**
* Convert witness to a stack.
* @returns {Stack}
*/
Witness.prototype.toStack = function toStack() {
return new Stack(this.toArray());
};
/**
* Inject properties from a stack.
* @private
* @param {Stack} stack
*/
Witness.prototype.fromStack = function fromStack(stack) {
return this.fromArray(stack.items);
};
/**
* Insantiate witness from a stack.
* @param {Stack} stack
* @returns {Witness}
*/
Witness.fromStack = function fromStack(stack) {
return new Witness().fromStack(stack);
};
/**
* Inspect a Witness object.
* @returns {String} Human-readable script.
@ -127,25 +169,6 @@ Witness.prototype.inspect = function inspect() {
return `<Witness: ${this.toString()}>`;
};
/**
* Convert a Witness object to a String.
* @returns {String} Human-readable script.
*/
Witness.prototype.toString = function toString() {
return common.formatStack(this.items);
};
/**
* Format the witness object as bitcoind asm.
* @param {Boolean?} decode - Attempt to decode hash types.
* @returns {String} Human-readable script.
*/
Witness.prototype.toASM = function toASM(decode) {
return common.formatStackASM(this.items, decode);
};
/**
* Clone the witness object.
* @returns {Witness} A clone of the current witness object.
@ -169,14 +192,12 @@ Witness.prototype.inject = function inject(witness) {
};
/**
* Convert the Witness to a Stack object.
* This is usually done before executing
* a witness program.
* @returns {Stack}
* Compile witness (NOP).
* @returns {Witness}
*/
Witness.prototype.toStack = function toStack() {
return new Stack(this.items.slice());
Witness.prototype.compile = function compile() {
return this;
};
/**
@ -215,6 +236,16 @@ Witness.prototype.isPubkeyInput = function isPubkeyInput() {
return false;
};
/**
* Get P2PK signature if present.
* Always returns null.
* @returns {Buffer|null}
*/
Witness.prototype.getPubkeyInput = function getPubkeyInput() {
return null;
};
/**
* "Guess" whether the witness is a pubkeyhash input.
* This method is not 100% reliable.
@ -227,6 +258,17 @@ Witness.prototype.isPubkeyhashInput = function isPubkeyhashInput() {
&& common.isKeyEncoding(this.items[1]);
};
/**
* Get P2PKH signature and key if present.
* @returns {Array} [sig, key]
*/
Witness.prototype.getPubkeyhashInput = function getPubkeyhashInput() {
if (!this.isPubkeyhashInput())
return [null, null];
return [this.items[0], this.items[1]];
};
/**
* "Test" whether the witness is a multisig input.
* Always returns false.
@ -237,6 +279,16 @@ Witness.prototype.isMultisigInput = function isMultisigInput() {
return false;
};
/**
* Get multisig signatures key if present.
* Always returns null.
* @returns {Buffer[]|null}
*/
Witness.prototype.getMultisigInput = function getMultisigInput() {
return null;
};
/**
* "Guess" whether the witness is a scripthash input.
* This method is not 100% reliable.
@ -247,6 +299,17 @@ Witness.prototype.isScripthashInput = function isScripthashInput() {
return this.items.length > 0 && !this.isPubkeyhashInput();
};
/**
* Get P2SH redeem script if present.
* @returns {Buffer|null}
*/
Witness.prototype.getScripthashInput = function getScripthashInput() {
if (!this.isScripthashInput())
return null;
return this.items[this.items.length - 1];
};
/**
* "Guess" whether the witness is an unknown/non-standard type.
* This method is not 100% reliable.
@ -292,14 +355,6 @@ Witness.prototype.getRedeem = function getRedeem() {
return Script.fromRaw(redeem);
};
/**
* Does nothing currently.
*/
Witness.prototype.compile = function compile() {
// NOP
};
/**
* Find a data element in a witness.
* @param {Buffer} data - Data element to match against.
@ -390,179 +445,6 @@ Witness.fromJSON = function fromJSON(json) {
return new Witness().fromJSON(json);
};
/**
* Unshift an item onto the witness vector.
* @param {Number|String|Buffer|ScriptNum} data
* @returns {Number}
*/
Witness.prototype.unshift = function unshift(data) {
return this.items.unshift(Witness.encodeItem(data));
};
/**
* Push an item onto the witness vector.
* @param {Number|String|Buffer|ScriptNum} data
* @returns {Number}
*/
Witness.prototype.push = function push(data) {
return this.items.push(Witness.encodeItem(data));
};
/**
* Shift an item off the witness vector.
* @returns {Buffer}
*/
Witness.prototype.shift = function shift() {
return this.items.shift();
};
/**
* Shift an item off the witness vector.
* @returns {Buffer}
*/
Witness.prototype.pop = function pop(data) {
return this.items.pop();
};
/**
* Remove an item from the witness vector.
* @param {Number} index
* @returns {Buffer}
*/
Witness.prototype.remove = function remove(i) {
return this.items.splice(i, 1)[0];
};
/**
* Insert an item into the witness vector.
* @param {Number} index
* @param {Number|String|Buffer|ScriptNum} data
*/
Witness.prototype.insert = function insert(i, data) {
assert(i <= this.items.length, 'Index out of bounds.');
this.items.splice(i, 0, Witness.encodeItem(data))[0];
};
/**
* Get an item from the witness vector.
* @param {Number} index
* @returns {Buffer}
*/
Witness.prototype.get = function get(i) {
return this.items[i];
};
/**
* Get a small int (0-16) from the witness vector.
* @param {Number} index
* @returns {Number} `-1` on non-existent.
*/
Witness.prototype.getSmall = function getSmall(i) {
const item = this.items[i];
if (!item || item.length > 1)
return -1;
if (item.length === 0)
return 0;
if (!(item[0] >= 1 && item[1] <= 16))
return -1;
return item[0];
};
/**
* Get a number from the witness vector.
* @param {Number} index
* @returns {ScriptNum}
*/
Witness.prototype.getNumber = function getNumber(i) {
const item = this.items[i];
if (!item || item.length > 5)
return null;
return ScriptNum.decode(item, false, 5);
};
/**
* Get a string from the witness vector.
* @param {Number} index
* @returns {String}
*/
Witness.prototype.getString = function getString(i) {
const item = this.items[i];
if (!item)
return null;
return item.toString('utf8');
};
/**
* Clear the witness items.
*/
Witness.prototype.clear = function clear() {
this.items.length = 0;
};
/**
* Set an item in the witness vector.
* @param {Number} index
* @param {Number|String|Buffer|ScriptNum} data
*/
Witness.prototype.set = function set(i, data) {
assert(i <= this.items.length, 'Index out of bounds.');
this.items[i] = Witness.encodeItem(data);
};
/**
* Encode a witness item.
* @param {Number|String|Buffer|ScriptNum} data
* @returns {Buffer}
*/
Witness.encodeItem = function encodeItem(data) {
if (data instanceof Opcode)
data = data.data || data.value;
if (Buffer.isBuffer(data))
return data;
if (typeof data === 'number') {
if (data === opcodes.OP_1NEGATE)
return STACK_NEGATE;
if (data === opcodes.OP_0)
return STACK_FALSE;
if (data >= opcodes.OP_1 && data <= opcodes.OP_16)
return Buffer.from([data - 0x50]);
throw new Error('Non-push opcode in witness.');
}
if (typeof data === 'string')
return Buffer.from(data, 'utf8');
if (typeof data === 'boolean')
return data ? STACK_TRUE : STACK_FALSE;
if (ScriptNum.isEncodable(data))
return ScriptNum.encode(data);
throw new Error('Not a witness item.');
};
/**
* Inject properties from buffer reader.
* @private
@ -655,9 +537,7 @@ Witness.fromString = function fromString(items) {
*/
Witness.isWitness = function isWitness(obj) {
return obj
&& Array.isArray(obj.items)
&& typeof obj.toStack === 'function';
return obj instanceof Witness;
};
/*

165
lib/utils/enforce.js Normal file
View File

@ -0,0 +1,165 @@
/*!
* enforce.js - type enforcement for bcoin
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
const util = require('./util');
function enforce(value, name, type, func) {
if (!value) {
if (!func)
func = enforce;
if (name && !type)
throwError(name, func);
if (!name)
name = 'value';
throwError(`'${name}' must be a(n) ${type}.`, func);
}
}
function throwError(msg, func) {
const error = new TypeError(msg);
if (Error.captureStackTrace && func)
Error.captureStackTrace(error, func);
throw error;
}
enforce.none = function none(value, name) {
enforce(value == null, name, 'object', none);
};
enforce.nul = function nul(value, name) {
enforce(value === null, name, 'object', nul);
};
enforce.undef = function undef(value, name) {
enforce(value === undefined, name, 'object', undef);
};
enforce.str = function str(value, name) {
enforce(typeof value === 'string', name, 'string', str);
};
enforce.bool = function bool(value, name) {
enforce(typeof value === 'boolean', name, 'boolean', bool);
};
enforce.num = function num(value, name) {
enforce(util.isNumber(value), name, 'number', num);
};
enforce.obj = function obj(v, name) {
enforce(v && typeof v === 'object' && !Array.isArray(v), name, 'object', obj);
};
enforce.array = function array(value, name) {
enforce(Array.isArray(value), name, 'object', array);
};
enforce.func = function func(value, name) {
enforce(typeof value === 'function', name, 'function', func);
};
enforce.error = function error(value, name) {
enforce(value instanceof Error, name, 'object', error);
};
enforce.regexp = function regexp(value, name) {
enforce(value && typeof value.exec === 'function' , name, 'object', regexp);
};
enforce.buf = function buf(value, name) {
enforce(Buffer.isBuffer(value), name, 'buffer', buf);
};
enforce.len = function len(value, length, name) {
if ((typeof value !== 'string' && !value) || value.length !== length) {
if (!name)
name = 'value';
throwError(`'${name}' must have a length of ${length}.`, len);
}
};
enforce.instance = function instance(obj, parent, name) {
if (!(obj instanceof parent)) {
if (!name)
name = 'value';
throwError(`'${name}' must be an instance of ${parent.name}.`, instance);
}
};
enforce.uint = function uint(value, name) {
enforce(util.isUInt(value), name, 'uint', uint);
};
enforce.int = function int(value, name) {
enforce(util.isInt(value), name, 'int', int);
};
enforce.u8 = function u8(value, name) {
enforce(util.isU8(value), name, 'uint8', u8);
};
enforce.u16 = function u16(value, name) {
enforce(util.isU16(value), name, 'uint16', u16);
};
enforce.u32 = function u32(value, name) {
enforce(util.isU32(value), name, 'uint32', u32);
};
enforce.u64 = function u64(value, name) {
enforce(util.isU64(value), name, 'uint64', u64);
};
enforce.i8 = function i8(value, name) {
enforce(util.isI8(value), name, 'int8', i8);
};
enforce.i16 = function i16(value, name) {
enforce(util.isI16(value), name, 'int16', i16);
};
enforce.i32 = function i32(value, name) {
enforce(util.isI32(value), name, 'int32', i32);
};
enforce.i64 = function i64(value, name) {
enforce(util.isI64(value), name, 'int64', i64);
};
enforce.ufloat = function ufloat(value, name) {
enforce(util.isUfloat(value), name, 'positive float', ufloat);
};
enforce.float = function float(value, name) {
enforce(util.isFloat(value), name, 'float', float);
};
enforce.ascii = function ascii(value, name) {
enforce(util.isAscii(value), name, 'ascii string', ascii);
};
enforce.hex = function hex(value, name) {
enforce(util.isHex(value), name, 'hex string', hex);
};
enforce.hex160 = function hex160(value, name) {
enforce(util.isHex160(value), name, '160 bit hex string', hex160);
};
enforce.hex256 = function hex256(value, name) {
enforce(util.isHex256(value), name, '256 bit hex string', hex256);
};
enforce.base58 = function base58(value, name) {
enforce(util.isBase58(value), name, 'base58 string', base58);
};
module.exports = enforce;

View File

@ -19,6 +19,7 @@ exports.bech32 = require('./bech32');
exports.Bloom = require('./bloom');
exports.co = require('./co');
exports.encoding = require('./encoding');
exports.enforce = require('./enforce');
exports.fs = require('./fs');
exports.GCSFilter = require('./gcs');
exports.Heap = require('./heap');

View File

@ -281,6 +281,7 @@ util.inspectify = function inspectify(obj, color) {
return obj;
inspectOptions.colors = color !== false;
return nodeUtil.inspect(obj, inspectOptions);
};

View File

@ -57,7 +57,7 @@ const Mnemonic = HD.Mnemonic;
* @param {Number?} options.m - `m` value for multisig.
* @param {Number?} options.n - `n` value for multisig.
* @param {String?} options.id - Wallet ID (used for storage)
* @param {String?} options.mnemonic - mnemonic phrase to use to instantiate an
* @param {String?} options.mnemonic - mnemonic phrase to use to instantiate an
* hd private key for wallet
* (default=account key "address").
*/
@ -1643,6 +1643,7 @@ Wallet.prototype.increaseFee = async function increaseFee(hash, rate, passphrase
throw new Error('Not all coins available.');
const oldFee = tx.getFee(view);
let fee = tx.getMinFee(null, rate);
if (fee > MTX.Selector.MAX_FEE)
@ -1655,10 +1656,8 @@ Wallet.prototype.increaseFee = async function increaseFee(hash, rate, passphrase
mtx.view = view;
for (const input of mtx.inputs) {
input.script.length = 0;
input.script.compile();
input.witness.length = 0;
input.witness.compile();
input.script.clear();
input.witness.clear();
}
let change;

View File

@ -20,7 +20,7 @@ const MTX = require('../primitives/mtx');
const TX = require('../primitives/tx');
const KeyRing = require('../primitives/keyring');
const CoinView = require('../coins/coinview');
const {ScriptError} = require('../script/common');
const ScriptError = require('../script/scripterror');
/*
* Constants

View File

@ -9,10 +9,10 @@ const Output = require('../lib/primitives/output');
const Outpoint = require('../lib/primitives/outpoint');
const TX = require('../lib/primitives/tx');
const random = require('../lib/crypto/random');
const flags = Script.flags;
const MANDATORY = Script.flags.MANDATORY_VERIFY_FLAGS
| Script.flags.VERIFY_WITNESS;
const STANDARD = Script.flags.STANDARD_VERIFY_FLAGS;
const MANDATORY = flags.MANDATORY_VERIFY_FLAGS | flags.VERIFY_WITNESS;
const STANDARD = flags.STANDARD_VERIFY_FLAGS;
function randomOutpoint() {
const hash = random.randomBytes(32).toString('hex');
@ -36,14 +36,13 @@ function randomTX() {
const tx = new TX();
const inputs = util.random(1, 5);
const outputs = util.random(0, 5);
let i;
tx.version = util.random(0, 0xffffffff);
for (i = 0; i < inputs; i++)
for (let i = 0; i < inputs; i++)
tx.inputs.push(randomInput());
for (i = 0; i < outputs; i++)
for (let i = 0; i < outputs; i++)
tx.inputs.push(randomOutput());
if (util.random(0, 5) === 0)
@ -57,10 +56,9 @@ function randomTX() {
function randomWitness(redeem) {
const size = util.random(1, 100);
const witness = new Witness();
let i, len;
for (i = 0; i < size; i++) {
len = util.random(0, 100);
for (let i = 0; i < size; i++) {
const len = util.random(0, 100);
witness.push(random.randomBytes(len));
}
@ -75,19 +73,16 @@ function randomWitness(redeem) {
function randomInputScript(redeem) {
const size = util.random(1, 100);
const script = new Script();
let i, len;
for (i = 0; i < size; i++) {
len = util.random(0, 100);
script.push(random.randomBytes(len));
for (let i = 0; i < size; i++) {
const len = util.random(0, 100);
script.pushData(random.randomBytes(len));
}
if (redeem)
script.push(redeem);
script.pushData(redeem);
script.compile();
return script;
return script.compile();
}
function randomOutputScript() {
@ -96,14 +91,10 @@ function randomOutputScript() {
}
function isPushOnly(script) {
let i, op;
if (script.isPushOnly())
return true;
for (i = 0; i < script.code.length; i++) {
op = script.code[i];
for (const op of script.code) {
if (op.value === Script.opcodes.NOP)
continue;
@ -132,10 +123,9 @@ function randomMultisig() {
const n = util.random(1, 16);
const m = util.random(1, n);
const keys = [];
let i, len;
for (i = 0; i < n; i++) {
len = util.random(0, 2) === 0 ? 33 : 65;
for (let i = 0; i < n; i++) {
const len = util.random(0, 2) === 0 ? 33 : 65;
keys.push(random.randomBytes(len));
}
@ -247,7 +237,7 @@ function randomWitnessNestedContext() {
const redeem = randomRedeem();
const program = Script.fromProgram(0, redeem.sha256());
return {
input: new Script([program.toRaw()]),
input: Script.fromItems([program.toRaw()]),
witness: randomWitness(redeem.toRaw()),
output: Script.fromScripthash(program.hash160()),
redeem: redeem
@ -275,7 +265,6 @@ function randomContext() {
function fuzzSimple(flags) {
let tx = randomTX();
let total = -1;
let stack, input, output;
for (;;) {
if (++total % 1000 === 0)
@ -284,8 +273,8 @@ function fuzzSimple(flags) {
if (total % 500 === 0)
tx = randomTX();
stack = new Stack();
input = randomInputScript();
const stack = new Stack();
const input = randomInputScript();
try {
input.execute(stack, flags, tx, 0, 0, 0);
@ -295,7 +284,7 @@ function fuzzSimple(flags) {
throw e;
}
output = randomOutputScript();
const output = randomOutputScript();
try {
output.execute(stack, flags, tx, 0, 0, 0);
@ -308,7 +297,7 @@ function fuzzSimple(flags) {
if (stack.length === 0)
continue;
if (!stack.bool(-1))
if (!stack.getBool(-1))
continue;
if (isPushOnly(output))
@ -332,7 +321,6 @@ function fuzzSimple(flags) {
function fuzzVerify(flags) {
let tx = randomTX();
let total = -1;
let input, output, witness;
for (;;) {
if (++total % 1000 === 0)
@ -341,9 +329,9 @@ function fuzzVerify(flags) {
if (total % 500 === 0)
tx = randomTX();
input = randomInputScript();
witness = randomWitness();
output = randomOutputScript();
const input = randomInputScript();
const witness = randomWitness();
const output = randomOutputScript();
try {
Script.verify(
@ -382,7 +370,6 @@ function fuzzVerify(flags) {
function fuzzLess(flags) {
let tx = randomTX();
let total = -1;
let ctx;
for (;;) {
if (++total % 1000 === 0)
@ -391,7 +378,7 @@ function fuzzLess(flags) {
if (total % 500 === 0)
tx = randomTX();
ctx = randomContext();
const ctx = randomContext();
try {
Script.verify(

View File

@ -6,13 +6,10 @@ const encoding = require('../lib/utils/encoding');
const TX = require('../lib/primitives/tx');
const Block = require('../lib/primitives/block');
const Script = require('../lib/script/script');
const Opcode = require('../lib/script/opcode');
const ScriptNum = require('../lib/script/scriptnum');
const opcodes = Script.opcodes;
function createGenesisBlock(options) {
let flags = options.flags;
let script = options.script;
let key = options.key;
let reward = options.reward;
if (!flags) {
@ -21,13 +18,11 @@ function createGenesisBlock(options) {
'ascii');
}
if (!script) {
script = Script.fromArray([
Buffer.from('04678afdb0fe5548271967f1a67130b7105cd6a828e039'
+ '09a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c3'
+ '84df7ba0b8d578a4c702b6bf11d5f', 'hex'),
opcodes.OP_CHECKSIG
]);
if (!key) {
key = Buffer.from(''
+ '04678afdb0fe5548271967f1a67130b7105cd6a828e039'
+ '09a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c3'
+ '84df7ba0b8d578a4c702b6bf11d5f', 'hex');
}
if (!reward)
@ -40,16 +35,16 @@ function createGenesisBlock(options) {
hash: encoding.NULL_HASH,
index: 0xffffffff
},
script: [
Opcode.fromNumber(new ScriptNum(486604799)),
Opcode.fromPush(Buffer.from([4])),
Opcode.fromData(flags)
],
script: Script()
.pushInt(486604799)
.pushPush(Buffer.from([4]))
.pushData(flags)
.compile(),
sequence: 0xffffffff
}],
outputs: [{
value: reward,
script: script
script: Script.fromPubkey(key)
}],
locktime: 0
});

View File

@ -4,7 +4,6 @@
'use strict';
const assert = require('./util/assert');
const ScriptNum = require('../lib/script/scriptnum');
const consensus = require('../lib/protocol/consensus');
const encoding = require('../lib/utils/encoding');
const Coin = require('../lib/primitives/coin');
@ -17,6 +16,7 @@ const MemWallet = require('./util/memwallet');
const Network = require('../lib/protocol/network');
const Output = require('../lib/primitives/output');
const common = require('../lib/blockchain/common');
const Opcode = require('../lib/script/opcode');
const opcodes = Script.opcodes;
const network = Network.get('regtest');
@ -79,8 +79,8 @@ async function mineCSV(fund) {
spend.addOutput({
script: [
ScriptNum.encode(1),
Script.opcodes.OP_CHECKSEQUENCEVERIFY
Opcode.fromInt(1),
Opcode.fromSymbol('checksequenceverify')
],
value: 10000
});
@ -419,8 +419,8 @@ describe('Chain', function() {
spend.addOutput({
script: [
ScriptNum.encode(2),
Script.opcodes.OP_CHECKSEQUENCEVERIFY
Opcode.fromInt(2),
Opcode.fromSymbol('checksequenceverify')
],
value: 10000
});
@ -444,8 +444,8 @@ describe('Chain', function() {
spend.addOutput({
script: [
ScriptNum.encode(1),
Script.opcodes.OP_CHECKSEQUENCEVERIFY
Opcode.fromInt(1),
Opcode.fromSymbol('checksequenceverify')
],
value: 1 * 1e8
});
@ -479,8 +479,8 @@ describe('Chain', function() {
spend.addOutput({
script: [
ScriptNum.encode(2),
Script.opcodes.OP_CHECKSEQUENCEVERIFY
Opcode.fromInt(2),
Opcode.fromSymbol('checksequenceverify')
],
value: 1 * 1e8
});
@ -547,7 +547,6 @@ describe('Chain', function() {
const tx = block.txs[0];
const input = tx.inputs[0];
input.witness.set(0, Buffer.allocUnsafe(33));
input.witness.compile();
block.refresh(true);
assert.strictEqual(await addBlock(block), 'bad-witness-nonce-size');
});
@ -557,7 +556,6 @@ describe('Chain', function() {
const tx = block.txs[0];
const input = tx.inputs[0];
input.witness.set(0, encoding.ONE_HASH);
input.witness.compile();
block.refresh(true);
assert.strictEqual(await addBlock(block), 'bad-witness-merkle-match');
});
@ -570,9 +568,9 @@ describe('Chain', function() {
assert(output.script.isCommitment());
const commit = Buffer.from(output.script.get(1));
const commit = Buffer.from(output.script.getData(1));
commit.fill(0, 10);
output.script.set(1, commit);
output.script.setData(1, commit);
output.script.compile();
block.refresh(true);
@ -787,13 +785,14 @@ describe('Chain', function() {
const flags = common.flags.DEFAULT_FLAGS & ~common.flags.VERIFY_POW;
const redeem = new Script();
redeem.push(new ScriptNum(20));
redeem.pushInt(20);
for (let i = 0; i < 20; i++)
redeem.push(encoding.ZERO_KEY);
redeem.pushData(encoding.ZERO_KEY);
redeem.pushInt(20);
redeem.pushOp(opcodes.OP_CHECKMULTISIG);
redeem.push(new ScriptNum(20));
redeem.push(opcodes.OP_CHECKMULTISIG);
redeem.compile();
const script = Script.fromScripthash(redeem.hash160());
@ -828,13 +827,15 @@ describe('Chain', function() {
const job = await cpu.createJob();
const script = new Script();
script.push(new ScriptNum(20));
script.pushInt(20);
for (let i = 0; i < 20; i++)
script.push(encoding.ZERO_KEY);
script.pushData(encoding.ZERO_KEY);
script.pushInt(20);
script.pushOp(opcodes.OP_CHECKMULTISIG);
script.push(new ScriptNum(20));
script.push(opcodes.OP_CHECKMULTISIG);
script.compile();
for (let i = start; i <= end; i++) {
@ -848,7 +849,7 @@ describe('Chain', function() {
for (let j = 2; j < cb.outputs.length; j++) {
mtx.addTX(cb, j);
mtx.inputs[j - 2].script = new Script([script.toRaw()]);
mtx.inputs[j - 2].script.fromItems([script.toRaw()]);
}
mtx.addOutput(witWallet.getAddress(), 1);

View File

@ -81,7 +81,7 @@ describe('Mempool', function() {
const sig = t1.signature(0, script, 70000, key.privateKey, ALL, 0);
t1.inputs[0].script = new Script([sig]);
t1.inputs[0].script = Script.fromItems([sig]);
// balance: 51000
wallet.sign(t1);
@ -126,8 +126,9 @@ describe('Mempool', function() {
wallet.template(fake);
// Fake signature
fake.inputs[0].script.set(0, encoding.ZERO_SIG);
fake.inputs[0].script.compile();
const input = fake.inputs[0];
input.script.setData(0, encoding.ZERO_SIG);
input.script.compile();
// balance: 11000
{
@ -188,7 +189,7 @@ describe('Mempool', function() {
chain.tip.height = 200;
const sig = tx.signature(0, prev, 70000, key.privateKey, ALL, 0);
tx.inputs[0].script = new Script([sig]);
tx.inputs[0].script = Script.fromItems([sig]);
await mempool.addTX(tx.toTX());
chain.tip.height = 0;
@ -209,7 +210,7 @@ describe('Mempool', function() {
chain.tip.height = 200 - 1;
const sig = tx.signature(0, prev, 70000, key.privateKey, ALL, 0);
tx.inputs[0].script = new Script([sig]);
tx.inputs[0].script = Script.fromItems([sig]);
let err;
try {
@ -268,7 +269,7 @@ describe('Mempool', function() {
tx.addCoin(dummyInput(prev, prevHash));
const sig = tx.signature(0, prev, 70000, key.privateKey, ALL, 0);
tx.inputs[0].script = new Script([sig]);
tx.inputs[0].script = Script.fromItems([sig]);
tx.inputs[0].witness.push(Buffer.alloc(0));
let err;

View File

@ -4,11 +4,11 @@
'use strict';
const assert = require('./util/assert');
const ScriptNum = require('../lib/script/scriptnum');
const consensus = require('../lib/protocol/consensus');
const co = require('../lib/utils/co');
const Coin = require('../lib/primitives/coin');
const Script = require('../lib/script/script');
const Opcode = require('../lib/script/opcode');
const FullNode = require('../lib/node/fullnode');
const MTX = require('../lib/primitives/mtx');
const TX = require('../lib/primitives/tx');
@ -63,8 +63,8 @@ async function mineCSV(fund) {
spend.addOutput({
script: [
ScriptNum.encode(1),
Script.opcodes.OP_CHECKSEQUENCEVERIFY
Opcode.fromInt(1),
Opcode.fromSymbol('checksequenceverify')
],
value: 10 * 1e8
});
@ -348,8 +348,8 @@ describe('Node', function() {
spend.addOutput({
script: [
ScriptNum.encode(2),
Script.opcodes.OP_CHECKSEQUENCEVERIFY
Opcode.fromInt(2),
Opcode.fromSymbol('checksequenceverify')
],
value: 10 * 1e8
});
@ -373,8 +373,8 @@ describe('Node', function() {
spend.addOutput({
script: [
ScriptNum.encode(1),
Script.opcodes.OP_CHECKSEQUENCEVERIFY
Opcode.fromInt(1),
Opcode.fromSymbol('checksequenceverify')
],
value: 10 * 1e8
});
@ -418,8 +418,8 @@ describe('Node', function() {
spend.addOutput({
script: [
ScriptNum.encode(2),
Script.opcodes.OP_CHECKSEQUENCEVERIFY
Opcode.fromInt(2),
Opcode.fromSymbol('checksequenceverify')
],
value: 10 * 1e8
});

View File

@ -7,10 +7,10 @@ const assert = require('./util/assert');
const Script = require('../lib/script/script');
const Witness = require('../lib/script/witness');
const Stack = require('../lib/script/stack');
const Opcode = require('../lib/script/opcode');
const TX = require('../lib/primitives/tx');
const util = require('../lib/utils/util');
const encoding = require('../lib/utils/encoding');
const opcodes = Script.opcodes;
const scripts = require('./data/script-tests.json');
@ -18,7 +18,7 @@ function isSuccess(stack) {
if (stack.length === 0)
return false;
if (!stack.bool(-1))
if (!stack.getBool(-1))
return false;
return true;
@ -67,34 +67,6 @@ function parseScriptTest(data) {
}
describe('Script', function() {
it('should encode/decode script', () => {
const src = Buffer.from(''
+ '20'
+ '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
+ '20'
+ '101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f'
+ 'ac',
'hex');
const decoded = Script.fromRaw(src);
assert.strictEqual(decoded.code.length, 3);
assert.strictEqual(decoded.code[0].data.toString('hex'),
'000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');
assert.strictEqual(decoded.code[1].data.toString('hex'),
'101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f');
assert.strictEqual(decoded.code[2].value, opcodes.OP_CHECKSIG);
const dst = decoded.toRaw();
assert.bufferEqual(dst, src);
});
it('should encode/decode numbers', () => {
const script = [0, 0x51, 0x52, 0x60];
const encoded = Script.fromArray(script).raw;
const decoded = Script(encoded).toArray();
assert.deepStrictEqual(decoded, script);
});
it('should recognize a P2SH output', () => {
const hex = 'a91419a7d869032368fd1f1e26e5e73a4ad0e474960e87';
const decoded = Script.fromRaw(hex, 'hex');
@ -110,17 +82,20 @@ describe('Script', function() {
it('should handle if statements correctly', () => {
{
const input = new Script([opcodes.OP_1, opcodes.OP_2]);
const input = new Script([
Opcode.fromInt(1),
Opcode.fromInt(2)
]);
const output = new Script([
opcodes.OP_2,
opcodes.OP_EQUAL,
opcodes.OP_IF,
opcodes.OP_3,
opcodes.OP_ELSE,
opcodes.OP_4,
opcodes.OP_ENDIF,
opcodes.OP_5
Opcode.fromInt(2),
Opcode.fromSymbol('equal'),
Opcode.fromSymbol('if'),
Opcode.fromInt(3),
Opcode.fromSymbol('else'),
Opcode.fromInt(4),
Opcode.fromSymbol('endif'),
Opcode.fromInt(5)
]);
const stack = new Stack();
@ -132,17 +107,20 @@ describe('Script', function() {
}
{
const input = new Script([opcodes.OP_1, opcodes.OP_2]);
const input = new Script([
Opcode.fromInt(1),
Opcode.fromInt(2)
]);
const output = new Script([
opcodes.OP_9,
opcodes.OP_EQUAL,
opcodes.OP_IF,
opcodes.OP_3,
opcodes.OP_ELSE,
opcodes.OP_4,
opcodes.OP_ENDIF,
opcodes.OP_5
Opcode.fromInt(9),
Opcode.fromSymbol('equal'),
Opcode.fromSymbol('if'),
Opcode.fromInt(3),
Opcode.fromSymbol('else'),
Opcode.fromInt(4),
Opcode.fromSymbol('endif'),
Opcode.fromInt(5)
]);
const stack = new Stack();
@ -154,15 +132,18 @@ describe('Script', function() {
}
{
const input = new Script([opcodes.OP_1, opcodes.OP_2]);
const input = new Script([
Opcode.fromInt(1),
Opcode.fromInt(2)
]);
const output = new Script([
opcodes.OP_2,
opcodes.OP_EQUAL,
opcodes.OP_IF,
opcodes.OP_3,
opcodes.OP_ENDIF,
opcodes.OP_5
Opcode.fromInt(2),
Opcode.fromSymbol('equal'),
Opcode.fromSymbol('if'),
Opcode.fromInt(3),
Opcode.fromSymbol('endif'),
Opcode.fromInt(5)
]);
const stack = new Stack();
@ -174,15 +155,18 @@ describe('Script', function() {
}
{
const input = new Script([opcodes.OP_1, opcodes.OP_2]);
const input = new Script([
Opcode.fromInt(1),
Opcode.fromInt(2)
]);
const output = new Script([
opcodes.OP_9,
opcodes.OP_EQUAL,
opcodes.OP_IF,
opcodes.OP_3,
opcodes.OP_ENDIF,
opcodes.OP_5
Opcode.fromInt(9),
Opcode.fromSymbol('equal'),
Opcode.fromSymbol('if'),
Opcode.fromInt(3),
Opcode.fromSymbol('endif'),
Opcode.fromInt(5)
]);
const stack = new Stack();
@ -194,15 +178,18 @@ describe('Script', function() {
}
{
const input = new Script([opcodes.OP_1, opcodes.OP_2]);
const input = new Script([
Opcode.fromInt(1),
Opcode.fromInt(2)
]);
const output = new Script([
opcodes.OP_9,
opcodes.OP_EQUAL,
opcodes.OP_NOTIF,
opcodes.OP_3,
opcodes.OP_ENDIF,
opcodes.OP_5
Opcode.fromInt(9),
Opcode.fromSymbol('equal'),
Opcode.fromSymbol('notif'),
Opcode.fromInt(3),
Opcode.fromSymbol('endif'),
Opcode.fromInt(5)
]);
const stack = new Stack();
@ -216,15 +203,15 @@ describe('Script', function() {
it('should handle CScriptNums correctly', () => {
const input = new Script([
Buffer.from('ffffff7f', 'hex'),
opcodes.OP_NEGATE,
opcodes.OP_DUP,
opcodes.OP_ADD
Opcode.fromString('ffffff7f', 'hex'),
Opcode.fromSymbol('negate'),
Opcode.fromSymbol('dup'),
Opcode.fromSymbol('add')
]);
const output = new Script([
Buffer.from('feffffff80', 'hex'),
opcodes.OP_EQUAL
Opcode.fromString('feffffff80', 'hex'),
Opcode.fromSymbol('equal')
]);
const stack = new Stack();
@ -237,15 +224,15 @@ describe('Script', function() {
it('should handle CScriptNums correctly', () => {
const input = new Script([
opcodes.OP_11,
opcodes.OP_10,
opcodes.OP_1,
opcodes.OP_ADD
Opcode.fromInt(11),
Opcode.fromInt(10),
Opcode.fromInt(1),
Opcode.fromSymbol('add')
]);
const output = new Script([
opcodes.OP_NUMNOTEQUAL,
opcodes.OP_NOT
Opcode.fromSymbol('numnotequal'),
Opcode.fromSymbol('not')
]);
const stack = new Stack();
@ -258,19 +245,19 @@ describe('Script', function() {
it('should handle OP_ROLL correctly', () => {
const input = new Script([
Buffer.from([0x16]),
Buffer.from([0x15]),
Buffer.from([0x14])
Opcode.fromInt(0x16),
Opcode.fromInt(0x15),
Opcode.fromInt(0x14)
]);
const output = new Script([
opcodes.OP_0,
opcodes.OP_ROLL,
Buffer.from([0x14]),
opcodes.OP_EQUALVERIFY,
opcodes.OP_DEPTH,
opcodes.OP_2,
opcodes.OP_EQUAL
Opcode.fromInt(0),
Opcode.fromSymbol('roll'),
Opcode.fromInt(0x14),
Opcode.fromSymbol('equalverify'),
Opcode.fromSymbol('depth'),
Opcode.fromInt(2),
Opcode.fromSymbol('equal')
]);
const stack = new Stack();
@ -302,7 +289,10 @@ describe('Script', function() {
hash: encoding.NULL_HASH,
index: 0xffffffff
},
script: [opcodes.OP_0, opcodes.OP_0],
script: [
Opcode.fromOp(0),
Opcode.fromOp(0)
],
witness: [],
sequence: 0xffffffff
}],

View File

@ -13,11 +13,11 @@ const Output = require('../lib/primitives/output');
const Outpoint = require('../lib/primitives/outpoint');
const Script = require('../lib/script/script');
const Witness = require('../lib/script/witness');
const Opcode = require('../lib/script/opcode');
const Input = require('../lib/primitives/input');
const CoinView = require('../lib/coins/coinview');
const KeyRing = require('../lib/primitives/keyring');
const common = require('./util/common');
const opcodes = Script.opcodes;
const validTests = require('./data/tx-valid.json');
const invalidTests = require('./data/tx-invalid.json');
@ -624,8 +624,8 @@ describe('TX', function() {
const output = Script.fromMultisig(1, 2, [pub, pub]);
const input = new Script([
opcodes.OP_0,
opcodes.OP_0
Opcode.fromOp(0),
Opcode.fromOp(0)
]);
const witness = new Witness();
@ -646,9 +646,9 @@ describe('TX', function() {
const output = Script.fromScripthash(redeem.hash160());
const input = new Script([
opcodes.OP_0,
opcodes.OP_0,
redeem.toRaw()
Opcode.fromOp(0),
Opcode.fromOp(0),
Opcode.fromData(redeem.toRaw())
]);
const witness = new Witness();
@ -707,7 +707,7 @@ describe('TX', function() {
const output = Script.fromScripthash(redeem.hash160());
const input = new Script([
redeem.toRaw()
Opcode.fromData(redeem.toRaw())
]);
const witness = new Witness([
@ -754,7 +754,7 @@ describe('TX', function() {
const output = Script.fromScripthash(redeem.hash160());
const input = new Script([
redeem.toRaw()
Opcode.fromData(redeem.toRaw())
]);
const witness = new Witness([

View File

@ -27,7 +27,7 @@ const KEY2 = 'xprv9s21ZrQH143K3mqiSThzPtWAabQ22Pjp3uSNnZ53A5bQ4udp'
+ 'faKekc2m4AChLYH1XDzANhrSdxHYWUeTWjYJwFwWFyHkTMnMeAcW4JyRCZa';
const workers = new WorkerPool({
enabled: true
enabled: false
});
const wdb = new WalletDB({
@ -222,8 +222,9 @@ async function testP2SH(witness, nesting) {
assert(bob.account.change.getAddress().equals(change2));
assert(carol.account.change.getAddress().equals(change2));
tx.inputs[0][vector].set(2, encoding.ZERO_SIG);
tx.inputs[0][vector].compile();
const input = tx.inputs[0];
input[vector].setData(2, encoding.ZERO_SIG);
input[vector].compile();
assert(!tx.verify(view, flags));
assert.strictEqual(tx.getFee(view), 10000);
@ -373,8 +374,9 @@ describe('Wallet', function() {
// Script inputs but do not sign
await alice.template(fake);
// Fake signature
fake.inputs[0].script.set(0, encoding.ZERO_SIG);
fake.inputs[0].script.compile();
const input = fake.inputs[0];
input.script.setData(0, encoding.ZERO_SIG);
input.script.compile();
// balance: 11000
// Fake TX should temporarily change output.