[bitcoin-dev] CTV through SIGHASH flags

Bob McElrath bob at mcelrath.org
Sat Feb 1 20:39:42 UTC 2020


We propose that OP_CHECKTEMPLATEVERIFY should behave more like CHECKSIG,
including a flags byte that specify what is hashed. This unifies the ways a
SigHash is computed, differing only in the final checksig which is omitted in
favor of chacking the hash directly. Having two paths to create a signature hash
introduces extra complexity, especially as concerns potential future SIGHASH
flag upgrades.

CTV omits inputs as part of its semantics, so CTV-type functionality using
CHECKSIG is also achievable if some form of NOINPUT flag is also deployed. With
NOINPUT alone, a standard CHECKSIG can be used to implement a covenant -- though
it uses an unnecessarily large number of bytes just to check a 32-byte hash.
Therefore, any pitfalls CTV intends to evade can be evaded by using a CHECKSIG,
if NOINPUT is deployed in some form, adding new flexibility.  Beyond what's
possible with NOINPUT/ANYPREVOUT, CTV additionally commits to: 

»·······1. Number of inputs
»·······2. Number of outputs
»·······3. Index of input

The justification given for committing to the number of inputs and outputs is
that "it makes CTV hashes easier to compute with script", however doing so would
require OP_CAT. It's noted that both of these are actually redundant
commitments. Since the constexpr requirement was removed, if OP_CAT were
enabled, this commitment to the input index could be evaded by computing the CTV
hash within the script, modifying the input index using data taken from the
witness. Therefore committing to the input index is a sender-specified-policy
choice, and not an anti-footgun measure for the redeemer. As such, it's
appropriate to consider committing to the input index using a flag instead.

I believe the above may be possible *without* a new opcode and simply with a
sighash flag. That is, consider a flag SIGHASH_NOSIG which behaves as follows:
The stack is expected to contain <hash> <flags>, where the hash to be checked is
<hash> and is in the place where you'd normally put a pubkey. The byte <flags>
is the second thing on the stack. This is intended to be an empty "signature"
with the flags byte appended (which must contain SIGHASH_NOSIG to succeed).

There are probably reasons this might not work as a flag that I haven't
discovered yet. Alternatively CTV might be considered to be an alternative type
of CHECKSIG operator and might be renamed to CHECKSIGHASH, appending flag bytes
to the hash to be checked.

The flags discussed above, NOINPUT, NOSIG, INPUTINDEX are all really
sender-policy choices, while SIGHASH flags are redeemer-choice as they usually
occur in the witness. There's really no way currently for an output to specify
that the redeemer must use a particular set of flags. One way to achieve this is
to put the CHECKSIG(HASH) including its flags into the redeemScript -- which is
functionally what CTV does (or a CHECKSIG in a redeemScript using NOINPUT).
This is committed to in outputs and therefore specifies sender policies, however
the redeemScript is specified by the receiver.  Perhaps an anti-footgun measure
would be to require that certain SIGHASH flags like these MUST be committed to
in the output, by the sender.

CSV (CHECKSEQUENCEVERIFY) is an example that redemption policies are committed
to in the output by the sender via the sequence_no field, and then checked with
an opcode OP_CSV to enable relative timelocks. It's probably possible to add new
flags to the sequence_no field, and check the new semantics with CSV instead of
an entiely new opcode.

As user policy choices, NOINPUT might be considered "MAY" conditions. A user MAY
use NOINPUT on an output, but let's not require it.  Covenants on the other
hand, are a MUST condition. The CTV proposal imposes "MUST" conditions on the
existence of the covenant itself, as well as the number of inputs and outputs,
to prevent txid malleability so that such transactions can be used in offline
protocols. Txid non-malleability can be achieved by enforcing that the output
must be spent using SIGHASH_ALL instead of committing to the number of inputs
separately with a new opcode. The MUST condition also helps with sighash caching
for batch validation.

INPUTINDEX is required in a CTV/CHECKSIGHASH world because of the half-spend
problem. Normally outputs are spent uniquely as long as different addresses are
used on the outputs. A transaction with the same address appearing twice would
also have a half-spend problem. Anyone signing the first output and giving that
PSBT to another person can allow them to spend the second input. Therefore one
might even want INPUTINDEX for non-covenant transactions, though making a tx
with the same address twice seems like a silly idea to me.

Therefore, assuming a CSV-type mechanism can be devised using sequence_no, CTV
is equivalent to a flag in sequence_no that is logically
MUST|ALL|NOSIG|INPUTINDEX and a redeemScript of <hash> <flags>.

Lightning-like use cases might put sequence_no flags that are logically
MAY|ALL|NOINPUT.

The other mechanism for sender policy is scriptPubKey, which is heavily
restricted by isStandard, but might be another place to specify flags like the
above.

Thoughts? 

Does this idea address any of the NOINPUT footguns? (which I'm not up on) 
Is there a reason this cannot be done with sequence_no and OP_CSV?
Is there a reason that a separate opcode (CTV) is different/better than this
approach?

--
Cheers, Bob McElrath

"For every complex problem, there is a solution that is simple, neat, and wrong."
    -- H. L. Mencken 



More information about the bitcoin-dev mailing list