From 3dd86446e09ed480a439761f9a61904d370fde9c Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Wed, 17 Dec 2014 17:24:33 -0300 Subject: [PATCH 1/2] add Interpreter docs and refactor a bit --- docs/Script.md | 48 +++ lib/crypto/signature.js | 2 +- lib/script/interpreter.js | 322 +++++++++--------- lib/script/script.js | 19 +- test/crypto/signature.js | 8 +- .../interpreter.js} | 106 +++--- test/{ => script}/script.js | 2 +- 7 files changed, 302 insertions(+), 205 deletions(-) rename test/{script_interpreter.js => script/interpreter.js} (67%) rename test/{ => script}/script.js (99%) diff --git a/docs/Script.md b/docs/Script.md index 16a9aa9..b9a0571 100644 --- a/docs/Script.md +++ b/docs/Script.md @@ -110,3 +110,51 @@ 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 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); +``` + diff --git a/lib/crypto/signature.js b/lib/crypto/signature.js index 48cb923..f2b4893 100644 --- a/lib/crypto/signature.js +++ b/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(); diff --git a/lib/script/interpreter.js b/lib/script/interpreter.js index 98a461c..6ddf40a 100644 --- a/lib/script/interpreter.js +++ b/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; }; + diff --git a/lib/script/script.js b/lib/script/script.js index 1ba0f75..5c2c04d 100644 --- a/lib/script/script.js +++ b/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, diff --git a/test/crypto/signature.js b/test/crypto/signature.js index a6ba4c3..1c00566 100644 --- a/test/crypto/signature.js +++ b/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); }); diff --git a/test/script_interpreter.js b/test/script/interpreter.js similarity index 67% rename from test/script_interpreter.js rename to test/script/interpreter.js index 1bdeb5e..a67f847 100644 --- a/test/script_interpreter.js +++ b/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; diff --git a/test/script.js b/test/script/script.js similarity index 99% rename from test/script.js rename to test/script/script.js index 0de98b9..192ab3e 100644 --- a/test/script.js +++ b/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; From f664e6a4af1bed37ed0239b1e74efddb6d77d4b3 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Wed, 17 Dec 2014 17:28:29 -0300 Subject: [PATCH 2/2] shorten example --- docs/Script.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/docs/Script.md b/docs/Script.md index b9a0571..e26c978 100644 --- a/docs/Script.md +++ b/docs/Script.md @@ -132,18 +132,6 @@ Note that `verify` expects two scripts: one is the input script (scriptSig) and It also accepts some optional parameters, assuming defaults if not provided: ``` // 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)