Skip to content

Commit fa09f7f

Browse files
committed
[BIP-119] Reimplement CTV in higher level pythonic pseduocode and clarify DoS Caching requirements.
1 parent ba648bc commit fa09f7f

File tree

1 file changed

+108
-22
lines changed

1 file changed

+108
-22
lines changed

bip-0119.mediawiki

Lines changed: 108 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,83 @@ forming a "Payment Pool".
161161

162162
==Detailed Specification==
163163

164-
The below code is the main logic for verifying CHECKTEMPLATEVERIFY, and is the canonical
165-
specification for the semantics of OP_CHECKTEMPLATEVERIFY.
164+
The below code is the main logic for verifying CHECKTEMPLATEVERIFY, described
165+
in pythonic pseduocode. The canonical specification for the semantics of
166+
OP_CHECKTEMPLATEVERIFY can be seen in the reference implementations.
167+
168+
The execution of the opcode is as follows:
169+
def execute_bip_119(self):
170+
# Before soft-fork activation / failed activation
171+
if not self.flags.script_verify_default_check_template_verify_hash:
172+
# Potentially set for node-local policy to discourage premature use
173+
if self.flags.script_verify_discourage_upgradable_nops:
174+
return self.errors_with(errors.script_err_discourage_upgradable_nops)
175+
return self.return_as_nop()
176+
# CTV always requires at least one stack argument
177+
if len(self.stack) < 1:
178+
return self.errors_with(errors.script_err_invalid_stack_operation)
179+
# CTV only verifies the hash against a 32 byte argument
180+
if len(self.stack[-1]) == 32:
181+
# Ensure the precomputed data required for anti-DoS is available,
182+
# or cache it on first use
183+
if self.context.precomputed_ctv_data == None:
184+
self.context.precomputed_ctv_data = self.context.tx.get_default_check_template_precomputed_data()
185+
if stack[-1] != self.context.tx.get_default_check_template_hash(self.context.nIn, self.context.precomputed_ctv_data)
186+
return self.errors_with(errors.script_err_template_mismatch)
187+
return self.return_as_nop()
188+
# future upgrade can add semantics for this opcode with different length args
189+
# so discourage use when applicable
190+
if self.flags.script_verify_discourage_upgradable_nops:
191+
return self.errors_with(errors.script_err_discourage_upgradable_nops)
192+
else:
193+
return self.return_as_nop()
194+
195+
The computation of this hash can be implemented as specified below (where self
196+
is the transaction type). Care must be taken that in any validation context,
197+
the precomputed data must be initialized to prevent Denial-of-Service attacks.
198+
Any implementation *must* cache these parts of the hash computation to avoid
199+
quadratic hashing DoS. All variable length computations must be precomputed
200+
including hashes of the scriptsigs, sequences, and outputs. See the section
201+
"Denial of Service and Validation Costs" below. This is not a performance
202+
optimization.
203+
204+
def get_default_check_template_precomputed_data(self):
205+
result = {}
206+
# If there are no scriptSigs we do not need to precompute a hash
207+
if any(inp.scriptSig for inp in self.vin):
208+
result["scriptSigs"] = sha256(b"".join(ser_string(inp.scriptSig) for inp in self.vin))
209+
# The same value is also pre-computed for and defined in BIP-341 and can be shared
210+
result["sequences"] = sha256(b"".join(struct.pack("<I", inp.nSequence) for inp in self.vin))
211+
# The same value is also pre-computed for and defined in BIP-341 and can be shared
212+
result["outputs"] = sha256(b"".join(out.serialize() for out in self.vout))
213+
return result
214+
215+
# parameter precomputed must be passed in for DoS resistance
216+
def get_default_check_template_hash(self, nIn, precomputed = None):
217+
if precomputed == None:
218+
precomputed = self.get_default_check_template_precomputed_data()
219+
r = b""
220+
# pack as 4 byte signed integer
221+
r += struct.pack("<i", self.nVersion)
222+
# pack as 4 byte unsigned integer
223+
r += struct.pack("<I", self.nLockTime)
224+
# we do not include the hash in the case where there is no
225+
# scriptSigs
226+
if "scriptSigs" in precomputed:
227+
r += precomputed["scriptSigs"]
228+
# pack as 4 byte unsigned integer
229+
r += struct.pack("<I", len(self.vin))
230+
r += precomputed["sequences"]
231+
# pack as 4 byte unsigned integer
232+
r += struct.pack("<I", len(self.vout))
233+
r += precomputed["outputs"]
234+
# pack as 4 byte unsigned integer
235+
r += struct.pack("<I", nIn)
236+
return sha256(r)
237+
238+
239+
240+
The C++ is below:
166241

167242
case OP_CHECKTEMPLATEVERIFY:
168243
{
@@ -196,10 +271,6 @@ specification for the semantics of OP_CHECKTEMPLATEVERIFY.
196271
Where
197272

198273
bool CheckDefaultCheckTemplateVerifyHash(const std::vector<unsigned char>& hash) {
199-
// note: for anti-DoS, a real implementation *must* cache parts of this computation
200-
// to avoid quadratic hashing DoS all variable length computations must be precomputed
201-
// including hashes of the scriptsigs, sequences, and outputs. See the section
202-
// "Denial of Service and Validation Costs" below.
203274
return GetDefaultCheckTemplateVerifyHash(current_tx, current_input_index) == uint256(hash);
204275
}
205276
@@ -255,20 +326,37 @@ The hash is computed as follows, where the outputs_hash and sequences_hash are c
255326
return h.GetSHA256();
256327
}
257328
258-
In python, this can be written as (but note this implementation is DoS-able).
259329

260-
def get_default_check_template_hash(self, nIn):
261-
r = b""
262-
r += struct.pack("<i", self.nVersion)
263-
r += struct.pack("<I", self.nLockTime)
264-
if any(inp.scriptSig for inp in self.vin):
265-
r += sha256(b"".join(ser_string(inp.scriptSig) for inp in self.vin))
266-
r += struct.pack("<I", len(self.vin))
267-
r += sha256(b"".join(struct.pack("<I", inp.nSequence) for inp in self.vin))
268-
r += struct.pack("<I", len(self.vout))
269-
r += sha256(b"".join(out.serialize() for out in self.vout))
270-
r += struct.pack("<I", nIn)
271-
return sha256(r)
330+
331+
case OP_CHECKTEMPLATEVERIFY:
332+
{
333+
// if flags not enabled; treat as a NOP4
334+
if (!(flags & SCRIPT_VERIFY_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH)) {
335+
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS)
336+
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS);
337+
break;
338+
}
339+
340+
if (stack.size() < 1)
341+
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
342+
343+
// If the argument was not 32 bytes, treat as OP_NOP4:
344+
switch (stack.back().size()) {
345+
case 32:
346+
if (!checker.CheckDefaultCheckTemplateVerifyHash(stack.back())) {
347+
return set_error(serror, SCRIPT_ERR_TEMPLATE_MISMATCH);
348+
}
349+
break;
350+
default:
351+
// future upgrade can add semantics for this opcode with different length args
352+
// so discourage use when applicable
353+
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) {
354+
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS);
355+
}
356+
}
357+
}
358+
359+
272360

273361
A PayToBareDefaultCheckTemplateVerifyHash output matches the following template:
274362

@@ -567,9 +655,7 @@ is O(T) (the size of the transaction).
567655

568656
An example of a script that could experience an DoS issue without caching is:
569657

570-
```
571-
<H> CTV CTV CTV... CTV
572-
```
658+
<H> CTV CTV CTV... CTV
573659
574660
Such a script would cause the intepreter to compute hashes (supposing N CTV's) over O(N*T) data.
575661
If the scriptSigs non-nullity is not cached, then the O(T) transaction could be scanned over O(N)

0 commit comments

Comments
 (0)