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.