Skip to content

Commit e6d98a7

Browse files
committed
make CHECKOUTPUTSVERIFY take stack arg, but check prev. executed opcode
Add prev_opcode variable that is set to the just executed opcode after main script eval switch. Make CHECKOUTPUTSVERIFY to check that prev_opcode > OP_PUSHDATA4 to ensure that the hash to be checked is not constructed dynamically
1 parent 4bc9ac9 commit e6d98a7

File tree

2 files changed

+33
-15
lines changed

2 files changed

+33
-15
lines changed

src/script/interpreter.cpp

+30-12
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
292292
CScript::const_iterator pend = script.end();
293293
CScript::const_iterator pbegincodehash = script.begin();
294294
opcodetype opcode;
295+
opcodetype prev_opcode = OP_INVALIDOPCODE;
295296
valtype vchPushValue;
296297
std::vector<bool> vfExec;
297298
std::vector<valtype> altstack;
@@ -774,18 +775,34 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
774775
{
775776
// Don't verify before enabled...
776777
if (flags & SCRIPT_VERIFY_OUTPUTS_HASH) {
777-
CScript::const_iterator lookahead = pc;
778-
opcodetype argument;
779-
// Read ahead one opcode as a lookahead argument
780-
if (!script.GetOp(lookahead, argument, vchPushValue))
781-
return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
782-
// If lookahead argument was exactly 32 bytes, check OutputHash
778+
779+
if (stack.size() < 1)
780+
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
781+
782+
// Previous executed operation must be a data push.
783+
//
784+
// NOTE:
785+
//
786+
// INVALID_STACK_OPERATION might not be appropriate error code
787+
// in this case, but this is closest thing in existing error codes.
788+
//
789+
// Making opcode behavior depend on the previous executed opcode
790+
// adds complexity to the script execution model, but the amount
791+
// of complexity added by this opcode-lookbehind mode is small,
792+
// and this is arguably better than making non-pushdata
793+
// opcodes use data lookahead, because this erodes the consistency
794+
// of the stack machine execution model. The argument here seems
795+
// to boil down to complexity vs model consistency.
796+
// Reducing consistency just externalizes the complexity outside:
797+
// to the users of the script and to the tools that operate on it.
798+
if (prev_opcode > OP_PUSHDATA4)
799+
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
800+
801+
valtype& vchHash = stacktop(-1);
802+
803+
// If stack argument was exactly 32 bytes, check OutputHash
783804
// This is so that we can later add different semantics for this opcode
784-
if (vchPushValue.size() == 32) {
785-
// Argument should be == 0x20 -- will fail later anyways
786-
if (!CheckMinimalPush(vchPushValue, argument)) {
787-
return set_error(serror, SCRIPT_ERR_MINIMALDATA);
788-
}
805+
if (vchHash.size() == 32) {
789806
// If multiple inputs allowed, two inputs with the same OutputsHashVerify
790807
// would pay only half intended amount!
791808
if (!checker.CheckOnlyOneInput()) {
@@ -796,7 +813,6 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
796813
return set_error(serror, SCRIPT_ERR_OUTPUTSHASHVERIFY);
797814
}
798815
}
799-
800816
}
801817
}
802818
break;
@@ -1155,6 +1171,8 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
11551171
if (stack.size() + altstack.size() > MAX_STACK_SIZE)
11561172
return set_error(serror, SCRIPT_ERR_STACK_SIZE);
11571173

1174+
prev_opcode = opcode;
1175+
11581176
++opcode_pos;
11591177
}
11601178
}

test/functional/feature_outputshashverify.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323
def random_bytes(n):
2424
return bytes(random.getrandbits(8) for i in range(n))
2525
def random_fake_script():
26-
return CScript([OP_CHECKOUTPUTSHASHVERIFY, random_bytes(32)])
26+
return CScript([random_bytes(32), OP_CHECKOUTPUTSHASHVERIFY])
2727
def random_real_outputs_and_script(n):
2828
outputs = [CTxOut((x+1)*100, CScript(bytes([OP_RETURN, 0x20]) + random_bytes(32))) for x in range(n)]
29-
return outputs, CScript(bytes([OP_CHECKOUTPUTSHASHVERIFY, 0x20]) + hash256(b"".join(o.serialize() for o in outputs)))
29+
return outputs, CScript([hash256(b"".join(o.serialize() for o in outputs)), OP_CHECKOUTPUTSHASHVERIFY])
3030

3131
def random_tapscript_tree(depth):
3232

@@ -39,7 +39,7 @@ def random_tapscript_tree(depth):
3939
for d in range(1, depth+2):
4040
idxs =zip(range(0, len(outputs_tree[-d]),2), range(1, len(outputs_tree[-d]), 2))
4141
for (idx, (a,b)) in enumerate([(outputs_tree[-d][i], outputs_tree[-d][j]) for (i,j) in idxs]):
42-
s = CScript(bytes([OP_CHECKOUTPUTSHASHVERIFY, 0x20]) + hash256(b"".join(o.serialize() for o in [a,b])))
42+
s = CScript([hash256(b"".join(o.serialize() for o in [a,b])), OP_CHECKOUTPUTSHASHVERIFY])
4343
a = sum(o.nValue for o in [a,b])
4444
taproot, tweak, controls = taproot_construct(pubkey1, [s])
4545
t = CTxOut(a+1000, taproot)

0 commit comments

Comments
 (0)