Skip to content

Commit

Permalink
New feature: tail-call recursion -- loop P2SH-style if stack is not c…
Browse files Browse the repository at this point in the history
…lean.

This commit implements tail-call recursion for fully expressive scripting capabilities. If, at the end of script execution, the stack has more than one item on it, the top-most element is interpreted as a serialized script and executed, with the remaining elements on the stack as inputs. No state is maintained, but that stack itself can be used to carry state to create other more complex recursion primitives.
  • Loading branch information
maaku committed Jul 31, 2017
1 parent cc4465e commit 10d5f90
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 12 deletions.
3 changes: 2 additions & 1 deletion src/policy/policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ static const unsigned int STANDARD_SCRIPT_VERIFY_FLAGS = MANDATORY_SCRIPT_VERIFY
SCRIPT_VERIFY_WITNESS |
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM |
SCRIPT_VERIFY_WITNESS_PUBKEYTYPE |
SCRIPT_VERIFY_INCREASE_CONFIRMATIONS_REQUIRED;
SCRIPT_VERIFY_INCREASE_CONFIRMATIONS_REQUIRED |
SCRIPT_VERIFY_TAIL_CALL;

/** For convenience, standard but not mandatory verify flags. */
static const unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS = STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS;
Expand Down
33 changes: 23 additions & 10 deletions src/script/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ bool static WithdrawProofReadStackItem(const vector<valtype>& stack, const bool
return true;
}

bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror)
bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& scriptIn, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror)
{
static const CScriptNum bnZero(0);
static const CScriptNum bnOne(1);
Expand All @@ -333,6 +333,8 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, un
static const valtype vchZero(0);
static const valtype vchTrue(1, 1);

CScript script = scriptIn;
tailcall:
CScript::const_iterator pc = script.begin();
CScript::const_iterator pend = script.end();
CScript::const_iterator pbegincodehash = script.begin();
Expand Down Expand Up @@ -1711,6 +1713,15 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, un
if (!vfExec.empty())
return set_error(serror, SCRIPT_ERR_UNBALANCED_CONDITIONAL);

bool fAllowTailCall = (flags & SCRIPT_VERIFY_TAIL_CALL) != 0;
if (fAllowTailCall && (stack.size() >= 2))
{
const valtype& pubKeySerialized = stacktop(-1);
script = CScript(pubKeySerialized.begin(), pubKeySerialized.end());
popstack(stack);
goto tailcall;
}

return set_success(serror);
}

Expand Down Expand Up @@ -2220,6 +2231,8 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
witness = &emptyWitness;
}
bool hadWitness = false;
int witnessversion;
std::vector<unsigned char> witnessprogram;

set_error(serror, SCRIPT_ERR_UNKNOWN_ERROR);

Expand All @@ -2239,13 +2252,15 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
flags &= ~SCRIPT_VERIFY_WITHDRAW;
}

hadWitness = scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram);

vector<vector<unsigned char> > stack, stackCopy;
if (!EvalScript(stack, scriptSig, flags, checker, SIGVERSION_BASE, serror))
if (!EvalScript(stack, scriptSig, (flags & ~SCRIPT_VERIFY_TAIL_CALL), checker, SIGVERSION_BASE, serror))
// serror is set
return false;
if (flags & SCRIPT_VERIFY_P2SH)
stackCopy = stack;
if (!EvalScript(stack, scriptPubKey, flags, checker, SIGVERSION_BASE, serror))
if (!EvalScript(stack, scriptPubKey, (scriptPubKey.IsPayToScriptHash() || hadWitness) ? (flags & ~SCRIPT_VERIFY_TAIL_CALL) : flags, checker, SIGVERSION_BASE, serror))
// serror is set
return false;
if (stack.empty())
Expand All @@ -2254,11 +2269,8 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
return set_error(serror, SCRIPT_ERR_EVAL_FALSE);

// Bare witness programs
int witnessversion;
std::vector<unsigned char> witnessprogram;
if (flags & SCRIPT_VERIFY_WITNESS) {
if (scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
hadWitness = true;
if (hadWitness) {
if (scriptSig.size() != 0) {
// The scriptSig must be _exactly_ CScript(), otherwise we reintroduce malleability.
return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED);
Expand Down Expand Up @@ -2291,7 +2303,9 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
CScript pubKey2(pubKeySerialized.begin(), pubKeySerialized.end());
popstack(stack);

if (!EvalScript(stack, pubKey2, flags, checker, SIGVERSION_BASE, serror))
hadWitness = pubKey2.IsWitnessProgram(witnessversion, witnessprogram);

if (!EvalScript(stack, pubKey2, hadWitness ? (flags & ~SCRIPT_VERIFY_TAIL_CALL) : flags, checker, SIGVERSION_BASE, serror))
// serror is set
return false;
if (stack.empty())
Expand All @@ -2301,8 +2315,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C

// P2SH witness program
if (flags & SCRIPT_VERIFY_WITNESS) {
if (pubKey2.IsWitnessProgram(witnessversion, witnessprogram)) {
hadWitness = true;
if (hadWitness) {
if (scriptSig != CScript() << std::vector<unsigned char>(pubKey2.begin(), pubKey2.end())) {
// The scriptSig must be _exactly_ a single push of the redeemScript. Otherwise we
// reintroduce malleability.
Expand Down
3 changes: 3 additions & 0 deletions src/script/interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ enum
// Allow inputs or outputs of a transaction to be selectively signed
// by means of a bitfield in the signature itself.
SCRIPT_VERIFY_BITMASK_SIGNATURE = (1U << 18),

// Tail call recursion
SCRIPT_VERIFY_TAIL_CALL = (1U << 19),
};

bool CheckSignatureEncoding(const std::vector<unsigned char> &vchSig, unsigned int flags, ScriptError* serror);
Expand Down
4 changes: 3 additions & 1 deletion src/script/standard.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ extern unsigned nMaxDatacarrierBytes;
* them to be valid. (but old blocks may not comply with) Currently just P2SH,
* but in the future other flags may be added, such as a soft-fork to enforce
* strict DER encoding.
*
* On elements, tail call evaluation is manditory as well.
*
* Failing one of these tests may trigger a DoS ban - see CheckInputs() for
* details.
*/
static const unsigned int MANDATORY_SCRIPT_VERIFY_FLAGS = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | SCRIPT_VERIFY_WITHDRAW;
static const unsigned int MANDATORY_SCRIPT_VERIFY_FLAGS = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | SCRIPT_VERIFY_WITHDRAW | SCRIPT_VERIFY_TAIL_CALL;

enum txnouttype
{
Expand Down

0 comments on commit 10d5f90

Please sign in to comment.