From f2d5631be044d9506efc022c8194d4dc04ff0915 Mon Sep 17 00:00:00 2001 From: Mark Friedenbach Date: Mon, 18 May 2015 17:20:46 -0700 Subject: [PATCH] Replace NOP3 with CHECKSEQUENCEVERIFY (BIPXX) CHECKSEQUENCEVERIFY -> Fails if ~txin.nSequence < nInvSequence (the nSequence field of the txin is bit-inverted before the comparison), allowing funds of a txout to be locked for a number of blocks or a duration of time after its inclusion in a block. Only the logic and unit tests are implemented; this commit does not have changes to policy or consensus behavior. --- src/script/interpreter.cpp | 74 +++++++++++++++++++++++++++++++--- src/script/interpreter.h | 7 +++- src/script/script.h | 1 + src/script/script_error.h | 2 +- src/test/data/tx_invalid.json | 73 +++++++++++++++++++++++++++++++++ src/test/data/tx_valid.json | 40 ++++++++++++++++++ src/test/transaction_tests.cpp | 3 +- 7 files changed, 190 insertions(+), 10 deletions(-) diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 515e53803c..3aa897988e 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -379,7 +379,47 @@ bool EvalScript(vector >& stack, const CScript& script, un break; } - case OP_NOP1: case OP_NOP3: case OP_NOP4: case OP_NOP5: + case OP_CHECKSEQUENCEVERIFY: + { + if (!(flags & SCRIPT_VERIFY_CHECKSEQUENCEVERIFY)) { + // not enabled; treat as a NOP3 + if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { + return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS); + } + break; + } + + if (stack.size() < 1) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + // Note that unlike CHECKLOCKTIMEVERIFY we do not need to + // accept 5-byte bignums since any value greater than or + // equal to SEQUENCE_THRESHOLD (= 1 << 31) will be rejected + // anyway. This limitation just happens to coincide with + // CScriptNum's default 4-byte limit with an explicit sign + // bit. + // + // This means there is a maximum relative lock time of 52 + // years, even though the nSequence field in transactions + // themselves is uint32_t and could allow a relative lock + // time of up to 120 years. + const CScriptNum nInvSequence(stacktop(-1), fRequireMinimal); + + // 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 (nInvSequence < 0) + return set_error(serror, SCRIPT_ERR_NEGATIVE_LOCKTIME); + + // Actually compare the specified inverse sequence number + // with the input. + if (!checker.CheckLockTime(nInvSequence, true)) + return set_error(serror, SCRIPT_ERR_UNSATISFIED_LOCKTIME); + + break; + } + + case OP_NOP1: case OP_NOP4: case OP_NOP5: case OP_NOP6: case OP_NOP7: case OP_NOP8: case OP_NOP9: case OP_NOP10: { if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) @@ -1126,8 +1166,30 @@ bool TransactionSignatureChecker::CheckSig(const vector& vchSigIn return true; } -bool TransactionSignatureChecker::CheckLockTime(const CScriptNum& nLockTime) const +bool TransactionSignatureChecker::CheckLockTime(const CScriptNum& nLockTime, bool fSequence) const { + // Relative lock times are supported by comparing the passed + // in lock time to the sequence number of the input. All other + // logic is the same, all that differs is what we are comparing + // the lock time to. + int64_t txToLockTime; + if (fSequence) { + // Fail under all circumstances if the transaction's version + // number is not set high enough to enable enforced sequence + // number rules. + if (txTo->nVersion < 2) + return false; + // Sequence number must be inverted to convert it into a + // relative lock-time. + txToLockTime = (int64_t)~txTo->vin[nIn].nSequence; + // Sequence numbers under SEQUENCE_THRESHOLD are not consensus + // constrained. + if (txToLockTime >= SEQUENCE_THRESHOLD) + return false; + } else { + txToLockTime = (int64_t)txTo->nLockTime; + } + // There are two types of nLockTime: lock-by-blockheight // and lock-by-blocktime, distinguished by whether // nLockTime < LOCKTIME_THRESHOLD. @@ -1136,14 +1198,14 @@ bool TransactionSignatureChecker::CheckLockTime(const CScriptNum& nLockTime) con // unless the type of nLockTime being tested is the same as // the nLockTime in the transaction. if (!( - (txTo->nLockTime < LOCKTIME_THRESHOLD && nLockTime < LOCKTIME_THRESHOLD) || - (txTo->nLockTime >= LOCKTIME_THRESHOLD && nLockTime >= LOCKTIME_THRESHOLD) + (txToLockTime < LOCKTIME_THRESHOLD && nLockTime < LOCKTIME_THRESHOLD) || + (txToLockTime >= LOCKTIME_THRESHOLD && nLockTime >= LOCKTIME_THRESHOLD) )) return false; // Now that we know we're comparing apples-to-apples, the // comparison is a simple numeric one. - if (nLockTime > (int64_t)txTo->nLockTime) + if (nLockTime > txToLockTime) return false; // Finally the nLockTime feature can be disabled and thus @@ -1156,7 +1218,7 @@ bool TransactionSignatureChecker::CheckLockTime(const CScriptNum& nLockTime) con // prevent this condition. Alternatively we could test all // inputs, but testing just this input minimizes the data // required to prove correct CHECKLOCKTIMEVERIFY execution. - if (txTo->vin[nIn].IsFinal()) + if (!fSequence && txTo->vin[nIn].IsFinal()) return false; return true; diff --git a/src/script/interpreter.h b/src/script/interpreter.h index 4d103ef848..acd3e25213 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -80,6 +80,9 @@ enum // Verify CHECKLOCKTIMEVERIFY (BIP65) // SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY = (1U << 9), + + // support CHECKSEQUENCEVERIFY opcode + SCRIPT_VERIFY_CHECKSEQUENCEVERIFY = (1U << 10), }; uint256 SignatureHash(const CScript &scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType); @@ -92,7 +95,7 @@ public: return false; } - virtual bool CheckLockTime(const CScriptNum& nLockTime) const + virtual bool CheckLockTime(const CScriptNum& nLockTime, bool fSequence = false) const { return false; } @@ -112,7 +115,7 @@ protected: public: TransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn) : txTo(txToIn), nIn(nInIn) {} bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode) const; - bool CheckLockTime(const CScriptNum& nLockTime) const; + bool CheckLockTime(const CScriptNum& nLockTime, bool fSequence = false) const; }; class MutableTransactionSignatureChecker : public TransactionSignatureChecker diff --git a/src/script/script.h b/src/script/script.h index d15b9c7550..0afbb517db 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -161,6 +161,7 @@ enum opcodetype OP_NOP2 = 0xb1, OP_CHECKLOCKTIMEVERIFY = OP_NOP2, OP_NOP3 = 0xb2, + OP_CHECKSEQUENCEVERIFY = OP_NOP3, OP_NOP4 = 0xb3, OP_NOP5 = 0xb4, OP_NOP6 = 0xb5, diff --git a/src/script/script_error.h b/src/script/script_error.h index bf205502c2..26df33932f 100644 --- a/src/script/script_error.h +++ b/src/script/script_error.h @@ -35,7 +35,7 @@ typedef enum ScriptError_t SCRIPT_ERR_INVALID_ALTSTACK_OPERATION, SCRIPT_ERR_UNBALANCED_CONDITIONAL, - /* CheckLockTime */ + /* CHECKLOCKTIMEVERIFY and CHECKSEQUENCEVERIFY */ SCRIPT_ERR_NEGATIVE_LOCKTIME, SCRIPT_ERR_UNSATISFIED_LOCKTIME, diff --git a/src/test/data/tx_invalid.json b/src/test/data/tx_invalid.json index 836a50d0ec..cbbc6d38eb 100644 --- a/src/test/data/tx_invalid.json +++ b/src/test/data/tx_invalid.json @@ -185,5 +185,78 @@ [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0xc5b93064159b3b2d6ab506a41b1f50463771b988 EQUAL"]], "0100000001000100000000000000000000000000000000000000000000000000000000000000000000030251b1000000000100000000000000000000000000", "P2SH,CHECKLOCKTIMEVERIFY"], +["CHECKSEQUENCEVERIFY tests"], + +["By-height locks, with argument just beyond ~txin.nSequence"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "1 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "2 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "499999999 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000019b32e20100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["By-time locks, with argument just beyond ~txin.nSequence (but within numerical boundries)"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000001 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000ff9a32e20100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4294967295 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000010000000100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Argument missing"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Argument negative with by-blockheight ~txin.nSequence=0"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "-1 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Argument negative with by-blocktime ~txin.nSequence=500,000,000"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "-1 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000ff9a32e20100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Another input being unlocked isn't sufficient; the CHECKSEQUENCEVERIFY-using input must be unlocked"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 NOP3 1"] , + ["0000000000000000000000000000000000000000000000000000000000000200", 1, "0 NOP3 1"]], +"030000000200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00020000000000000000000000000000000000000000000000000000000000000100000000000000000100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Argument/tx height/time mismatch, both versions"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000ff9a32e20100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "499999999 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000ff9a32e20100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000000 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000000 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000009b32e20100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Argument 2^32 with nLockTime=2^32-1"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4294967296 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Same, but with nLockTime=2^31-1"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "2147483648 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000000000800100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["An ADD producing a 5-byte result that is out of bounds"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "2147483647 1ADD NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000000000800100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["5 byte non-minimally-encoded arguments are invalid even if their contents are valid"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x05 0x0000000000 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000000000080100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Failure due to failing CHECKSEQUENCEVERIFY in scriptSig"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "1"]], +"03000000010001000000000000000000000000000000000000000000000000000000000000000000000251b2ffffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Failure due to failing CHECKSEQUENCEVERIFY in redeemScript"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x7c17aff532f22beb54069942f9bf567a66133eaf EQUAL"]], +"0300000001000100000000000000000000000000000000000000000000000000000000000000000000030251b2ffffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Failure due to insufficient tx.nVersion (<=2)"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 NOP3 1"]], +"010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000000 NOP3 1"]], +"010000000100010000000000000000000000000000000000000000000000000000000000000000000000ff9a32e20100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + ["Make diffs cleaner by leaving a comment here without comma at the end"] ] diff --git a/src/test/data/tx_valid.json b/src/test/data/tx_valid.json index 24fff575c1..75268a04dd 100644 --- a/src/test/data/tx_valid.json +++ b/src/test/data/tx_valid.json @@ -229,5 +229,45 @@ [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0xc5b93064159b3b2d6ab506a41b1f50463771b988 EQUAL"]], "0100000001000100000000000000000000000000000000000000000000000000000000000000000000030251b1000000000100000000000000000001000000", "P2SH,CHECKLOCKTIMEVERIFY"], +["CHECKSEQUENCEVERIFY tests"], + +["By-height locks, with argument == 0 and == ~txin.nSequence"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "499999999 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000009b32e20100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000009b32e20100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["By-time locks, with argument just beyond ~txin.nSequence (but within numerical boundries)"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000000 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000ff9a32e20100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "2147483647 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000000000800100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000000 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000000000800100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Any non-maxint nSequence is fine"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "1 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Input locked"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["The argument can be calculated rather than created directly by a PUSHDATA"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "499999999 1ADD NOP3 1"]], +"030000000100010000000000000000000000000000000000000000000000000000000000000000000000ff9a32e20100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Valid CHECKSEQUENCEVERIFY in scriptSig"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "1"]], +"03000000010001000000000000000000000000000000000000000000000000000000000000000000000251b2feffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Valid CHECKSEQUENCEVERIFY in redeemScript"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x7c17aff532f22beb54069942f9bf567a66133eaf EQUAL"]], +"0300000001000100000000000000000000000000000000000000000000000000000000000000000000030251b2feffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + ["Make diffs cleaner by leaving a comment here without comma at the end"] ] diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 68ebe8382c..92d7822097 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -41,7 +41,8 @@ static std::map mapFlagNames = boost::assign::map_list_of (string("NULLDUMMY"), (unsigned int)SCRIPT_VERIFY_NULLDUMMY) (string("DISCOURAGE_UPGRADABLE_NOPS"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) (string("CLEANSTACK"), (unsigned int)SCRIPT_VERIFY_CLEANSTACK) - (string("CHECKLOCKTIMEVERIFY"), (unsigned int)SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY); + (string("CHECKLOCKTIMEVERIFY"), (unsigned int)SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY) + (string("CHECKSEQUENCEVERIFY"), (unsigned int)SCRIPT_VERIFY_CHECKSEQUENCEVERIFY); unsigned int ParseScriptFlags(string strFlags) {