Skip to content
This repository has been archived by the owner on Apr 3, 2019. It is now read-only.

Commit

Permalink
add checksequenceverify
Browse files Browse the repository at this point in the history
  • Loading branch information
matiu committed Apr 23, 2018
1 parent 63f9a34 commit 524a691
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 10 deletions.
1 change: 1 addition & 0 deletions lib/opcode.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ Opcode.map = {
OP_CHECKMULTISIGVERIFY: 175,

OP_CHECKLOCKTIMEVERIFY: 177,
OP_CHECKSEQUENCEVERIFY: 178,

// expansion
OP_NOP1: 176,
Expand Down
148 changes: 146 additions & 2 deletions lib/script/interpreter.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,33 @@ Interpreter.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY = (1 << 9);
Interpreter.SCRIPT_VERIFY_WITNESS = (1 << 10);
Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = (1 << 11);

// support CHECKSEQUENCEVERIFY opcode
//
// See BIP112 for details
Interpreter.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY = (1 << 10),


/* Below flags apply in the context of BIP 68*/
/**
* If this flag set, CTxIn::nSequence is NOT interpreted as a relative
* lock-time.
*/
Interpreter.SEQUENCE_LOCKTIME_DISABLE_FLAG = (1 << 31);

/**
* If CTxIn::nSequence encodes a relative lock-time and this flag is set,
* the relative lock-time has units of 512 seconds, otherwise it specifies
* blocks with a granularity of 1.
*/
Interpreter.SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22);

/**
* If CTxIn::nSequence encodes a relative lock-time, this mask is applied to
* extract that lock-time from the sequence field.
*/
Interpreter.SEQUENCE_LOCKTIME_MASK = 0x0000ffff;


Interpreter.castToBool = function(buf) {
for (var i = 0; i < buf.length; i++) {
if (buf[i] !== 0) {
Expand All @@ -384,6 +411,13 @@ Interpreter.castToBool = function(buf) {
*/
Interpreter.prototype.checkSignatureEncoding = function(buf) {
var sig;

// Empty signature. Not strictly DER encoded, but allowed to provide a
// compact way to provide an invalid signature for use with CHECK(MULTI)SIG
if (buf.length == 0) {
return true;
}

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;
Expand All @@ -400,6 +434,7 @@ Interpreter.prototype.checkSignatureEncoding = function(buf) {
return false;
}
}

return true;
};

Expand Down Expand Up @@ -498,6 +533,66 @@ Interpreter.prototype.checkLockTime = function(nLockTime) {
return true;
}


/**
* Checks a sequence parameter with the transaction's sequence.
* @param {BN} nSequence the sequence read from the script
* @return {boolean} true if the transaction's sequence is less than or equal to
* the transaction's sequence
*/
Interpreter.prototype.checkSequence = function(nSequence) {

// Relative lock times are supported by comparing the passed in operand to
// the sequence number of the input.
var txToSequence = this.tx.inputs[this.nin].sequenceNumber;
console.log('[interpreter.js.392:txToSequence:]',txToSequence); //TODO


console.log('[interpreter.js.395] version', this.tx.version); //TODO
// Fail if the transaction's version number is not set high enough to
// trigger BIP 68 rules.
if (this.tx.version < 2) {
return false;
}
// TODO BN!

// Sequence numbers with their most significant bit set are not consensus
// constrained. Testing that the transaction's sequence number do not have
// this bit set prevents using this property to get around a
// CHECKSEQUENCEVERIFY check.
if (txToSequence & SEQUENCE_LOCKTIME_DISABLE_FLAG) {
return false;
}

// Mask off any bits that do not have consensus-enforced meaning before
// doing the integer comparisons
var nLockTimeMask =
Interpreter.SEQUENCE_LOCKTIME_TYPE_FLAG | Interpreter.SEQUENCE_LOCKTIME_MASK;
var txToSequenceMasked = txToSequence & nLockTimeMask;
var nSequenceMasked = nSequence & nLockTimeMask;

// There are two kinds of nSequence: lock-by-blockheight and
// lock-by-blocktime, distinguished by whether nSequenceMasked <
// CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG.
//
// We want to compare apples to apples, so fail the script unless the type
// of nSequenceMasked being tested is the same as the nSequenceMasked in the
// transaction.
if (!((txToSequenceMasked < Interpreter.SEQUENCE_LOCKTIME_TYPE_FLAG &&
nSequenceMasked < Interpreter.SEQUENCE_LOCKTIME_TYPE_FLAG) ||
(txToSequenceMasked >= Interpreter.SEQUENCE_LOCKTIME_TYPE_FLAG &&
nSequenceMasked >= Interpreter.SEQUENCE_LOCKTIME_TYPE_FLAG))) {
return false;
}

// Now that we know we're comparing apples-to-apples, the comparison is a
// simple numeric one.
if (nSequenceMasked > txToSequenceMasked) {
return false;
}
return true;
}

/**
* Based on the inner loop of bitcoind's EvalScript function
* bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104
Expand Down Expand Up @@ -649,8 +744,57 @@ Interpreter.prototype.step = function() {
}
break;

case Opcode.OP_NOP1:
case Opcode.OP_NOP3:
case Opcode.OP_CHECKSEQUENCEVERIFY:

if (!(this.flags & Interpreter.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY)) {
// not enabled; treat as a NOP3
if (this.flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) {
this.errstr = 'SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS';
return false;
}
break;
}

if (this.stack.length < 1) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}


// nSequence, like nLockTime, is a 32-bit unsigned
// integer field. See the comment in CHECKLOCKTIMEVERIFY
// regarding 5-byte numeric operands.

var nSequence = BN.fromScriptNumBuffer(this.stack[this.stack.length - 1], fRequireMinimal, 5);


// In the rare event that the argument may be < 0 due to
// some arithmetic being done first, you can always use
// 0 MAX CHECKSEQUENCEVERIFY.
if (nSequence.lt(new BN(0))) {
this.errstr = 'SCRIPT_ERR_NEGATIVE_LOCKTIME';
return false;
}

// To provide for future soft-fork extensibility, if the
// operand has the disabled lock-time flag set,
// CHECKSEQUENCEVERIFY behaves as a NOP.
if ((nSequence &
Interpreter.SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0) {
break;
}

// Actually compare the specified lock time with the transaction.
if (!this.checkSequence(nSequence)) {
this.errstr = 'SCRIPT_ERR_UNSATISFIED_LOCKTIME';
return false;
}
break;



case Opcode.OP_NOP1:
case Opcode.OP_NOP4:
case Opcode.OP_NOP5:
case Opcode.OP_NOP6:
Expand Down Expand Up @@ -1237,7 +1381,6 @@ Interpreter.prototype.step = function() {
// Drop the signature, since there's no way for a signature to sign itself
var tmpScript = new Script().add(bufSig);
subscript.findAndDelete(tmpScript);

if (!this.checkSignatureEncoding(bufSig) || !this.checkPubkeyEncoding(bufPubkey)) {
return false;
}
Expand All @@ -1253,6 +1396,7 @@ Interpreter.prototype.step = function() {

this.stack.pop();
this.stack.pop();

// stack.push_back(fSuccess ? vchTrue : vchFalse);
this.stack.push(fSuccess ? Interpreter.true : Interpreter.false);
if (opcodenum === Opcode.OP_CHECKSIGVERIFY) {
Expand Down
17 changes: 9 additions & 8 deletions test/script/interpreter.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ 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 script_tests = require('../data/bitcoind/script_tests');
var tx_valid = require('../data/bitcoind/tx_valid');
var tx_invalid = require('../data/bitcoind/tx_invalid');

Expand Down Expand Up @@ -337,25 +336,27 @@ describe('Interpreter', function() {
verified.should.equal(expected);
};
describe('bitcoind script evaluation fixtures', function() {
var testAllFixtures = function(set, expected) {
var testAllFixtures = function(set) {
var c = 0;
set.forEach(function(vector) {
if (vector.length === 1) {
return;
}
c++;
var descstr = vector[3];

var fullScriptString = vector[0] + ' ' + vector[1];
var expected = vector[3] == 'OK';
var descstr = vector[4];

var comment = descstr ? (' (' + descstr + ')') : '';
it('should pass script_' + (expected ? '' : 'in') + 'valid ' +
it('should ' + vector[3] + ' script_tests ' +
'vector #' + c + ': ' + fullScriptString + comment,
function() {
testFixture(vector, expected);
});
});
};
testAllFixtures(script_valid, true);
testAllFixtures(script_invalid, false);
testAllFixtures(script_tests);

});
describe('bitcoind transaction evaluation fixtures', function() {
Expand All @@ -370,8 +371,8 @@ describe('Interpreter', function() {
it('should pass tx_' + (expected ? '' : 'in') + 'valid vector ' + cc, function() {
var inputs = vector[0];
var txhex = vector[1];
var flags = getFlags(vector[2]);

var flags = getFlags(vector[2]);
var map = {};
inputs.forEach(function(input) {
var txid = input[0];
Expand Down

0 comments on commit 524a691

Please sign in to comment.