Browse Source

Merge pull request #800 from maraoz/add/ScriptInterpreter/devguide

add Interpreter docs and refactor a bit
patch-2
Braydon Fuller 10 years ago
parent
commit
2ba7a39496
  1. 36
      docs/Script.md
  2. 2
      lib/crypto/signature.js
  3. 322
      lib/script/interpreter.js
  4. 19
      lib/script/script.js
  5. 8
      test/crypto/signature.js
  6. 106
      test/script/interpreter.js
  7. 2
      test/script/script.js

36
docs/Script.md

@ -110,3 +110,39 @@ s.isPublicKeyHashOut() // false
s.isScriptHashOut() // false s.isScriptHashOut() // false
s.isMultisigOut() // true s.isMultisigOut() // true
``` ```
## Script interpreting and validation
To validate a transaction, the bitcoin network validates all of its inputs and outputs. To validate an input, the input's script is concatenated with the referenced output script, and the result is executed. If at the end of execution the stack contains a 'true' value, then the transaction is valid.
You can do this in `bitcore` by using the `Interpreter` class. The entry point (and probably the only interface you'll need for most applications) is the method `Interpreter#verify()`.
You can use it like this:
```
var inputScript = Script('OP_1');
var outputScript = Script('OP_15 OP_ADD OP_16 OP_EQUAL');
var verified = Interpreter().verify(inputScript, outputScript);
// verified will be true
```
Note that `verify` expects two scripts: one is the input script (scriptSig) and the other is the output script (scriptPubkey). This is because different conditions are checked for each.
It also accepts some optional parameters, assuming defaults if not provided:
```
// first we create a transaction
var tx = new Transaction()
.from(utxo)
.to(toAddress, 100000)
.sign(privateKey);
// we then extract the signature from the first input
var inputIndex = 0;
var signature = tx.getSignatures(privateKey)[inputIndex].signature;
var scriptSig = Script.buildPublicKeyHashIn(publicKey, signature);
var flags = Interpreter.SCRIPT_VERIFY_P2SH | Interpreter.SCRIPT_VERIFY_STRICTENC;
var verified = Interpreter().verify(scriptSig, scriptPubkey, tx, inputIndex, flags);
```

2
lib/crypto/signature.js

@ -173,7 +173,7 @@ Signature.prototype.toCompact = function(i, compressed) {
return Buffer.concat([b1, b2, b3]); return Buffer.concat([b1, b2, b3]);
}; };
Signature.prototype.toDER = function() { Signature.prototype.toBuffer = Signature.prototype.toDER = function() {
var rnbuf = this.r.toBuffer(); var rnbuf = this.r.toBuffer();
var snbuf = this.s.toBuffer(); var snbuf = this.s.toBuffer();

322
lib/script/interpreter.js

@ -19,11 +19,11 @@ var PublicKey = require('../publickey');
* "true" value, then the transaction is valid. * "true" value, then the transaction is valid.
* *
* The primary way to use this class is via the verify function. * The primary way to use this class is via the verify function.
* e.g., ScriptInterpreter().verify( ... ); * e.g., Interpreter().verify( ... );
*/ */
var ScriptInterpreter = function ScriptInterpreter(obj) { var Interpreter = function Interpreter(obj) {
if (!(this instanceof ScriptInterpreter)) { if (!(this instanceof Interpreter)) {
return new ScriptInterpreter(obj); return new Interpreter(obj);
} }
if (obj) { if (obj) {
this.initialize(); this.initialize();
@ -33,9 +33,130 @@ var ScriptInterpreter = function ScriptInterpreter(obj) {
} }
}; };
module.exports = ScriptInterpreter; /**
* Verifies a Script by executing it and returns true if it is valid.
* This function needs to be provided with the scriptSig and the scriptPubkey
* separately.
* @param {Script} scriptSig - the script's first part (corresponding to the tx input)
* @param {Script} scriptPubkey - the script's last part (corresponding to the tx output)
* @param {Transaction} [tx] - the Transaction containing the scriptSig in one input (used
* to check signature validity for some opcodes like OP_CHECKSIG)
* @param {number} nin - index of the transaction input containing the scriptSig verified.
* @param {number} flags - evaluation flags. See Interpreter.SCRIPT_* constants
*
* Translated from bitcoind's VerifyScript
*/
Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags) {
var Transaction = require('../transaction');
if (_.isUndefined(tx)) {
tx = new Transaction();
}
if (_.isUndefined(nin)) {
nin = 0;
}
if (_.isUndefined(flags)) {
flags = 0;
}
this.set({
script: scriptSig,
tx: tx,
nin: nin,
flags: flags
});
var stackCopy;
if ((flags & Interpreter.SCRIPT_VERIFY_SIGPUSHONLY) !== 0 && !scriptSig.isPushOnly()) {
this.errstr = 'SCRIPT_ERR_SIG_PUSHONLY';
return false;
}
// evaluate scriptSig
if (!this.evaluate()) {
return false;
}
if (flags & Interpreter.SCRIPT_VERIFY_P2SH) {
stackCopy = this.stack.slice();
}
var stack = this.stack;
this.initialize();
this.set({
script: scriptPubkey,
stack: stack,
tx: tx,
nin: nin,
flags: flags
});
// evaluate scriptPubkey
if (!this.evaluate()) {
return false;
}
if (this.stack.length === 0) {
this.errstr = 'SCRIPT_ERR_EVAL_FALSE_NO_RESULT';
return false;
}
var buf = this.stack[this.stack.length - 1];
if (!Interpreter.castToBool(buf)) {
this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_STACK';
return false;
}
// Additional validation for spend-to-script-hash transactions:
if ((flags & Interpreter.SCRIPT_VERIFY_P2SH) && scriptPubkey.isScriptHashOut()) {
// scriptSig must be literals-only or validation fails
if (!scriptSig.isPushOnly()) {
this.errstr = 'SCRIPT_ERR_SIG_PUSHONLY';
return false;
}
// stackCopy cannot be empty here, because if it was the
// P2SH HASH <> EQUAL scriptPubKey would be evaluated with
// an empty stack and the EvalScript above would return false.
if (stackCopy.length === 0) {
throw new Error('internal error - stack copy empty');
}
var redeemScriptSerialized = stackCopy[stackCopy.length - 1];
var redeemScript = Script.fromBuffer(redeemScriptSerialized);
stackCopy.pop();
this.initialize();
this.set({
script: redeemScript,
stack: stackCopy,
tx: tx,
nin: nin,
flags: flags
});
// evaluate redeemScript
if (!this.evaluate()) {
return false;
}
if (stackCopy.length === 0) {
this.errstr = 'SCRIPT_ERR_EVAL_FALSE_NO_P2SH_STACK';
return false;
}
if (!Interpreter.castToBool(stackCopy[stackCopy.length - 1])) {
this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_P2SH_STACK';
return false;
} else {
return true;
}
}
return true;
};
ScriptInterpreter.prototype.initialize = function(obj) { module.exports = Interpreter;
Interpreter.prototype.initialize = function(obj) {
this.stack = []; this.stack = [];
this.altstack = []; this.altstack = [];
this.pc = 0; this.pc = 0;
@ -46,7 +167,7 @@ ScriptInterpreter.prototype.initialize = function(obj) {
this.flags = 0; this.flags = 0;
}; };
ScriptInterpreter.prototype.set = function(obj) { Interpreter.prototype.set = function(obj) {
this.script = obj.script || this.script; this.script = obj.script || this.script;
this.tx = obj.tx || this.tx; this.tx = obj.tx || this.tx;
this.nin = typeof obj.nin !== 'undefined' ? obj.nin : this.nin; this.nin = typeof obj.nin !== 'undefined' ? obj.nin : this.nin;
@ -60,42 +181,42 @@ ScriptInterpreter.prototype.set = function(obj) {
this.flags = typeof obj.flags !== 'undefined' ? obj.flags : this.flags; this.flags = typeof obj.flags !== 'undefined' ? obj.flags : this.flags;
}; };
ScriptInterpreter.true = new Buffer([1]); Interpreter.true = new Buffer([1]);
ScriptInterpreter.false = new Buffer([]); Interpreter.false = new Buffer([]);
ScriptInterpreter.MAX_SCRIPT_ELEMENT_SIZE = 520; Interpreter.MAX_SCRIPT_ELEMENT_SIZE = 520;
// flags taken from bitcoind // flags taken from bitcoind
// bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104 // bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104
ScriptInterpreter.SCRIPT_VERIFY_NONE = 0; Interpreter.SCRIPT_VERIFY_NONE = 0;
// Evaluate P2SH subscripts (softfork safe, BIP16). // Evaluate P2SH subscripts (softfork safe, BIP16).
ScriptInterpreter.SCRIPT_VERIFY_P2SH = (1 << 0); Interpreter.SCRIPT_VERIFY_P2SH = (1 << 0);
// Passing a non-strict-DER signature or one with undefined hashtype to a checksig operation causes script failure. // Passing a non-strict-DER signature or one with undefined hashtype to a checksig operation causes script failure.
// Passing a pubkey that is not (0x04 + 64 bytes) or (0x02 or 0x03 + 32 bytes) to checksig causes that pubkey to be // Passing a pubkey that is not (0x04 + 64 bytes) or (0x02 or 0x03 + 32 bytes) to checksig causes that pubkey to be
// skipped (not softfork safe: this flag can widen the validity of OP_CHECKSIG OP_NOT). // skipped (not softfork safe: this flag can widen the validity of OP_CHECKSIG OP_NOT).
ScriptInterpreter.SCRIPT_VERIFY_STRICTENC = (1 << 1); Interpreter.SCRIPT_VERIFY_STRICTENC = (1 << 1);
// Passing a non-strict-DER signature to a checksig operation causes script failure (softfork safe, BIP62 rule 1) // Passing a non-strict-DER signature to a checksig operation causes script failure (softfork safe, BIP62 rule 1)
ScriptInterpreter.SCRIPT_VERIFY_DERSIG = (1 << 2); Interpreter.SCRIPT_VERIFY_DERSIG = (1 << 2);
// Passing a non-strict-DER signature or one with S > order/2 to a checksig operation causes script failure // Passing a non-strict-DER signature or one with S > order/2 to a checksig operation causes script failure
// (softfork safe, BIP62 rule 5). // (softfork safe, BIP62 rule 5).
ScriptInterpreter.SCRIPT_VERIFY_LOW_S = (1 << 3); Interpreter.SCRIPT_VERIFY_LOW_S = (1 << 3);
// verify dummy stack item consumed by CHECKMULTISIG is of zero-length (softfork safe, BIP62 rule 7). // verify dummy stack item consumed by CHECKMULTISIG is of zero-length (softfork safe, BIP62 rule 7).
ScriptInterpreter.SCRIPT_VERIFY_NULLDUMMY = (1 << 4); Interpreter.SCRIPT_VERIFY_NULLDUMMY = (1 << 4);
// Using a non-push operator in the scriptSig causes script failure (softfork safe, BIP62 rule 2). // Using a non-push operator in the scriptSig causes script failure (softfork safe, BIP62 rule 2).
ScriptInterpreter.SCRIPT_VERIFY_SIGPUSHONLY = (1 << 5); Interpreter.SCRIPT_VERIFY_SIGPUSHONLY = (1 << 5);
// Require minimal encodings for all push operations (OP_0... OP_16, OP_1NEGATE where possible, direct // Require minimal encodings for all push operations (OP_0... OP_16, OP_1NEGATE where possible, direct
// pushes up to 75 bytes, OP_PUSHDATA up to 255 bytes, OP_PUSHDATA2 for anything larger). Evaluating // pushes up to 75 bytes, OP_PUSHDATA up to 255 bytes, OP_PUSHDATA2 for anything larger). Evaluating
// any other push causes the script to fail (BIP62 rule 3). // any other push causes the script to fail (BIP62 rule 3).
// In addition, whenever a stack element is interpreted as a number, it must be of minimal length (BIP62 rule 4). // In addition, whenever a stack element is interpreted as a number, it must be of minimal length (BIP62 rule 4).
// (softfork safe) // (softfork safe)
ScriptInterpreter.SCRIPT_VERIFY_MINIMALDATA = (1 << 6); Interpreter.SCRIPT_VERIFY_MINIMALDATA = (1 << 6);
// Discourage use of NOPs reserved for upgrades (NOP1-10) // Discourage use of NOPs reserved for upgrades (NOP1-10)
// //
@ -105,9 +226,9 @@ ScriptInterpreter.SCRIPT_VERIFY_MINIMALDATA = (1 << 6);
// discouraged NOPs fails the script. This verification flag will never be // discouraged NOPs fails the script. This verification flag will never be
// a mandatory flag applied to scripts in a block. NOPs that are not // a mandatory flag applied to scripts in a block. NOPs that are not
// executed, e.g. within an unexecuted IF ENDIF block, are *not* rejected. // executed, e.g. within an unexecuted IF ENDIF block, are *not* rejected.
ScriptInterpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = (1 << 7); Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = (1 << 7);
ScriptInterpreter.castToBool = function(buf) { Interpreter.castToBool = function(buf) {
for (var i = 0; i < buf.length; i++) { for (var i = 0; i < buf.length; i++) {
if (buf[i] !== 0) { if (buf[i] !== 0) {
// can be negative zero // can be negative zero
@ -123,18 +244,18 @@ ScriptInterpreter.castToBool = function(buf) {
/** /**
* Translated from bitcoind's CheckSignatureEncoding * Translated from bitcoind's CheckSignatureEncoding
*/ */
ScriptInterpreter.prototype.checkSignatureEncoding = function(buf) { Interpreter.prototype.checkSignatureEncoding = function(buf) {
var sig; var sig;
if ((this.flags & (ScriptInterpreter.SCRIPT_VERIFY_DERSIG | ScriptInterpreter.SCRIPT_VERIFY_LOW_S | ScriptInterpreter.SCRIPT_VERIFY_STRICTENC)) !== 0 && !Signature.isTxDER(buf)) { if ((this.flags & (Interpreter.SCRIPT_VERIFY_DERSIG | Interpreter.SCRIPT_VERIFY_LOW_S | Interpreter.SCRIPT_VERIFY_STRICTENC)) !== 0 && !Signature.isTxDER(buf)) {
this.errstr = 'SCRIPT_ERR_SIG_DER_INVALID_FORMAT'; this.errstr = 'SCRIPT_ERR_SIG_DER_INVALID_FORMAT';
return false; return false;
} else if ((this.flags & ScriptInterpreter.SCRIPT_VERIFY_LOW_S) !== 0) { } else if ((this.flags & Interpreter.SCRIPT_VERIFY_LOW_S) !== 0) {
sig = Signature.fromTxFormat(buf); sig = Signature.fromTxFormat(buf);
if (!sig.hasLowS()) { if (!sig.hasLowS()) {
this.errstr = 'SCRIPT_ERR_SIG_DER_HIGH_S'; this.errstr = 'SCRIPT_ERR_SIG_DER_HIGH_S';
return false; return false;
} }
} else if ((this.flags & ScriptInterpreter.SCRIPT_VERIFY_STRICTENC) !== 0) { } else if ((this.flags & Interpreter.SCRIPT_VERIFY_STRICTENC) !== 0) {
sig = Signature.fromTxFormat(buf); sig = Signature.fromTxFormat(buf);
if (!sig.hasDefinedHashtype()) { if (!sig.hasDefinedHashtype()) {
this.errstr = 'SCRIPT_ERR_SIG_HASHTYPE'; this.errstr = 'SCRIPT_ERR_SIG_HASHTYPE';
@ -147,8 +268,8 @@ ScriptInterpreter.prototype.checkSignatureEncoding = function(buf) {
/** /**
* Translated from bitcoind's CheckPubKeyEncoding * Translated from bitcoind's CheckPubKeyEncoding
*/ */
ScriptInterpreter.prototype.checkPubkeyEncoding = function(buf) { Interpreter.prototype.checkPubkeyEncoding = function(buf) {
if ((this.flags & ScriptInterpreter.SCRIPT_VERIFY_STRICTENC) !== 0 && !PublicKey.isValid(buf)) { if ((this.flags & Interpreter.SCRIPT_VERIFY_STRICTENC) !== 0 && !PublicKey.isValid(buf)) {
this.errstr = 'SCRIPT_ERR_PUBKEYTYPE'; this.errstr = 'SCRIPT_ERR_PUBKEYTYPE';
return false; return false;
} }
@ -157,10 +278,10 @@ ScriptInterpreter.prototype.checkPubkeyEncoding = function(buf) {
/** /**
* Based on bitcoind's EvalScript function, with the inner loop moved to * Based on bitcoind's EvalScript function, with the inner loop moved to
* ScriptInterpreter.prototype.step() * Interpreter.prototype.step()
* bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104 * bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104
*/ */
ScriptInterpreter.prototype.evaluate = function() { Interpreter.prototype.evaluate = function() {
if (this.script.toBuffer().length > 10000) { if (this.script.toBuffer().length > 10000) {
this.errstr = 'SCRIPT_ERR_SCRIPT_SIZE'; this.errstr = 'SCRIPT_ERR_SCRIPT_SIZE';
return false; return false;
@ -196,9 +317,9 @@ ScriptInterpreter.prototype.evaluate = function() {
* Based on the inner loop of bitcoind's EvalScript function * Based on the inner loop of bitcoind's EvalScript function
* bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104 * bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104
*/ */
ScriptInterpreter.prototype.step = function() { Interpreter.prototype.step = function() {
var fRequireMinimal = (this.flags & ScriptInterpreter.SCRIPT_VERIFY_MINIMALDATA) !== 0; var fRequireMinimal = (this.flags & Interpreter.SCRIPT_VERIFY_MINIMALDATA) !== 0;
//bool fExec = !count(vfExec.begin(), vfExec.end(), false); //bool fExec = !count(vfExec.begin(), vfExec.end(), false);
var fExec = (this.vfExec.indexOf(false) === -1); var fExec = (this.vfExec.indexOf(false) === -1);
@ -212,7 +333,7 @@ ScriptInterpreter.prototype.step = function() {
this.errstr = 'SCRIPT_ERR_UNDEFINED_OPCODE'; this.errstr = 'SCRIPT_ERR_UNDEFINED_OPCODE';
return false; return false;
} }
if (chunk.buf && chunk.buf.length > ScriptInterpreter.MAX_SCRIPT_ELEMENT_SIZE) { if (chunk.buf && chunk.buf.length > Interpreter.MAX_SCRIPT_ELEMENT_SIZE) {
this.errstr = 'SCRIPT_ERR_PUSH_SIZE'; this.errstr = 'SCRIPT_ERR_PUSH_SIZE';
return false; return false;
} }
@ -249,7 +370,7 @@ ScriptInterpreter.prototype.step = function() {
return false; return false;
} }
if (!chunk.buf) { if (!chunk.buf) {
this.stack.push(ScriptInterpreter.false); this.stack.push(Interpreter.false);
} else if (chunk.len !== chunk.buf.length) { } else if (chunk.len !== chunk.buf.length) {
throw new Error('Length of push value not equal to length of data'); throw new Error('Length of push value not equal to length of data');
} else { } else {
@ -304,7 +425,7 @@ ScriptInterpreter.prototype.step = function() {
case Opcode.OP_NOP9: case Opcode.OP_NOP9:
case Opcode.OP_NOP10: case Opcode.OP_NOP10:
{ {
if (this.flags & ScriptInterpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { if (this.flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) {
this.errstr = 'SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS'; this.errstr = 'SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS';
return false; return false;
} }
@ -323,7 +444,7 @@ ScriptInterpreter.prototype.step = function() {
return false; return false;
} }
var buf = this.stack.pop(); var buf = this.stack.pop();
fValue = ScriptInterpreter.castToBool(buf); fValue = Interpreter.castToBool(buf);
if (opcodenum === Opcode.OP_NOTIF) if (opcodenum === Opcode.OP_NOTIF)
fValue = !fValue; fValue = !fValue;
} }
@ -360,7 +481,7 @@ ScriptInterpreter.prototype.step = function() {
return false; return false;
} }
var buf = this.stack[this.stack.length - 1]; var buf = this.stack[this.stack.length - 1];
var fValue = ScriptInterpreter.castToBool(buf); var fValue = Interpreter.castToBool(buf);
if (fValue) if (fValue)
this.stack.pop(); this.stack.pop();
else { else {
@ -491,7 +612,7 @@ ScriptInterpreter.prototype.step = function() {
return false; return false;
} }
var buf = this.stack[this.stack.length - 1]; var buf = this.stack[this.stack.length - 1];
var fValue = ScriptInterpreter.castToBool(buf); var fValue = Interpreter.castToBool(buf);
if (fValue) if (fValue)
this.stack.push(buf); this.stack.push(buf);
} }
@ -652,7 +773,7 @@ ScriptInterpreter.prototype.step = function() {
// fEqual = !fEqual; // fEqual = !fEqual;
this.stack.pop(); this.stack.pop();
this.stack.pop(); this.stack.pop();
this.stack.push(fEqual ? ScriptInterpreter.true : ScriptInterpreter.false); this.stack.push(fEqual ? Interpreter.true : Interpreter.false);
if (opcodenum === Opcode.OP_EQUALVERIFY) { if (opcodenum === Opcode.OP_EQUALVERIFY) {
if (fEqual) if (fEqual)
this.stack.pop(); this.stack.pop();
@ -790,7 +911,7 @@ ScriptInterpreter.prototype.step = function() {
if (opcodenum === Opcode.OP_NUMEQUALVERIFY) { if (opcodenum === Opcode.OP_NUMEQUALVERIFY) {
// if (CastToBool(stacktop(-1))) // if (CastToBool(stacktop(-1)))
if (ScriptInterpreter.castToBool(this.stack[this.stack.length - 1])) if (Interpreter.castToBool(this.stack[this.stack.length - 1]))
this.stack.pop(); this.stack.pop();
else { else {
this.errstr = 'SCRIPT_ERR_NUMEQUALVERIFY'; this.errstr = 'SCRIPT_ERR_NUMEQUALVERIFY';
@ -815,7 +936,7 @@ ScriptInterpreter.prototype.step = function() {
this.stack.pop(); this.stack.pop();
this.stack.pop(); this.stack.pop();
this.stack.pop(); this.stack.pop();
this.stack.push(fValue ? ScriptInterpreter.true : ScriptInterpreter.false); this.stack.push(fValue ? Interpreter.true : Interpreter.false);
} }
break; break;
@ -898,7 +1019,7 @@ ScriptInterpreter.prototype.step = function() {
this.stack.pop(); this.stack.pop();
this.stack.pop(); this.stack.pop();
// stack.push_back(fSuccess ? vchTrue : vchFalse); // stack.push_back(fSuccess ? vchTrue : vchFalse);
this.stack.push(fSuccess ? ScriptInterpreter.true : ScriptInterpreter.false); this.stack.push(fSuccess ? Interpreter.true : Interpreter.false);
if (opcodenum === Opcode.OP_CHECKSIGVERIFY) { if (opcodenum === Opcode.OP_CHECKSIGVERIFY) {
if (fSuccess) { if (fSuccess) {
this.stack.pop(); this.stack.pop();
@ -1013,13 +1134,13 @@ ScriptInterpreter.prototype.step = function() {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false; return false;
} }
if ((this.flags & ScriptInterpreter.SCRIPT_VERIFY_NULLDUMMY) && this.stack[this.stack.length - 1].length) { if ((this.flags & Interpreter.SCRIPT_VERIFY_NULLDUMMY) && this.stack[this.stack.length - 1].length) {
this.errstr = 'SCRIPT_ERR_SIG_NULLDUMMY'; this.errstr = 'SCRIPT_ERR_SIG_NULLDUMMY';
return false; return false;
} }
this.stack.pop(); this.stack.pop();
this.stack.push(fSuccess ? ScriptInterpreter.true : ScriptInterpreter.false); this.stack.push(fSuccess ? Interpreter.true : Interpreter.false);
if (opcodenum === Opcode.OP_CHECKMULTISIGVERIFY) { if (opcodenum === Opcode.OP_CHECKMULTISIGVERIFY) {
if (fSuccess) if (fSuccess)
@ -1038,121 +1159,6 @@ ScriptInterpreter.prototype.step = function() {
} }
} }
return true;
}
/**
* Verifies a Script by executing it and returns true if it is valid.
* This function needs to be provided with the scriptSig and the scriptPubkey
* separately.
* @param {Script} scriptSig - the script's first part (corresponding to the tx input)
* @param {Script} scriptPubkey - the script's last part (corresponding to the tx output)
* @param {Transaction} [tx] - the Transaction containing the scriptSig in one input (used
* to check signature validity for some opcodes like OP_CHECKSIG)
* @param {number} nin - index of the transaction input containing the scriptSig verified.
* @param {number} flags - evaluation flags. See ScriptInterpreter.SCRIPT_* constants
*
* Translated from bitcoind's VerifyScript
*/
ScriptInterpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags) {
var Transaction = require('../transaction');
if (_.isUndefined(tx)) {
tx = new Transaction();
}
if (_.isUndefined(nin)) {
nin = 0;
}
if (_.isUndefined(flags)) {
flags = 0;
}
this.set({
script: scriptSig,
tx: tx,
nin: nin,
flags: flags
});
if ((flags & ScriptInterpreter.SCRIPT_VERIFY_SIGPUSHONLY) != 0 && !scriptSig.isPushOnly()) {
this.errstr = 'SCRIPT_ERR_SIG_PUSHONLY';
return false;
}
// evaluate scriptSig
if (!this.evaluate()) {
return false;
}
if (flags & ScriptInterpreter.SCRIPT_VERIFY_P2SH)
var stackCopy = this.stack.slice();
var stack = this.stack;
this.initialize();
this.set({
script: scriptPubkey,
stack: stack,
tx: tx,
nin: nin,
flags: flags
});
// evaluate scriptPubkey
if (!this.evaluate())
return false;
if (this.stack.length === 0) {
this.errstr = 'SCRIPT_ERR_EVAL_FALSE_NO_RESULT';
return false;
}
var buf = this.stack[this.stack.length - 1];
if (!ScriptInterpreter.castToBool(buf)) {
this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_STACK';
return false;
}
// Additional validation for spend-to-script-hash transactions:
if ((flags & ScriptInterpreter.SCRIPT_VERIFY_P2SH) && scriptPubkey.isScriptHashOut()) {
// scriptSig must be literals-only or validation fails
if (!scriptSig.isPushOnly()) {
this.errstr = 'SCRIPT_ERR_SIG_PUSHONLY';
return false;
}
// stackCopy cannot be empty here, because if it was the
// P2SH HASH <> EQUAL scriptPubKey would be evaluated with
// an empty stack and the EvalScript above would return false.
if (stackCopy.length === 0)
throw new Error('internal error - stack copy empty');
var redeemScriptSerialized = stackCopy[stackCopy.length - 1];
var redeemScript = Script.fromBuffer(redeemScriptSerialized);
stackCopy.pop();
this.initialize();
this.set({
script: redeemScript,
stack: stackCopy,
tx: tx,
nin: nin,
flags: flags
});
// evaluate redeemScript
if (!this.evaluate())
return false;
if (stackCopy.length === 0) {
this.errstr = 'SCRIPT_ERR_EVAL_FALSE_NO_P2SH_STACK';
return false;
}
if (!ScriptInterpreter.castToBool(stackCopy[stackCopy.length - 1])) {
this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_P2SH_STACK';
return false;
} else {
return true;
}
}
return true; return true;
}; };

19
lib/script/script.js

@ -555,6 +555,9 @@ Script.buildMultisigOut = function(publicKeys, threshold, opts) {
* @returns Script * @returns Script
*/ */
Script.buildP2SHMultisigIn = function(pubkeys, threshold, signatures, opts) { Script.buildP2SHMultisigIn = function(pubkeys, threshold, signatures, opts) {
$.checkArgument(_.isArray(pubkeys));
$.checkArgument(_.isNumber(threshold));
$.checkArgument(_.isArray(signatures));
opts = opts || {}; opts = opts || {};
var s = new Script(); var s = new Script();
s.add(Opcode.OP_0); s.add(Opcode.OP_0);
@ -571,6 +574,8 @@ Script.buildP2SHMultisigIn = function(pubkeys, threshold, signatures, opts) {
* @param {(Address|PublicKey)} to - destination address or public key * @param {(Address|PublicKey)} to - destination address or public key
*/ */
Script.buildPublicKeyHashOut = function(to) { Script.buildPublicKeyHashOut = function(to) {
$.checkArgument(!_.isUndefined(to));
$.checkArgument(to instanceof PublicKey || to instanceof Address || _.isString(to));
if (to instanceof PublicKey) { if (to instanceof PublicKey) {
to = to.toAddress(); to = to.toAddress();
} else if (_.isString(to)) { } else if (_.isString(to)) {
@ -590,6 +595,7 @@ Script.buildPublicKeyHashOut = function(to) {
* public key * public key
*/ */
Script.buildPublicKeyOut = function(pubkey) { Script.buildPublicKeyOut = function(pubkey) {
$.checkArgument(pubkey instanceof PublicKey);
var s = new Script(); var s = new Script();
s.add(pubkey.toBuffer()) s.add(pubkey.toBuffer())
.add(Opcode.OP_CHECKSIG); .add(Opcode.OP_CHECKSIG);
@ -601,6 +607,7 @@ Script.buildPublicKeyOut = function(pubkey) {
* @param {(string|Buffer)} to - the data to embed in the output * @param {(string|Buffer)} to - the data to embed in the output
*/ */
Script.buildDataOut = function(data) { Script.buildDataOut = function(data) {
$.checkArgument(_.isUndefined(data) || _.isString(data) || BufferUtil.isBuffer(data));
if (typeof data === 'string') { if (typeof data === 'string') {
data = new Buffer(data); data = new Buffer(data);
} }
@ -613,10 +620,13 @@ Script.buildDataOut = function(data) {
}; };
/** /**
* @param {Script} script - the redeemScript for the new p2sh output * @param {Script|Address} script - the redeemScript for the new p2sh output.
* It can also be a p2sh address
* @returns Script new pay to script hash script for given script * @returns Script new pay to script hash script for given script
*/ */
Script.buildScriptHashOut = function(script) { Script.buildScriptHashOut = function(script) {
$.checkArgument(script instanceof Script ||
(script instanceof Address && script.isPayToScriptHash()));
var s = new Script(); var s = new Script();
s.add(Opcode.OP_HASH160) s.add(Opcode.OP_HASH160)
.add(Hash.sha256ripemd160(script.toBuffer())) .add(Hash.sha256ripemd160(script.toBuffer()))
@ -629,10 +639,15 @@ Script.buildScriptHashOut = function(script) {
* output script. * output script.
* *
* @param {Buffer|string|PublicKey} publicKey * @param {Buffer|string|PublicKey} publicKey
* @param {Buffer} signature - the signature in DER cannonical encoding * @param {Signature|Buffer} signature - a Signature object, or the signature in DER cannonical encoding
* @param {number=} sigtype - the type of the signature (defaults to SIGHASH_ALL) * @param {number=} sigtype - the type of the signature (defaults to SIGHASH_ALL)
*/ */
Script.buildPublicKeyHashIn = function(publicKey, signature, sigtype) { Script.buildPublicKeyHashIn = function(publicKey, signature, sigtype) {
$.checkArgument(signature instanceof Signature || BufferUtil.isBuffer(signature));
$.checkArgument(_.isUndefined(sigtype) || _.isNumber(sigtype));
if (signature instanceof Signature) {
signature = signature.toBuffer();
}
var script = new Script() var script = new Script()
.add(BufferUtil.concat([ .add(BufferUtil.concat([
signature, signature,

8
test/crypto/signature.js

@ -5,7 +5,7 @@ var bitcore = require('../..');
var BN = bitcore.crypto.BN; var BN = bitcore.crypto.BN;
var Signature = bitcore.crypto.Signature; var Signature = bitcore.crypto.Signature;
var JSUtil = bitcore.util.js; var JSUtil = bitcore.util.js;
var ScriptInterpreter = bitcore.Script.Interpreter; var Interpreter = bitcore.Script.Interpreter;
var sig_canonical = require('../data/bitcoind/sig_canonical'); var sig_canonical = require('../data/bitcoind/sig_canonical');
var sig_noncanonical = require('../data/bitcoind/sig_noncanonical'); var sig_noncanonical = require('../data/bitcoind/sig_noncanonical');
@ -218,9 +218,9 @@ describe('Signature', function() {
} }
it('should be ' + (expected ? '' : 'in') + 'valid for fixture #' + i, function() { it('should be ' + (expected ? '' : 'in') + 'valid for fixture #' + i, function() {
var sighex = vector; var sighex = vector;
var interp = ScriptInterpreter(); var interp = Interpreter();
interp.flags = ScriptInterpreter.SCRIPT_VERIFY_DERSIG | interp.flags = Interpreter.SCRIPT_VERIFY_DERSIG |
ScriptInterpreter.SCRIPT_VERIFY_STRICTENC; Interpreter.SCRIPT_VERIFY_STRICTENC;
var result = interp.checkSignatureEncoding(new Buffer(sighex, 'hex')); var result = interp.checkSignatureEncoding(new Buffer(sighex, 'hex'));
result.should.equal(expected); result.should.equal(expected);
}); });

106
test/script_interpreter.js → test/script/interpreter.js

@ -1,20 +1,20 @@
'use strict'; 'use strict';
var should = require('chai').should(); var should = require('chai').should();
var bitcore = require('..'); var bitcore = require('../..');
var ScriptInterpreter = bitcore.Script.Interpreter; var Interpreter = bitcore.Script.Interpreter;
var Transaction = bitcore.Transaction; var Transaction = bitcore.Transaction;
var PrivateKey = bitcore.PrivateKey;
var Script = bitcore.Script; var Script = bitcore.Script;
var BN = bitcore.crypto.BN; var BN = bitcore.crypto.BN;
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter; var BufferWriter = bitcore.encoding.BufferWriter;
var Opcode = bitcore.Opcode; var Opcode = bitcore.Opcode;
var _ = require('lodash'); var _ = require('lodash');
var script_valid = require('./data/bitcoind/script_valid'); var script_valid = require('../data/bitcoind/script_valid');
var script_invalid = require('./data/bitcoind/script_invalid'); var script_invalid = require('../data/bitcoind/script_invalid');
var tx_valid = require('./transaction/tx_valid'); var tx_valid = require('../transaction/tx_valid');
var tx_invalid = require('./transaction/tx_invalid'); var tx_invalid = require('../transaction/tx_invalid');
//the script string format used in bitcoind data tests //the script string format used in bitcoind data tests
Script.fromBitcoindString = function(str) { Script.fromBitcoindString = function(str) {
@ -59,11 +59,11 @@ Script.fromBitcoindString = function(str) {
describe('ScriptInterpreter', function() { describe('Interpreter', function() {
it('should make a new interp', function() { it('should make a new interp', function() {
var interp = new ScriptInterpreter(); var interp = new Interpreter();
(interp instanceof ScriptInterpreter).should.equal(true); (interp instanceof Interpreter).should.equal(true);
interp.stack.length.should.equal(0); interp.stack.length.should.equal(0);
interp.altstack.length.should.equal(0); interp.altstack.length.should.equal(0);
interp.pc.should.equal(0); interp.pc.should.equal(0);
@ -77,14 +77,14 @@ describe('ScriptInterpreter', function() {
describe('@castToBool', function() { describe('@castToBool', function() {
it('should cast these bufs to bool correctly', function() { it('should cast these bufs to bool correctly', function() {
ScriptInterpreter.castToBool(BN(0).toSM({ Interpreter.castToBool(BN(0).toSM({
endian: 'little' endian: 'little'
})).should.equal(false); })).should.equal(false);
ScriptInterpreter.castToBool(new Buffer('0080', 'hex')).should.equal(false); //negative 0 Interpreter.castToBool(new Buffer('0080', 'hex')).should.equal(false); //negative 0
ScriptInterpreter.castToBool(BN(1).toSM({ Interpreter.castToBool(BN(1).toSM({
endian: 'little' endian: 'little'
})).should.equal(true); })).should.equal(true);
ScriptInterpreter.castToBool(BN(-1).toSM({ Interpreter.castToBool(BN(-1).toSM({
endian: 'little' endian: 'little'
})).should.equal(true); })).should.equal(true);
@ -92,7 +92,7 @@ describe('ScriptInterpreter', function() {
var bool = BN().fromSM(buf, { var bool = BN().fromSM(buf, {
endian: 'little' endian: 'little'
}).cmp(0) !== 0; }).cmp(0) !== 0;
ScriptInterpreter.castToBool(buf).should.equal(bool); Interpreter.castToBool(buf).should.equal(bool);
}); });
}); });
@ -101,58 +101,86 @@ describe('ScriptInterpreter', function() {
it('should verify these trivial scripts', function() { it('should verify these trivial scripts', function() {
var verified; var verified;
var si = ScriptInterpreter(); var si = Interpreter();
verified = si.verify(Script('OP_1'), Script('OP_1')); verified = si.verify(Script('OP_1'), Script('OP_1'));
verified.should.equal(true); verified.should.equal(true);
verified = ScriptInterpreter().verify(Script('OP_1'), Script('OP_0')); verified = Interpreter().verify(Script('OP_1'), Script('OP_0'));
verified.should.equal(false); verified.should.equal(false);
verified = ScriptInterpreter().verify(Script('OP_0'), Script('OP_1')); verified = Interpreter().verify(Script('OP_0'), Script('OP_1'));
verified.should.equal(true); verified.should.equal(true);
verified = ScriptInterpreter().verify(Script('OP_CODESEPARATOR'), Script('OP_1')); verified = Interpreter().verify(Script('OP_CODESEPARATOR'), Script('OP_1'));
verified.should.equal(true); verified.should.equal(true);
verified = ScriptInterpreter().verify(Script(''), Script('OP_DEPTH OP_0 OP_EQUAL')); verified = Interpreter().verify(Script(''), Script('OP_DEPTH OP_0 OP_EQUAL'));
verified.should.equal(true); verified.should.equal(true);
verified = ScriptInterpreter().verify(Script('OP_1 OP_2'), Script('OP_2 OP_EQUALVERIFY OP_1 OP_EQUAL')); verified = Interpreter().verify(Script('OP_1 OP_2'), Script('OP_2 OP_EQUALVERIFY OP_1 OP_EQUAL'));
verified.should.equal(true); verified.should.equal(true);
verified = ScriptInterpreter().verify(Script('9 0x000000000000000010'), Script('')); verified = Interpreter().verify(Script('9 0x000000000000000010'), Script(''));
verified.should.equal(true); verified.should.equal(true);
verified = ScriptInterpreter().verify(Script('OP_1'), Script('OP_15 OP_ADD OP_16 OP_EQUAL')); verified = Interpreter().verify(Script('OP_1'), Script('OP_15 OP_ADD OP_16 OP_EQUAL'));
verified.should.equal(true); verified.should.equal(true);
verified = ScriptInterpreter().verify(Script('OP_0'), Script('OP_IF OP_VER OP_ELSE OP_1 OP_ENDIF')); verified = Interpreter().verify(Script('OP_0'), Script('OP_IF OP_VER OP_ELSE OP_1 OP_ENDIF'));
verified.should.equal(true); verified.should.equal(true);
}); });
it('should verify these simple transaction', function() {
// first we create a transaction
var privateKey = new PrivateKey('cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY');
var publicKey = privateKey.publicKey;
var fromAddress = publicKey.toAddress();
var toAddress = 'mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc';
var scriptPubkey = Script.buildPublicKeyHashOut(fromAddress);
var utxo = {
address: fromAddress,
txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458',
outputIndex: 0,
script: scriptPubkey,
satoshis: 100000
};
var tx = new Transaction()
.from(utxo)
.to(toAddress, 100000)
.sign(privateKey);
// we then extract the signature from the first input
var inputIndex = 0;
var signature = tx.getSignatures(privateKey)[inputIndex].signature;
var scriptSig = Script.buildPublicKeyHashIn(publicKey, signature);
var flags = Interpreter.SCRIPT_VERIFY_P2SH | Interpreter.SCRIPT_VERIFY_STRICTENC;
var verified = Interpreter().verify(scriptSig, scriptPubkey, tx, inputIndex, flags);
verified.should.equal(true);
});
}); });
var getFlags = function getFlags(flagstr) { var getFlags = function getFlags(flagstr) {
var flags = 0; var flags = 0;
if (flagstr.indexOf('NONE') !== -1) { if (flagstr.indexOf('NONE') !== -1) {
flags = flags | ScriptInterpreter.SCRIPT_VERIFY_NONE; flags = flags | Interpreter.SCRIPT_VERIFY_NONE;
} }
if (flagstr.indexOf('P2SH') !== -1) { if (flagstr.indexOf('P2SH') !== -1) {
flags = flags | ScriptInterpreter.SCRIPT_VERIFY_P2SH; flags = flags | Interpreter.SCRIPT_VERIFY_P2SH;
} }
if (flagstr.indexOf('STRICTENC') !== -1) { if (flagstr.indexOf('STRICTENC') !== -1) {
flags = flags | ScriptInterpreter.SCRIPT_VERIFY_STRICTENC; flags = flags | Interpreter.SCRIPT_VERIFY_STRICTENC;
} }
if (flagstr.indexOf('DERSIG') !== -1) { if (flagstr.indexOf('DERSIG') !== -1) {
flags = flags | ScriptInterpreter.SCRIPT_VERIFY_DERSIG; flags = flags | Interpreter.SCRIPT_VERIFY_DERSIG;
} }
if (flagstr.indexOf('LOW_S') !== -1) { if (flagstr.indexOf('LOW_S') !== -1) {
flags = flags | ScriptInterpreter.SCRIPT_VERIFY_LOW_S; flags = flags | Interpreter.SCRIPT_VERIFY_LOW_S;
} }
if (flagstr.indexOf('NULLDUMMY') !== -1) { if (flagstr.indexOf('NULLDUMMY') !== -1) {
flags = flags | ScriptInterpreter.SCRIPT_VERIFY_NULLDUMMY; flags = flags | Interpreter.SCRIPT_VERIFY_NULLDUMMY;
} }
if (flagstr.indexOf('SIGPUSHONLY') !== -1) { if (flagstr.indexOf('SIGPUSHONLY') !== -1) {
flags = flags | ScriptInterpreter.SCRIPT_VERIFY_SIGPUSHONLY; flags = flags | Interpreter.SCRIPT_VERIFY_SIGPUSHONLY;
} }
if (flagstr.indexOf('MINIMALDATA') !== -1) { if (flagstr.indexOf('MINIMALDATA') !== -1) {
flags = flags | ScriptInterpreter.SCRIPT_VERIFY_MINIMALDATA; flags = flags | Interpreter.SCRIPT_VERIFY_MINIMALDATA;
} }
if (flagstr.indexOf('DISCOURAGE_UPGRADABLE_NOPS') !== -1) { if (flagstr.indexOf('DISCOURAGE_UPGRADABLE_NOPS') !== -1) {
flags = flags | ScriptInterpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS; flags = flags | Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS;
} }
return flags; return flags;
}; };
@ -168,7 +196,6 @@ describe('ScriptInterpreter', function() {
var scriptPubkey = Script.fromBitcoindString(vector[1]); var scriptPubkey = Script.fromBitcoindString(vector[1]);
var flags = getFlags(vector[2]); var flags = getFlags(vector[2]);
//testToFromString(scriptSig); //testToFromString(scriptSig);
//testToFromString(scriptPubkey); //testToFromString(scriptPubkey);
@ -199,7 +226,7 @@ describe('ScriptInterpreter', function() {
satoshis: 0 satoshis: 0
})); }));
var interp = ScriptInterpreter(); var interp = Interpreter();
var verified = interp.verify(scriptSig, scriptPubkey, spendtx, 0, flags); var verified = interp.verify(scriptSig, scriptPubkey, spendtx, 0, flags);
verified.should.equal(expected); verified.should.equal(expected);
}; };
@ -214,9 +241,10 @@ describe('ScriptInterpreter', function() {
var descstr = vector[3]; var descstr = vector[3];
var fullScriptString = vector[0] + ' ' + vector[1]; var fullScriptString = vector[0] + ' ' + vector[1];
var comment = descstr ? (' (' + descstr + ')') : ''; var comment = descstr ? (' (' + descstr + ')') : '';
it('should pass script_' + (expected ? '' : 'in') + 'valid vector #' + c + ': ' + fullScriptString + comment, function() { it('should pass script_' + (expected ? '' : 'in') + 'valid ' +
testFixture(vector, expected); 'vector #' + c + ': ' + fullScriptString + comment, function() {
}); testFixture(vector, expected);
});
}); });
}; };
testAllFixtures(script_valid, true); testAllFixtures(script_valid, true);
@ -256,7 +284,7 @@ describe('ScriptInterpreter', function() {
var scriptPubkey = map[txidhex + ':' + txoutnum]; var scriptPubkey = map[txidhex + ':' + txoutnum];
should.exist(scriptPubkey); should.exist(scriptPubkey);
should.exist(scriptSig); should.exist(scriptSig);
var interp = ScriptInterpreter(); var interp = Interpreter();
var verified = interp.verify(scriptSig, scriptPubkey, tx, j, flags); var verified = interp.verify(scriptSig, scriptPubkey, tx, j, flags);
if (!verified) { if (!verified) {
allInputsVerified = false; allInputsVerified = false;

2
test/script.js → test/script/script.js

@ -1,7 +1,7 @@
'use strict'; 'use strict';
var should = require('chai').should(); var should = require('chai').should();
var bitcore = require('..'); var bitcore = require('../..');
var Script = bitcore.Script; var Script = bitcore.Script;
var Opcode = bitcore.Opcode; var Opcode = bitcore.Opcode;
var PublicKey = bitcore.PublicKey; var PublicKey = bitcore.PublicKey;
Loading…
Cancel
Save