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

View File

@ -1,37 +1,38 @@
Scripts are array-like objects with some helper functions. Scripts are array-like objects with some helper functions.
``` js ``` js
var bcoin = require('bcoin'); const bcoin = require('bcoin');
var assert = require('assert'); const assert = require('assert');
var ScriptNum = bcoin.scriptnum; const Script = bcoin.script;
var opcodes = bcoin.script.opcodes; const Witness = bcoin.witness;
const Stack = bcoin.stack;
var output = new bcoin.script(); const output = new Script();
output.push(opcodes.OP_DROP); output.pushSym('OP_DROP');
output.push(opcodes.OP_ADD); output.pushSym('OP_ADD');
output.push(new ScriptNum(7)); output.pushInt(7);
output.push(opcodes.OP_NUMEQUAL); output.pushSym('OP_NUMEQUAL');
// Compile the script to its binary representation // Compile the script to its binary representation
// (you must do this if you change something!). // (you must do this if you change something!).
output.compile();
assert(output.getSmall(2) === 7); // compiled as OP_7 assert(output.getSmall(2) === 7); // compiled as OP_7
output.compile();
var input = new bcoin.script(); const input = new Script();
input.set(0, 'hello world'); // add some metadata input.setString(0, 'hello world'); // add some metadata
input.push(new ScriptNum(2)); input.pushInt(2);
input.push(new ScriptNum(5)); input.pushInt(5);
input.push(input.shift()); input.push(input.shift());
assert(input.getString(2) === 'hello world'); assert(input.getString(2) === 'hello world');
input.compile(); input.compile();
// A stack is another array-like object which contains // A stack is another array-like object which contains
// only Buffers (whereas scripts contain Opcode objects). // only Buffers (whereas scripts contain Opcode objects).
var stack = new bcoin.stack(); const stack = new Stack();
input.execute(stack); input.execute(stack);
output.execute(stack); output.execute(stack);
// Verify the script was successful in its execution: // Verify the script was successful in its execution:
assert(stack.length === 1); 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 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). Stack object (an array-like object containing Buffers).
``` js ``` js
var witness = new bcoin.witness(); const witness = new Witness();
witness.push(new ScriptNum(2)); witness.pushInt(2);
witness.push(new ScriptNum(5)); witness.pushInt(5);
witness.push('hello world'); witness.pushString('hello world');
var stack = witness.toStack(); const stack = witness.toStack();
output.execute(stack); output.execute(stack);
``` ```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -89,6 +89,28 @@ Input.prototype.clone = function clone() {
return input; 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. * Get the previous output script type as a string.
* Will "guess" based on the input script and/or * Will "guess" based on the input script and/or
@ -488,11 +510,7 @@ Input.fromTX = function fromTX(tx, index) {
*/ */
Input.isInput = function isInput(obj) { Input.isInput = function isInput(obj) {
return obj return obj instanceof Input;
&& typeof obj.prevout === 'object'
&& typeof obj.script === 'object'
&& typeof obj.witness === 'object'
&& typeof obj.getAddress === 'function';
}; };
/* /*

View File

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

View File

@ -6,8 +6,8 @@
'use strict'; 'use strict';
const util = require('../utils/util');
const assert = require('assert'); const assert = require('assert');
const util = require('../utils/util');
const StaticWriter = require('../utils/writer'); const StaticWriter = require('../utils/writer');
const BufferReader = require('../utils/reader'); const BufferReader = require('../utils/reader');
const encoding = require('../utils/encoding'); const encoding = require('../utils/encoding');
@ -62,6 +62,47 @@ Outpoint.fromOptions = function fromOptions(options) {
return new Outpoint().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 * Test whether the outpoint is null (hash of zeroes
* with max-u32 index). Used to detect coinbases. * with max-u32 index). Used to detect coinbases.
@ -293,10 +334,7 @@ Outpoint.prototype.inspect = function inspect() {
*/ */
Outpoint.isOutpoint = function isOutpoint(obj) { Outpoint.isOutpoint = function isOutpoint(obj) {
return obj return obj instanceof Outpoint;
&& typeof obj.hash === 'string'
&& typeof obj.index === 'number'
&& typeof obj.toKey === 'function';
}; };
/* /*

View File

@ -118,6 +118,35 @@ Output.prototype.clone = function clone() {
return output; 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. * Get the script type as a string.
* @returns {ScriptType} type * @returns {ScriptType} type
@ -339,10 +368,7 @@ Output.fromRaw = function fromRaw(data, enc) {
*/ */
Output.isOutput = function isOutput(obj) { Output.isOutput = function isOutput(obj) {
return obj return obj instanceof Output;
&& typeof obj.value === 'number'
&& typeof obj.script === 'object'
&& typeof obj.getAddress === 'function';
}; };
/* /*

View File

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

View File

@ -14,6 +14,7 @@
const assert = require('assert'); const assert = require('assert');
const util = require('../utils/util'); const util = require('../utils/util');
const secp256k1 = require('../crypto/secp256k1'); const secp256k1 = require('../crypto/secp256k1');
const ScriptNum = require('./scriptnum');
/** /**
* Script opcodes. * Script opcodes.
@ -163,6 +164,32 @@ exports.opcodes = {
exports.opcodesByVal = util.reverse(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}. * Script and locktime flags. See {@link VerifyFlags}.
* @enum {Number} * @enum {Number}
@ -293,27 +320,6 @@ exports.types = {
exports.typesByVal = util.reverse(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. * Test a signature to see whether it contains a valid sighash type.
* @param {Buffer} sig * @param {Buffer} sig
@ -347,56 +353,6 @@ exports.isLowDER = function isLowDER(sig) {
return secp256k1.isLowS(sig.slice(0, -1)); 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. * Test whether the data element is a valid key.
* @param {Buffer} key * @param {Buffer} key
@ -532,224 +488,31 @@ exports.isSignatureEncoding = function isSignatureEncoding(sig) {
}; };
/** /**
* Cast a big number or Buffer to a bool. * Format stack item into bitcoind asm format.
* @see CastToBool * @param {Buffer} item
* @param {Buffer} value * @param {Boolean?} decode - Attempt to decode hash types.
* @returns {Boolean} * @returns {String} Human-readable string.
*/ */
exports.toBool = function toBool(value) { exports.toASM = function toASM(item, decode) {
assert(Buffer.isBuffer(value)); if (item.length <= 4) {
const num = ScriptNum.decode(item, false, 4);
for (let i = 0; i < value.length; i++) { return num.toString(10);
if (value[i] !== 0) {
// Cannot be negative zero
if (i === value.length - 1 && value[i] === 0x80)
return false;
return true;
}
} }
return false; if (decode && exports.isSignatureEncoding(item)) {
}; const type = item[item.length - 1];
/** let symbol = exports.hashTypeByVal[type & 0x1f] || '';
* Format script code into a human readable-string.
* @param {Array} code
* @returns {String} Human-readable string.
*/
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) { if (symbol) {
out.push(symbol); if (type & exports.hashType.ANYONECANPAY)
continue; symbol += '|ANYONECANPAY';
symbol = `[${symbol}]`;
} }
// Unknown opcodes return item.slice(0, -1).toString('hex') + symbol;
symbol = op.value.toString(16);
if (symbol.length < 2)
symbol = '0' + symbol;
out.push(`0x${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.Opcode = require('./opcode');
exports.Program = require('./program'); exports.Program = require('./program');
exports.Script = require('./script'); exports.Script = require('./script');
exports.ScriptError = require('./scripterror');
exports.ScriptNum = require('./scriptnum'); exports.ScriptNum = require('./scriptnum');
exports.sigcache = require('./sigcache'); exports.sigcache = require('./sigcache');
exports.Stack = require('./stack'); 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; 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. * Encode the opcode to a buffer writer.
* @param {BufferWriter} bw * @param {BufferWriter} bw
@ -156,27 +344,225 @@ Opcode.prototype.toRaw = function toRaw() {
}; };
/** /**
* Calculate opcode size. * Convert the opcode to a bitcoind test string.
* @returns {Number} * @returns {String} Human-readable script code.
*/ */
Opcode.prototype.getSize = function getSize() { Opcode.prototype.toFormat = function toFormat() {
if (!this.data) // Bad push
return 1; if (this.value === -1)
return 'OP_INVALIDOPCODE';
if (this.value <= 0x4b) if (this.data) {
return 1 + this.data.length; const symbol = common.opcodesByVal[this.value];
const data = this.data.toString('hex');
switch (this.value) { // Direct push
case opcodes.OP_PUSHDATA1: if (!symbol) {
return 2 + this.data.length; const size = util.hex8(this.value);
case opcodes.OP_PUSHDATA2: return `0x${size} 0x${data}`;
return 3 + this.data.length; }
case opcodes.OP_PUSHDATA4:
return 5 + this.data.length; // Pushdatas
default: let size = this.data.length.toString(16);
throw new Error('Unknown pushdata opcode.');
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()); br.seek(br.left());
return op; return op;
} }
op.value = value;
op.data = br.readBytes(value); op.data = br.readBytes(value);
return op; return op;
} }
let size;
switch (value) { switch (value) {
case opcodes.OP_PUSHDATA1: case opcodes.OP_PUSHDATA1: {
if (br.left() < 1) { if (br.left() < 1) {
op.value = -1; op.value = -1;
break; break;
} }
size = br.readU8();
const size = br.readU8();
if (br.left() < size) { if (br.left() < size) {
op.value = -1; op.value = -1;
br.seek(br.left()); br.seek(br.left());
break; break;
} }
op.value = value;
op.data = br.readBytes(size); op.data = br.readBytes(size);
break; break;
case opcodes.OP_PUSHDATA2: }
case opcodes.OP_PUSHDATA2: {
if (br.left() < 2) { if (br.left() < 2) {
op.value = -1; op.value = -1;
br.seek(br.left()); br.seek(br.left());
break; break;
} }
size = br.readU16();
const size = br.readU16();
if (br.left() < size) { if (br.left() < size) {
op.value = -1; op.value = -1;
br.seek(br.left()); br.seek(br.left());
break; break;
} }
op.value = value;
op.data = br.readBytes(size); op.data = br.readBytes(size);
break; break;
case opcodes.OP_PUSHDATA4: }
case opcodes.OP_PUSHDATA4: {
if (br.left() < 4) { if (br.left() < 4) {
op.value = -1; op.value = -1;
br.seek(br.left()); br.seek(br.left());
break; break;
} }
size = br.readU32();
const size = br.readU32();
if (br.left() < size) { if (br.left() < size) {
op.value = -1; op.value = -1;
br.seek(br.left()); br.seek(br.left());
break; break;
} }
op.value = value;
op.data = br.readBytes(size); op.data = br.readBytes(size);
break; break;
default: }
op.value = value;
break;
} }
return op; return op;
@ -271,183 +663,6 @@ Opcode.fromRaw = function fromRaw(data) {
return Opcode.fromReader(new BufferReader(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. * Test whether an object an Opcode.
* @param {Object} obj * @param {Object} obj
@ -455,9 +670,7 @@ Opcode.prototype.toSymbol = function toSymbol() {
*/ */
Opcode.isOpcode = function isOpcode(obj) { Opcode.isOpcode = function isOpcode(obj) {
return obj return obj instanceof Opcode;
&& typeof obj.value === 'number'
&& (Buffer.isBuffer(obj.data) || obj.data === null);
}; };
/* /*

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 assert = require('assert');
const {I64} = require('../utils/int64'); const {I64} = require('../utils/int64');
const {ScriptError} = require('./common'); const ScriptError = require('./scripterror');
/* /*
* Constants * Constants

View File

@ -10,10 +10,6 @@
const assert = require('assert'); const assert = require('assert');
const common = require('./common'); const common = require('./common');
const ScriptNum = require('./scriptnum'); 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. * 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. * Inspect the stack.
* @returns {String} Human-readable stack. * @returns {String} Human-readable stack.
@ -60,7 +83,12 @@ Stack.prototype.inspect = function inspect() {
*/ */
Stack.prototype.toString = function toString() { 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) { 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()); 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. * Push item onto stack.
* @see Array#push * @see Array#push
@ -91,60 +212,8 @@ Stack.prototype.clone = function clone() {
Stack.prototype.push = function push(item) { Stack.prototype.push = function push(item) {
assert(Buffer.isBuffer(item)); assert(Buffer.isBuffer(item));
return this.items.push(item); this.items.push(item);
}; return this;
/**
* 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());
}; };
/** /**
@ -156,41 +225,27 @@ Stack.prototype.pushInt = function pushInt(value) {
Stack.prototype.unshift = function unshift(item) { Stack.prototype.unshift = function unshift(item) {
assert(Buffer.isBuffer(item)); assert(Buffer.isBuffer(item));
return this.items.unshift(item); 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);
return this; return this;
}; };
/** /**
* Splice stack items. * Insert an item.
* @see Array#splice
* @param {Number} index * @param {Number} index
* @param {Number} remove * @param {Buffer} item
* @param {Buffer?} insert * @returns {Buffer}
* @returns {Buffer[]}
*/ */
Stack.prototype.splice = function splice(i, remove, insert) { Stack.prototype.insert = function insert(index, item) {
if (i < 0) if (index < 0)
i = this.items.length + i; index += this.items.length;
if (insert === undefined) assert(Buffer.isBuffer(item));
return this.items.splice(i, remove); 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); 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. * Swap stack values.
* @param {Number} i1 - Index 1. * @param {Number} i1 - Index 1.
@ -391,6 +285,211 @@ Stack.prototype.swap = function swap(i1, i2) {
this.items[i2] = v1; 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. * Test an object to see if it is a Stack.
* @param {Object} obj * @param {Object} obj
@ -398,7 +497,73 @@ Stack.prototype.swap = function swap(i1, i2) {
*/ */
Stack.isStack = function isStack(obj) { 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'; 'use strict';
const assert = require('assert'); const assert = require('assert');
const ScriptNum = require('./scriptnum');
const util = require('../utils/util'); const util = require('../utils/util');
const Script = require('./script'); const Script = require('./script');
const common = require('./common'); const common = require('./common');
const encoding = require('../utils/encoding'); const encoding = require('../utils/encoding');
const Opcode = require('./opcode');
const BufferReader = require('../utils/reader'); const BufferReader = require('../utils/reader');
const StaticWriter = require('../utils/staticwriter'); const StaticWriter = require('../utils/staticwriter');
const Address = require('../primitives/address'); const Address = require('../primitives/address');
const Stack = require('./stack'); const Stack = require('./stack');
const opcodes = common.opcodes;
const scriptTypes = common.types; 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. * Refers to the witness field of segregated witness transactions.
@ -39,25 +33,13 @@ function Witness(options) {
if (!(this instanceof Witness)) if (!(this instanceof Witness))
return new Witness(options); return new Witness(options);
this.items = []; Stack.call(this, []);
if (options) if (options)
this.fromOptions(options); this.fromOptions(options);
} }
/* Object.setPrototypeOf(Witness.prototype, Stack.prototype);
* 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;
}
});
/** /**
* Inject properties from options object. * Inject properties from options object.
@ -118,6 +100,66 @@ Witness.fromArray = function fromArray(items) {
return new Witness().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. * Inspect a Witness object.
* @returns {String} Human-readable script. * @returns {String} Human-readable script.
@ -127,25 +169,6 @@ Witness.prototype.inspect = function inspect() {
return `<Witness: ${this.toString()}>`; 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. * Clone the witness object.
* @returns {Witness} A clone of the current 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. * Compile witness (NOP).
* This is usually done before executing * @returns {Witness}
* a witness program.
* @returns {Stack}
*/ */
Witness.prototype.toStack = function toStack() { Witness.prototype.compile = function compile() {
return new Stack(this.items.slice()); return this;
}; };
/** /**
@ -215,6 +236,16 @@ Witness.prototype.isPubkeyInput = function isPubkeyInput() {
return false; 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. * "Guess" whether the witness is a pubkeyhash input.
* This method is not 100% reliable. * This method is not 100% reliable.
@ -227,6 +258,17 @@ Witness.prototype.isPubkeyhashInput = function isPubkeyhashInput() {
&& common.isKeyEncoding(this.items[1]); && 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. * "Test" whether the witness is a multisig input.
* Always returns false. * Always returns false.
@ -237,6 +279,16 @@ Witness.prototype.isMultisigInput = function isMultisigInput() {
return false; 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. * "Guess" whether the witness is a scripthash input.
* This method is not 100% reliable. * This method is not 100% reliable.
@ -247,6 +299,17 @@ Witness.prototype.isScripthashInput = function isScripthashInput() {
return this.items.length > 0 && !this.isPubkeyhashInput(); 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. * "Guess" whether the witness is an unknown/non-standard type.
* This method is not 100% reliable. * This method is not 100% reliable.
@ -292,14 +355,6 @@ Witness.prototype.getRedeem = function getRedeem() {
return Script.fromRaw(redeem); return Script.fromRaw(redeem);
}; };
/**
* Does nothing currently.
*/
Witness.prototype.compile = function compile() {
// NOP
};
/** /**
* Find a data element in a witness. * Find a data element in a witness.
* @param {Buffer} data - Data element to match against. * @param {Buffer} data - Data element to match against.
@ -390,179 +445,6 @@ Witness.fromJSON = function fromJSON(json) {
return new Witness().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. * Inject properties from buffer reader.
* @private * @private
@ -655,9 +537,7 @@ Witness.fromString = function fromString(items) {
*/ */
Witness.isWitness = function isWitness(obj) { Witness.isWitness = function isWitness(obj) {
return obj return obj instanceof Witness;
&& Array.isArray(obj.items)
&& typeof obj.toStack === 'function';
}; };
/* /*

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.Bloom = require('./bloom');
exports.co = require('./co'); exports.co = require('./co');
exports.encoding = require('./encoding'); exports.encoding = require('./encoding');
exports.enforce = require('./enforce');
exports.fs = require('./fs'); exports.fs = require('./fs');
exports.GCSFilter = require('./gcs'); exports.GCSFilter = require('./gcs');
exports.Heap = require('./heap'); exports.Heap = require('./heap');

View File

@ -281,6 +281,7 @@ util.inspectify = function inspectify(obj, color) {
return obj; return obj;
inspectOptions.colors = color !== false; inspectOptions.colors = color !== false;
return nodeUtil.inspect(obj, inspectOptions); 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.m - `m` value for multisig.
* @param {Number?} options.n - `n` value for multisig. * @param {Number?} options.n - `n` value for multisig.
* @param {String?} options.id - Wallet ID (used for storage) * @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 * hd private key for wallet
* (default=account key "address"). * (default=account key "address").
*/ */
@ -1643,6 +1643,7 @@ Wallet.prototype.increaseFee = async function increaseFee(hash, rate, passphrase
throw new Error('Not all coins available.'); throw new Error('Not all coins available.');
const oldFee = tx.getFee(view); const oldFee = tx.getFee(view);
let fee = tx.getMinFee(null, rate); let fee = tx.getMinFee(null, rate);
if (fee > MTX.Selector.MAX_FEE) if (fee > MTX.Selector.MAX_FEE)
@ -1655,10 +1656,8 @@ Wallet.prototype.increaseFee = async function increaseFee(hash, rate, passphrase
mtx.view = view; mtx.view = view;
for (const input of mtx.inputs) { for (const input of mtx.inputs) {
input.script.length = 0; input.script.clear();
input.script.compile(); input.witness.clear();
input.witness.length = 0;
input.witness.compile();
} }
let change; let change;

View File

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

View File

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

View File

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

View File

@ -4,7 +4,6 @@
'use strict'; 'use strict';
const assert = require('./util/assert'); const assert = require('./util/assert');
const ScriptNum = require('../lib/script/scriptnum');
const consensus = require('../lib/protocol/consensus'); const consensus = require('../lib/protocol/consensus');
const encoding = require('../lib/utils/encoding'); const encoding = require('../lib/utils/encoding');
const Coin = require('../lib/primitives/coin'); const Coin = require('../lib/primitives/coin');
@ -17,6 +16,7 @@ const MemWallet = require('./util/memwallet');
const Network = require('../lib/protocol/network'); const Network = require('../lib/protocol/network');
const Output = require('../lib/primitives/output'); const Output = require('../lib/primitives/output');
const common = require('../lib/blockchain/common'); const common = require('../lib/blockchain/common');
const Opcode = require('../lib/script/opcode');
const opcodes = Script.opcodes; const opcodes = Script.opcodes;
const network = Network.get('regtest'); const network = Network.get('regtest');
@ -79,8 +79,8 @@ async function mineCSV(fund) {
spend.addOutput({ spend.addOutput({
script: [ script: [
ScriptNum.encode(1), Opcode.fromInt(1),
Script.opcodes.OP_CHECKSEQUENCEVERIFY Opcode.fromSymbol('checksequenceverify')
], ],
value: 10000 value: 10000
}); });
@ -419,8 +419,8 @@ describe('Chain', function() {
spend.addOutput({ spend.addOutput({
script: [ script: [
ScriptNum.encode(2), Opcode.fromInt(2),
Script.opcodes.OP_CHECKSEQUENCEVERIFY Opcode.fromSymbol('checksequenceverify')
], ],
value: 10000 value: 10000
}); });
@ -444,8 +444,8 @@ describe('Chain', function() {
spend.addOutput({ spend.addOutput({
script: [ script: [
ScriptNum.encode(1), Opcode.fromInt(1),
Script.opcodes.OP_CHECKSEQUENCEVERIFY Opcode.fromSymbol('checksequenceverify')
], ],
value: 1 * 1e8 value: 1 * 1e8
}); });
@ -479,8 +479,8 @@ describe('Chain', function() {
spend.addOutput({ spend.addOutput({
script: [ script: [
ScriptNum.encode(2), Opcode.fromInt(2),
Script.opcodes.OP_CHECKSEQUENCEVERIFY Opcode.fromSymbol('checksequenceverify')
], ],
value: 1 * 1e8 value: 1 * 1e8
}); });
@ -547,7 +547,6 @@ describe('Chain', function() {
const tx = block.txs[0]; const tx = block.txs[0];
const input = tx.inputs[0]; const input = tx.inputs[0];
input.witness.set(0, Buffer.allocUnsafe(33)); input.witness.set(0, Buffer.allocUnsafe(33));
input.witness.compile();
block.refresh(true); block.refresh(true);
assert.strictEqual(await addBlock(block), 'bad-witness-nonce-size'); assert.strictEqual(await addBlock(block), 'bad-witness-nonce-size');
}); });
@ -557,7 +556,6 @@ describe('Chain', function() {
const tx = block.txs[0]; const tx = block.txs[0];
const input = tx.inputs[0]; const input = tx.inputs[0];
input.witness.set(0, encoding.ONE_HASH); input.witness.set(0, encoding.ONE_HASH);
input.witness.compile();
block.refresh(true); block.refresh(true);
assert.strictEqual(await addBlock(block), 'bad-witness-merkle-match'); assert.strictEqual(await addBlock(block), 'bad-witness-merkle-match');
}); });
@ -570,9 +568,9 @@ describe('Chain', function() {
assert(output.script.isCommitment()); assert(output.script.isCommitment());
const commit = Buffer.from(output.script.get(1)); const commit = Buffer.from(output.script.getData(1));
commit.fill(0, 10); commit.fill(0, 10);
output.script.set(1, commit); output.script.setData(1, commit);
output.script.compile(); output.script.compile();
block.refresh(true); block.refresh(true);
@ -787,13 +785,14 @@ describe('Chain', function() {
const flags = common.flags.DEFAULT_FLAGS & ~common.flags.VERIFY_POW; const flags = common.flags.DEFAULT_FLAGS & ~common.flags.VERIFY_POW;
const redeem = new Script(); const redeem = new Script();
redeem.push(new ScriptNum(20)); redeem.pushInt(20);
for (let i = 0; i < 20; i++) 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(); redeem.compile();
const script = Script.fromScripthash(redeem.hash160()); const script = Script.fromScripthash(redeem.hash160());
@ -828,13 +827,15 @@ describe('Chain', function() {
const job = await cpu.createJob(); const job = await cpu.createJob();
const script = new Script(); const script = new Script();
script.push(new ScriptNum(20));
script.pushInt(20);
for (let i = 0; i < 20; i++) 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(); script.compile();
for (let i = start; i <= end; i++) { for (let i = start; i <= end; i++) {
@ -848,7 +849,7 @@ describe('Chain', function() {
for (let j = 2; j < cb.outputs.length; j++) { for (let j = 2; j < cb.outputs.length; j++) {
mtx.addTX(cb, 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); 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); 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 // balance: 51000
wallet.sign(t1); wallet.sign(t1);
@ -126,8 +126,9 @@ describe('Mempool', function() {
wallet.template(fake); wallet.template(fake);
// Fake signature // Fake signature
fake.inputs[0].script.set(0, encoding.ZERO_SIG); const input = fake.inputs[0];
fake.inputs[0].script.compile(); input.script.setData(0, encoding.ZERO_SIG);
input.script.compile();
// balance: 11000 // balance: 11000
{ {
@ -188,7 +189,7 @@ describe('Mempool', function() {
chain.tip.height = 200; chain.tip.height = 200;
const sig = tx.signature(0, prev, 70000, key.privateKey, ALL, 0); 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()); await mempool.addTX(tx.toTX());
chain.tip.height = 0; chain.tip.height = 0;
@ -209,7 +210,7 @@ describe('Mempool', function() {
chain.tip.height = 200 - 1; chain.tip.height = 200 - 1;
const sig = tx.signature(0, prev, 70000, key.privateKey, ALL, 0); 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; let err;
try { try {
@ -268,7 +269,7 @@ describe('Mempool', function() {
tx.addCoin(dummyInput(prev, prevHash)); tx.addCoin(dummyInput(prev, prevHash));
const sig = tx.signature(0, prev, 70000, key.privateKey, ALL, 0); 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)); tx.inputs[0].witness.push(Buffer.alloc(0));
let err; let err;

View File

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

View File

@ -7,10 +7,10 @@ const assert = require('./util/assert');
const Script = require('../lib/script/script'); const Script = require('../lib/script/script');
const Witness = require('../lib/script/witness'); const Witness = require('../lib/script/witness');
const Stack = require('../lib/script/stack'); const Stack = require('../lib/script/stack');
const Opcode = require('../lib/script/opcode');
const TX = require('../lib/primitives/tx'); const TX = require('../lib/primitives/tx');
const util = require('../lib/utils/util'); const util = require('../lib/utils/util');
const encoding = require('../lib/utils/encoding'); const encoding = require('../lib/utils/encoding');
const opcodes = Script.opcodes;
const scripts = require('./data/script-tests.json'); const scripts = require('./data/script-tests.json');
@ -18,7 +18,7 @@ function isSuccess(stack) {
if (stack.length === 0) if (stack.length === 0)
return false; return false;
if (!stack.bool(-1)) if (!stack.getBool(-1))
return false; return false;
return true; return true;
@ -67,34 +67,6 @@ function parseScriptTest(data) {
} }
describe('Script', function() { 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', () => { it('should recognize a P2SH output', () => {
const hex = 'a91419a7d869032368fd1f1e26e5e73a4ad0e474960e87'; const hex = 'a91419a7d869032368fd1f1e26e5e73a4ad0e474960e87';
const decoded = Script.fromRaw(hex, 'hex'); const decoded = Script.fromRaw(hex, 'hex');
@ -110,17 +82,20 @@ describe('Script', function() {
it('should handle if statements correctly', () => { 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([ const output = new Script([
opcodes.OP_2, Opcode.fromInt(2),
opcodes.OP_EQUAL, Opcode.fromSymbol('equal'),
opcodes.OP_IF, Opcode.fromSymbol('if'),
opcodes.OP_3, Opcode.fromInt(3),
opcodes.OP_ELSE, Opcode.fromSymbol('else'),
opcodes.OP_4, Opcode.fromInt(4),
opcodes.OP_ENDIF, Opcode.fromSymbol('endif'),
opcodes.OP_5 Opcode.fromInt(5)
]); ]);
const stack = new Stack(); 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([ const output = new Script([
opcodes.OP_9, Opcode.fromInt(9),
opcodes.OP_EQUAL, Opcode.fromSymbol('equal'),
opcodes.OP_IF, Opcode.fromSymbol('if'),
opcodes.OP_3, Opcode.fromInt(3),
opcodes.OP_ELSE, Opcode.fromSymbol('else'),
opcodes.OP_4, Opcode.fromInt(4),
opcodes.OP_ENDIF, Opcode.fromSymbol('endif'),
opcodes.OP_5 Opcode.fromInt(5)
]); ]);
const stack = new Stack(); 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([ const output = new Script([
opcodes.OP_2, Opcode.fromInt(2),
opcodes.OP_EQUAL, Opcode.fromSymbol('equal'),
opcodes.OP_IF, Opcode.fromSymbol('if'),
opcodes.OP_3, Opcode.fromInt(3),
opcodes.OP_ENDIF, Opcode.fromSymbol('endif'),
opcodes.OP_5 Opcode.fromInt(5)
]); ]);
const stack = new Stack(); 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([ const output = new Script([
opcodes.OP_9, Opcode.fromInt(9),
opcodes.OP_EQUAL, Opcode.fromSymbol('equal'),
opcodes.OP_IF, Opcode.fromSymbol('if'),
opcodes.OP_3, Opcode.fromInt(3),
opcodes.OP_ENDIF, Opcode.fromSymbol('endif'),
opcodes.OP_5 Opcode.fromInt(5)
]); ]);
const stack = new Stack(); 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([ const output = new Script([
opcodes.OP_9, Opcode.fromInt(9),
opcodes.OP_EQUAL, Opcode.fromSymbol('equal'),
opcodes.OP_NOTIF, Opcode.fromSymbol('notif'),
opcodes.OP_3, Opcode.fromInt(3),
opcodes.OP_ENDIF, Opcode.fromSymbol('endif'),
opcodes.OP_5 Opcode.fromInt(5)
]); ]);
const stack = new Stack(); const stack = new Stack();
@ -216,15 +203,15 @@ describe('Script', function() {
it('should handle CScriptNums correctly', () => { it('should handle CScriptNums correctly', () => {
const input = new Script([ const input = new Script([
Buffer.from('ffffff7f', 'hex'), Opcode.fromString('ffffff7f', 'hex'),
opcodes.OP_NEGATE, Opcode.fromSymbol('negate'),
opcodes.OP_DUP, Opcode.fromSymbol('dup'),
opcodes.OP_ADD Opcode.fromSymbol('add')
]); ]);
const output = new Script([ const output = new Script([
Buffer.from('feffffff80', 'hex'), Opcode.fromString('feffffff80', 'hex'),
opcodes.OP_EQUAL Opcode.fromSymbol('equal')
]); ]);
const stack = new Stack(); const stack = new Stack();
@ -237,15 +224,15 @@ describe('Script', function() {
it('should handle CScriptNums correctly', () => { it('should handle CScriptNums correctly', () => {
const input = new Script([ const input = new Script([
opcodes.OP_11, Opcode.fromInt(11),
opcodes.OP_10, Opcode.fromInt(10),
opcodes.OP_1, Opcode.fromInt(1),
opcodes.OP_ADD Opcode.fromSymbol('add')
]); ]);
const output = new Script([ const output = new Script([
opcodes.OP_NUMNOTEQUAL, Opcode.fromSymbol('numnotequal'),
opcodes.OP_NOT Opcode.fromSymbol('not')
]); ]);
const stack = new Stack(); const stack = new Stack();
@ -258,19 +245,19 @@ describe('Script', function() {
it('should handle OP_ROLL correctly', () => { it('should handle OP_ROLL correctly', () => {
const input = new Script([ const input = new Script([
Buffer.from([0x16]), Opcode.fromInt(0x16),
Buffer.from([0x15]), Opcode.fromInt(0x15),
Buffer.from([0x14]) Opcode.fromInt(0x14)
]); ]);
const output = new Script([ const output = new Script([
opcodes.OP_0, Opcode.fromInt(0),
opcodes.OP_ROLL, Opcode.fromSymbol('roll'),
Buffer.from([0x14]), Opcode.fromInt(0x14),
opcodes.OP_EQUALVERIFY, Opcode.fromSymbol('equalverify'),
opcodes.OP_DEPTH, Opcode.fromSymbol('depth'),
opcodes.OP_2, Opcode.fromInt(2),
opcodes.OP_EQUAL Opcode.fromSymbol('equal')
]); ]);
const stack = new Stack(); const stack = new Stack();
@ -302,7 +289,10 @@ describe('Script', function() {
hash: encoding.NULL_HASH, hash: encoding.NULL_HASH,
index: 0xffffffff index: 0xffffffff
}, },
script: [opcodes.OP_0, opcodes.OP_0], script: [
Opcode.fromOp(0),
Opcode.fromOp(0)
],
witness: [], witness: [],
sequence: 0xffffffff sequence: 0xffffffff
}], }],

View File

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

View File

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