Bitcoin Core is somewhat outside my core competence, but the various OP_PUSHDATA are already multi-byte opcodes and GetOp already has a data return parameter that is suitable for returning the payload of an immediate 32-byte data variant of OP_SECURETHEBAG.  All that I expect is needed is to ensure that nowhere else is using a non-empty data-field as a proxy for a non-empty push operation and fixing any such occurrences if they exist.  (AFAIKT there are only a handful of calls to GetOp).

It is probably worth updating the tapscript implementation to better prepare it for new uses of OP_SUCCESSx.  Parsing should halt when an OP_SUCCESSx is encountered, by having GetScriptOp advance the pc to end after encountering such a code (decoding Script is no longer meaningful after an OP_SUCCESS is encountered).  However, that means that GetScriptOp needs to know what version of script it is expected to be parsing.  This could be done by sending down some versioning flags, possibly by adding a versioning field to CScript that can be initialized @ https://github.com/sipa/bitcoin/blob/7ddc7027b2cbdd11416809400c588e585a8b44ed/src/script/interpreter.cpp#L1679 or some other mechanism (and at the same time perhaps having GetSigOpCount return 0 for tapscript, since counting sigops is not really meaningful in tapscript). There are probably other reasonable approaches too (e.g your option 2 below).  I could write some code to illustrate what I'm thinking if you feel that would be helpful and I do think such changes around OP_SUCCESS should be implemented regardless of whether we move forward with OP_SECURETHEBAG or not.

It is probably worth doing this properly the first time around if we are going to do it at all.

P.S. OP_RESERVED1 has been renamed to OP_SUCCESS137 in bip-tapscript.
 

On Mon, Jun 24, 2019 at 6:47 PM Jeremy <jlrubin@mit.edu> wrote:
I agree in principal, but I think that's just a bit of 'how things are' versus how they should be.

I disagree that we get composability semantics because of OP_IF. E.g., the script "OP_IF .... " and "OP_END" are two scripts that separately are invalid as parsed, but together are valid. OP_IF already imposes some lookahead functionality... but as I understand it, it may be feasible to get rid of OP_IF for tapscripts anyways. Also in this bucket are P2SH and segwit, which I think breaks this because the concat of two p2sh scripts or segwit scripts is not the same as them severally.

I also think that the OP_SECURETHEBAG use of pushdata is a backwards compatible hack: we can always later redefine the parser to parse OP_SECURETHEBAG as the 34 byte opcode, recapturing the purity of the semantics. We can also fix it to not use an extra byte in a future tapleaf version.

In any case, I don't disagree with figuring out what patching the parser to handle multibyte opcodes would look like. If that sort of upgrade-path were readily available when I wrote this, it's how I would have done it. There are two approaches I looked at mostly:

1) Adding flags to GetOp to change how it parses
  a) Most of the same code paths used for new and old script
  b) Higher risk of breaking something in old script style/downstream
  c) Cleans up only one issue (multibyte opcodes) leaves other warts in place
  d) less bikesheddable design (mostly same as old script)
  e) code not increased in size
2) Adding a completely new interpreter for Tapscript
  a) Fork the existing interpreter code
  b) For all places where scripts are run, switch based on if it is tapscript or not
  c) Can clean up various semantics, can even do fancier things like huffman encode opcodes to less than a byte
  d) Can clearly separate parsing the script from executing it
  e) Can improve versioning techniques
  f) Low risk of breaking something in old script style/downstream
  g) Increases amount of code substantially
  h) Bikesheddable design (everything is on the table).
  i) probably a better general mechanism for future changes to script parsing, less consensus risk
  j) More compatible with templated script as well.

If not clear, I think that 2 is probably a better approach, but I'm worried that 2.h means this would take a much longer time to implement.

2 can be segmented into two components:

1) the architecture of script parser versioning
2) the actual new script version

I think that component 1 can be relatively non controversial, thankfully, using tapleaf versions (the architecture question is more around code structure). A proof of concept of this would be to have a fork that uses two independent, but identical, script parsers.

Part two of this plan would be to modify one of the versions substantially. I'm not sure what exists on the laundry list, but I think it would be possible to pick a few worthwhile cleanups. E.g.:

1) Multibyte opcodes
2) Templated scripts
3) Huffman Encoding opcodes
4) OP_IF handling (maybe just get rid of it in favor of conditional Verify semantics)

And make it clear that because we can add future script versions fairly easily, this is a sufficient step.


Does that seem in line with your understanding of how this might be done?