diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index 7bd97895..73bae982 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -164,7 +164,8 @@ exports.opcodes = { nop1: 0xb0, // nop2: 0xb1, checklocktimeverify: 0xb1, - nop3: 0xb2, + // nop3: 0xb2, + checksequenceverify: 0xb2, nop4: 0xb3, nop5: 0xb4, nop6: 0xb5, @@ -268,6 +269,10 @@ exports.hd = { exports.locktimeThreshold = 500000000; // Tue Nov 5 00:53:20 1985 UTC +exports.sequenceLocktimeDisableFlag = 0x80000000; // (1 << 31) +exports.sequenceLocktimeTypeFlag = 1 << 22; +exports.sequenceLocktimeMask = 0x0000ffff; + exports.oneHash = new Buffer( '0000000000000000000000000000000000000000000000000000000000000001', 'hex' @@ -298,7 +303,9 @@ exports.flags = { VERIFY_CLEANSTACK: (1 << 8), VERIFY_CHECKLOCKTIMEVERIFY: (1 << 9), VERIFY_WITNESS: (1 << 10), - VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM: (1 << 11) + VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM: (1 << 11), + // NOTE: Should be (1 << 10) - but that conflicts with segwit + VERIFY_CHECKSEQUENCEVERIFY: (1 << 12) }; // Block validation @@ -316,4 +323,5 @@ exports.flags.STANDARD_VERIFY_FLAGS = | exports.flags.VERIFY_CHECKLOCKTIMEVERIFY | exports.flags.VERIFY_LOW_S; // | exports.flags.VERIFY_WITNESS - // | exports.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM; + // | exports.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM + // | exports.flags.VERIFY_CHECKSEQUENCEVERIFY; diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 6986267e..3ebd998b 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -613,7 +613,6 @@ script.execute = function execute(data, stack, tx, index, flags, version, recurs switch (o) { case 'nop': - case 'nop3': case 'nop4': case 'nop5': case 'nop6': @@ -1172,6 +1171,46 @@ script.execute = function execute(data, stack, tx, index, flags, version, recurs break; } + case 'checksequenceverify': { + // OP_CHECKSEQUENCEVERIFY = OP_NOP3 + if (!(flags & constants.flags.VERIFY_CHECKSEQUENCEVERIFY)) { + if (flags & constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) + return false; + break; + } + + if (!tx || stack.length === 0) + return false; + + locktime = stack[stack.length - 1]; + + if (!Buffer.isBuffer(locktime)) + return false; + + if (locktime.length > 5) { + utils.debug('Warning: sequence is over 40 bits.'); + return false; + } + + locktime = script.num(locktime); + + // if (locktime < 0) + // return false; + + // if ((locktime & constants.sequenceLocktimeDisableFlag) !== 0) + // break; + + if (locktime.cmpn(0) < 0) + return false; + + if (locktime.uandn(constants.sequenceLocktimeDisableFlag).cmpn(0) !== 0) + break; + + if (!script.checkSequenceBN(locktime, tx, i)) + return false; + + break; + } case 'nop1': { // OP_EVAL = OP_NOP1 if (!script.allowEval) { @@ -1221,6 +1260,66 @@ script.execute = function execute(data, stack, tx, index, flags, version, recurs return true; }; +script.checkSequence = function checkSequence(sequence, tx, i) { + var txSequence = tx.inputs[i].sequence; + var locktimeMask, txSequenceMasked, sequenceMasked; + + if (tx.version < 2) + return false; + + // if (txSequence & constants.sequenceLocktimeDisableFlag) + return false; + + locktimeMask = constants.sequenceLocktimeTypeFlag + | constants.sequenceLocktimeMask; + txSequenceMasked = txSequence & locktimeMask; + sequenceMasked = sequence & locktimeMask; + + if (!( + (txSequenceMasked < constants.sequenceLocktimeTypeFlag + && sequenceMasked < constants.sequenceLocktimeTypeFlag + || (txSequenceMasked >= constants.sequenceLocktimeTypeFlag + && sequenceMasked >= constants.sequenceLocktimeTypeFlag + )) { + return false; + } + + if (sequenceMasked > txSequenceMasked) + return false; + + return true; +}; + +script.checkSequenceBN = function checkSequence(sequence, tx, i) { + var txSequence = new bn(tx.inputs[i].sequence); + var locktimeMask, txSequenceMasked, sequenceMasked; + + if (tx.version < 2) + return false; + + if (txSequence.uandn(constants.sequenceLocktimeDisableFlag).cmpn(0) !== 0) + return false; + + locktimeMask = new bn(constants.sequenceLocktimeTypeFlag) + .uorn(constants.sequenceLocktimeMask); + txSequenceMasked = txSequence.uand(locktimeMask); + sequenceMasked = sequence.uand(locktimeMask); + + if (!( + (txSequenceMasked.cmpn(constants.sequenceLocktimeTypeFlag) < 0 + && sequenceMasked.cmpn(constants.sequenceLocktimeTypeFlag) < 0 + || (txSequenceMasked.cmpn(constants.sequenceLocktimeTypeFlag) >= 0 + && sequenceMasked.cmpn(constants.sequenceLocktimeTypeFlag) >= 0 + )) { + return false; + } + + if (sequenceMasked.cmpn(txSequenceMasked) > 0) + return false; + + return true; +}; + script.bool = function bool(value) { var i;