Subject: [bitcoin-dev] Signing CHECKSIG position in Tapscript

From: "Russell O'Connor" <[email-masked]>
Date: Wed, 27 Nov 2019 16:29:32 -0500

Hi all,

I'd like to revisit an old topic from last year about the data signed in
tapscript signatures <
https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-November/016508.html
>.

The current tapscript proposal requires a signature on the last executed
CODESEPRATOR position.  I'd like to propose an amendment whereby instead of
signing the last executed CODESEPRATOR position, we simply always sign the
position of the CHECKSIG (or other signing opcode) being executed. Then we
can deprecate CODESEPARTOR (either by making it OP_SUCCESS, or a nop, or
always fail when executed, or whatever).

The main motivation for this proposal is to increase robustness against
various signature-copying attacks in Scripts that have multiple spending
conditions.  Bitcoin is already robust against attacks where the attacker
attempts to peddle a victim's UTXO as their own and try to copy the
victim's signature from one transaction input to another input.  Because
Bitcoin signatures specify which input within a transaction is being signed
for, such attacks fail (see https://bitcoin.stackexchange.com/a/85665/49364
).

However, unless CODESEPARATOR is explicitly used, there is no protection
against these sorts of attacks when there are multiple participants that
have signing conditions within a single UTXO (or rather within a single
tapleaf in the tapscript case).  As it stands, Bitcoin's signed data only
covers which input is being signed, and not the specific conditions are
being signed for.  So for example, if Alice and Bob are engaged in some
kind of multi-party protocol, and Alice wants to pre-sign a transaction
redeeming a UTXO but subject to the condition that a certain hash-preimage
is revealed, she might verify the Script template shows that the code path
to her public key enforces that the hash pre-image is revealed (using a
toolkit like miniscript can aid in this), and she might make her signature
feeling secure that it, if her signature is used, the required preimage
must be revealed on the blockchain.  But perhaps Bob has masquated Alice's
pubkey as his own, and maybe he has inserted a copy of Alice's pubkey into
a different path of the Script template.  Now Alice's signature can be
copied and used in this alternate path, allowing the UTXO to be redeemed
under circumstances that Alice didn't believe she was authorizing.  In
general, to protect herself, Alice needs to inspect the Script to see if
her pubkey occurs in any other branch.  Given that her pubkey, in
principle, could be derived from a computation rather that pushed directly
into the stack, it is arguably infeasible for Alice to perform the required
check in general.

I believe that it would be safer, and less surprising to users, to always
sign the CHECKSIG position by default.  This will automatically enforce
conditions with the signature in most cases, rather than requiring users to
proactively try to reason if CODESEPARATOR is required for protection
within their protocol or not, and risk having them leave it out for cost
savings when it ends up being required for security after all.

I do not believe signing the CHECKSIG position is an undue burden on those
signers who have no conditions they require enforcement for.  As it stands,
the tapscript proposal already requires the tapleaf_hash value under the
signature; this CHECKSIG position value is simply more of the same kind of
data.  In simple Script templates (e.g. those with only one CHECKSIG
operation) the signed position will be a fixed known value.  Complex Script
templates are precisely the situations where you want to be careful about
enforcement of conditions with your signature.

As a side benefit, we get to eliminate CODESEPARATOR, removing a fairly
awkward opcode from this script version.