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.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]);
};
Signature.prototype.toDER = function() {
Signature.prototype.toBuffer = Signature.prototype.toDER = function() {
var rnbuf = this.r.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.
*
* 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) {
if (!(this instanceof ScriptInterpreter)) {
return new ScriptInterpreter(obj);
var Interpreter = function Interpreter(obj) {
if (!(this instanceof Interpreter)) {
return new Interpreter(obj);
}
if (obj) {
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.altstack = [];
this.pc = 0;
@ -46,7 +167,7 @@ ScriptInterpreter.prototype.initialize = function(obj) {
this.flags = 0;
};
ScriptInterpreter.prototype.set = function(obj) {
Interpreter.prototype.set = function(obj) {
this.script = obj.script || this.script;
this.tx = obj.tx || this.tx;
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;
};
ScriptInterpreter.true = new Buffer([1]);
ScriptInterpreter.false = new Buffer([]);
Interpreter.true = new Buffer([1]);
Interpreter.false = new Buffer([]);
ScriptInterpreter.MAX_SCRIPT_ELEMENT_SIZE = 520;
Interpreter.MAX_SCRIPT_ELEMENT_SIZE = 520;
// flags taken from bitcoind
// bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104
ScriptInterpreter.SCRIPT_VERIFY_NONE = 0;
Interpreter.SCRIPT_VERIFY_NONE = 0;
// 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 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).
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)
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
// (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).
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).
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
// 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).
// In addition, whenever a stack element is interpreted as a number, it must be of minimal length (BIP62 rule 4).
// (softfork safe)
ScriptInterpreter.SCRIPT_VERIFY_MINIMALDATA = (1 << 6);
Interpreter.SCRIPT_VERIFY_MINIMALDATA = (1 << 6);
// 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
// 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.
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++) {
if (buf[i] !== 0) {
// can be negative zero
@ -123,18 +244,18 @@ ScriptInterpreter.castToBool = function(buf) {
/**
* Translated from bitcoind's CheckSignatureEncoding
*/
ScriptInterpreter.prototype.checkSignatureEncoding = function(buf) {
Interpreter.prototype.checkSignatureEncoding = function(buf) {
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';
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);
if (!sig.hasLowS()) {
this.errstr = 'SCRIPT_ERR_SIG_DER_HIGH_S';
return false;
}
} else if ((this.flags & ScriptInterpreter.SCRIPT_VERIFY_STRICTENC) !== 0) {
} else if ((this.flags & Interpreter.SCRIPT_VERIFY_STRICTENC) !== 0) {
sig = Signature.fromTxFormat(buf);
if (!sig.hasDefinedHashtype()) {
this.errstr = 'SCRIPT_ERR_SIG_HASHTYPE';
@ -147,8 +268,8 @@ ScriptInterpreter.prototype.checkSignatureEncoding = function(buf) {
/**
* Translated from bitcoind's CheckPubKeyEncoding
*/
ScriptInterpreter.prototype.checkPubkeyEncoding = function(buf) {
if ((this.flags & ScriptInterpreter.SCRIPT_VERIFY_STRICTENC) !== 0 && !PublicKey.isValid(buf)) {
Interpreter.prototype.checkPubkeyEncoding = function(buf) {
if ((this.flags & Interpreter.SCRIPT_VERIFY_STRICTENC) !== 0 && !PublicKey.isValid(buf)) {
this.errstr = 'SCRIPT_ERR_PUBKEYTYPE';
return false;
}
@ -157,10 +278,10 @@ ScriptInterpreter.prototype.checkPubkeyEncoding = function(buf) {
/**
* Based on bitcoind's EvalScript function, with the inner loop moved to
* ScriptInterpreter.prototype.step()
* Interpreter.prototype.step()
* bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104
*/
ScriptInterpreter.prototype.evaluate = function() {
Interpreter.prototype.evaluate = function() {
if (this.script.toBuffer().length > 10000) {
this.errstr = 'SCRIPT_ERR_SCRIPT_SIZE';
return false;
@ -196,9 +317,9 @@ ScriptInterpreter.prototype.evaluate = function() {
* Based on the inner loop of bitcoind's EvalScript function
* 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);
var fExec = (this.vfExec.indexOf(false) === -1);
@ -212,7 +333,7 @@ ScriptInterpreter.prototype.step = function() {
this.errstr = 'SCRIPT_ERR_UNDEFINED_OPCODE';
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';
return false;
}
@ -249,7 +370,7 @@ ScriptInterpreter.prototype.step = function() {
return false;
}
if (!chunk.buf) {
this.stack.push(ScriptInterpreter.false);
this.stack.push(Interpreter.false);
} else if (chunk.len !== chunk.buf.length) {
throw new Error('Length of push value not equal to length of data');
} else {
@ -304,7 +425,7 @@ ScriptInterpreter.prototype.step = function() {
case Opcode.OP_NOP9:
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';
return false;
}
@ -323,7 +444,7 @@ ScriptInterpreter.prototype.step = function() {
return false;
}
var buf = this.stack.pop();
fValue = ScriptInterpreter.castToBool(buf);
fValue = Interpreter.castToBool(buf);
if (opcodenum === Opcode.OP_NOTIF)
fValue = !fValue;
}
@ -360,7 +481,7 @@ ScriptInterpreter.prototype.step = function() {
return false;
}
var buf = this.stack[this.stack.length - 1];
var fValue = ScriptInterpreter.castToBool(buf);
var fValue = Interpreter.castToBool(buf);
if (fValue)
this.stack.pop();
else {
@ -491,7 +612,7 @@ ScriptInterpreter.prototype.step = function() {
return false;
}
var buf = this.stack[this.stack.length - 1];
var fValue = ScriptInterpreter.castToBool(buf);
var fValue = Interpreter.castToBool(buf);
if (fValue)
this.stack.push(buf);
}
@ -652,7 +773,7 @@ ScriptInterpreter.prototype.step = function() {
// fEqual = !fEqual;
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 (fEqual)
this.stack.pop();
@ -790,7 +911,7 @@ ScriptInterpreter.prototype.step = function() {
if (opcodenum === Opcode.OP_NUMEQUALVERIFY) {
// 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();
else {
this.errstr = 'SCRIPT_ERR_NUMEQUALVERIFY';
@ -815,7 +936,7 @@ ScriptInterpreter.prototype.step = function() {
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;
@ -898,7 +1019,7 @@ ScriptInterpreter.prototype.step = function() {
this.stack.pop();
this.stack.pop();
// 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 (fSuccess) {
this.stack.pop();
@ -1013,13 +1134,13 @@ ScriptInterpreter.prototype.step = function() {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
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';
return false;
}
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 (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;
};

19
lib/script/script.js

@ -555,6 +555,9 @@ Script.buildMultisigOut = function(publicKeys, threshold, opts) {
* @returns Script
*/
Script.buildP2SHMultisigIn = function(pubkeys, threshold, signatures, opts) {
$.checkArgument(_.isArray(pubkeys));
$.checkArgument(_.isNumber(threshold));
$.checkArgument(_.isArray(signatures));
opts = opts || {};
var s = new Script();
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
*/
Script.buildPublicKeyHashOut = function(to) {
$.checkArgument(!_.isUndefined(to));
$.checkArgument(to instanceof PublicKey || to instanceof Address || _.isString(to));
if (to instanceof PublicKey) {
to = to.toAddress();
} else if (_.isString(to)) {
@ -590,6 +595,7 @@ Script.buildPublicKeyHashOut = function(to) {
* public key
*/
Script.buildPublicKeyOut = function(pubkey) {
$.checkArgument(pubkey instanceof PublicKey);
var s = new Script();
s.add(pubkey.toBuffer())
.add(Opcode.OP_CHECKSIG);
@ -601,6 +607,7 @@ Script.buildPublicKeyOut = function(pubkey) {
* @param {(string|Buffer)} to - the data to embed in the output
*/
Script.buildDataOut = function(data) {
$.checkArgument(_.isUndefined(data) || _.isString(data) || BufferUtil.isBuffer(data));
if (typeof data === 'string') {
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
*/
Script.buildScriptHashOut = function(script) {
$.checkArgument(script instanceof Script ||
(script instanceof Address && script.isPayToScriptHash()));
var s = new Script();
s.add(Opcode.OP_HASH160)
.add(Hash.sha256ripemd160(script.toBuffer()))
@ -629,10 +639,15 @@ Script.buildScriptHashOut = function(script) {
* output script.
*
* @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)
*/
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()
.add(BufferUtil.concat([
signature,

8
test/crypto/signature.js

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

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

@ -1,20 +1,20 @@
'use strict';
var should = require('chai').should();
var bitcore = require('..');
var ScriptInterpreter = bitcore.Script.Interpreter;
var bitcore = require('../..');
var Interpreter = bitcore.Script.Interpreter;
var Transaction = bitcore.Transaction;
var PrivateKey = bitcore.PrivateKey;
var Script = bitcore.Script;
var BN = bitcore.crypto.BN;
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
var Opcode = bitcore.Opcode;
var _ = require('lodash');
var script_valid = require('./data/bitcoind/script_valid');
var script_invalid = require('./data/bitcoind/script_invalid');
var tx_valid = require('./transaction/tx_valid');
var tx_invalid = require('./transaction/tx_invalid');
var script_valid = require('../data/bitcoind/script_valid');
var script_invalid = require('../data/bitcoind/script_invalid');
var tx_valid = require('../transaction/tx_valid');
var tx_invalid = require('../transaction/tx_invalid');
//the script string format used in bitcoind data tests
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() {
var interp = new ScriptInterpreter();
(interp instanceof ScriptInterpreter).should.equal(true);
var interp = new Interpreter();
(interp instanceof Interpreter).should.equal(true);
interp.stack.length.should.equal(0);
interp.altstack.length.should.equal(0);
interp.pc.should.equal(0);
@ -77,14 +77,14 @@ describe('ScriptInterpreter', function() {
describe('@castToBool', function() {
it('should cast these bufs to bool correctly', function() {
ScriptInterpreter.castToBool(BN(0).toSM({
Interpreter.castToBool(BN(0).toSM({
endian: 'little'
})).should.equal(false);
ScriptInterpreter.castToBool(new Buffer('0080', 'hex')).should.equal(false); //negative 0
ScriptInterpreter.castToBool(BN(1).toSM({
Interpreter.castToBool(new Buffer('0080', 'hex')).should.equal(false); //negative 0
Interpreter.castToBool(BN(1).toSM({
endian: 'little'
})).should.equal(true);
ScriptInterpreter.castToBool(BN(-1).toSM({
Interpreter.castToBool(BN(-1).toSM({
endian: 'little'
})).should.equal(true);
@ -92,7 +92,7 @@ describe('ScriptInterpreter', function() {
var bool = BN().fromSM(buf, {
endian: 'little'
}).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() {
var verified;
var si = ScriptInterpreter();
var si = Interpreter();
verified = si.verify(Script('OP_1'), Script('OP_1'));
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 = ScriptInterpreter().verify(Script('OP_0'), Script('OP_1'));
verified = Interpreter().verify(Script('OP_0'), Script('OP_1'));
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 = 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 = 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 = ScriptInterpreter().verify(Script('9 0x000000000000000010'), Script(''));
verified = Interpreter().verify(Script('9 0x000000000000000010'), Script(''));
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 = 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);
});
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 flags = 0;
if (flagstr.indexOf('NONE') !== -1) {
flags = flags | ScriptInterpreter.SCRIPT_VERIFY_NONE;
flags = flags | Interpreter.SCRIPT_VERIFY_NONE;
}
if (flagstr.indexOf('P2SH') !== -1) {
flags = flags | ScriptInterpreter.SCRIPT_VERIFY_P2SH;
flags = flags | Interpreter.SCRIPT_VERIFY_P2SH;
}
if (flagstr.indexOf('STRICTENC') !== -1) {
flags = flags | ScriptInterpreter.SCRIPT_VERIFY_STRICTENC;
flags = flags | Interpreter.SCRIPT_VERIFY_STRICTENC;
}
if (flagstr.indexOf('DERSIG') !== -1) {
flags = flags | ScriptInterpreter.SCRIPT_VERIFY_DERSIG;
flags = flags | Interpreter.SCRIPT_VERIFY_DERSIG;
}
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) {
flags = flags | ScriptInterpreter.SCRIPT_VERIFY_NULLDUMMY;
flags = flags | Interpreter.SCRIPT_VERIFY_NULLDUMMY;
}
if (flagstr.indexOf('SIGPUSHONLY') !== -1) {
flags = flags | ScriptInterpreter.SCRIPT_VERIFY_SIGPUSHONLY;
flags = flags | Interpreter.SCRIPT_VERIFY_SIGPUSHONLY;
}
if (flagstr.indexOf('MINIMALDATA') !== -1) {
flags = flags | ScriptInterpreter.SCRIPT_VERIFY_MINIMALDATA;
flags = flags | Interpreter.SCRIPT_VERIFY_MINIMALDATA;
}
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;
};
@ -168,7 +196,6 @@ describe('ScriptInterpreter', function() {
var scriptPubkey = Script.fromBitcoindString(vector[1]);
var flags = getFlags(vector[2]);
//testToFromString(scriptSig);
//testToFromString(scriptPubkey);
@ -199,7 +226,7 @@ describe('ScriptInterpreter', function() {
satoshis: 0
}));
var interp = ScriptInterpreter();
var interp = Interpreter();
var verified = interp.verify(scriptSig, scriptPubkey, spendtx, 0, flags);
verified.should.equal(expected);
};
@ -214,9 +241,10 @@ describe('ScriptInterpreter', function() {
var descstr = vector[3];
var fullScriptString = vector[0] + ' ' + vector[1];
var comment = descstr ? (' (' + descstr + ')') : '';
it('should pass script_' + (expected ? '' : 'in') + 'valid vector #' + c + ': ' + fullScriptString + comment, function() {
testFixture(vector, expected);
});
it('should pass script_' + (expected ? '' : 'in') + 'valid ' +
'vector #' + c + ': ' + fullScriptString + comment, function() {
testFixture(vector, expected);
});
});
};
testAllFixtures(script_valid, true);
@ -256,7 +284,7 @@ describe('ScriptInterpreter', function() {
var scriptPubkey = map[txidhex + ':' + txoutnum];
should.exist(scriptPubkey);
should.exist(scriptSig);
var interp = ScriptInterpreter();
var interp = Interpreter();
var verified = interp.verify(scriptSig, scriptPubkey, tx, j, flags);
if (!verified) {
allInputsVerified = false;

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

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