{"type":"rich","version":"1.0","author_name":"npub103ycruxnchhvja33mcnnkfdkgd0s7vlqlfkvufcdm5lnhpuh6f4q82kpam","author_url":"https://nostr.ae/npub103ycruxnchhvja33mcnnkfdkgd0s7vlqlfkvufcdm5lnhpuh6f4q82kpam","provider_name":"njump","provider_url":"https://nostr.ae","html":"📅 Original date posted:2023-05-01\n🗒️ Summary of this message: A proposal to add new opcode functionality to existing scripts with no deeper introspection needed, similar to APO and CTV proposals.\n📝 Original message:Hi Salvatore\n\nCan you clarify for me which bucket this proposal sits? We have APO, CTV, OP_VAULT etc that are proposals to add additional functionality to SegWit version 1, Tapleaf version 0 scripts. We have Simplicity that would need a new Tapleaf version (e.g. Tapleaf version 1). And then there are CISA like proposals that would need a new SegWit version (e.g. SegWit version 2). It looks to me like your proposal is in the first bucket (same as APO, CTV etc) as it is just introducing new opcode functionality to existing script with no deeper introspection needed but previous and current discussion of fraud proofs, MATT frameworks etc made me initially think it was going to require more than that.\n\nThanks\nMichael\n\n--\nMichael Folkson\nEmail: michaelfolkson at [protonmail.com](http://protonmail.com/)\nGPG: A2CF5D71603C92010659818D2A75D601B23FEE0F\n\nLearn about Bitcoin: https://www.youtube.com/@portofbitcoin\n\n------- Original Message -------\nOn Monday, April 24th, 2023 at 20:37, Salvatore Ingala via bitcoin-dev \u003cbitcoin-dev at lists.linuxfoundation.org\u003e wrote:\n\n\u003e Hello list,\n\u003e\n\u003e TL;DR: the core opcodes of MATT can build vaults with a very similar design\n\u003e to OP_VAULT. Code example here:\n\u003e\n\u003e https://github.com/bitcoin-inquisition/bitcoin/compare/24.0...bigspider:bitcoin-inquisition:matt-vault\n\u003e\n\u003e In my previous emails about the MATT proposal for smart contracts in\n\u003e bitcoin [1], I mostly focused on proving its generality; that is, it\n\u003e allows arbitrary smart contracts thanks to fraud proofs.\n\u003e\n\u003e While I still find this \"completeness\" result compelling, I spent more time\n\u003e thinking about the framework itself; the construction is not very interesting\n\u003e if it turns simple things into complicated ones. Luckily, this is not the case.\n\u003e In particular, in this email we will not merkleize anything (other than taptrees).\n\u003e\n\u003e This post describes some progress into formalizing the semantics of the core\n\u003e opcodes, and demonstrates how they could be used to create vaults that seem\n\u003e comparable to the ones built with OP_VAULT [2], despite using general purpose\n\u003e opcodes.\n\u003e\n\u003e An implementation and some minimal tests matching the content of this\n\u003e e-mail can be found in the link above, using the bitcoin-inquisition as the\n\u003e base branch.\n\u003e\n\u003e Note that the linked code is not well tested and is only intended for\n\u003e exploratory and demonstrative purposes; therefore, bugs are likely at this\n\u003e stage.\n\u003e\n\u003e ##########################\n\u003e # PART 1: MATT's core\n\u003e ##########################\n\u003e\n\u003e In this section, I will discuss plausible semantics for the core opcodes for MATT.\n\u003e\n\u003e The two core opcodes are defined below as OP_CHECKINPUTCONTRACTVERIFY and\n\u003e OP_CHECKOUTPUTCONTRACTVERIFY.\n\u003e\n\u003e (the initial posts named them OP_CHECK{INPUT,OUTPUT}COVENANTVERIFY)\n\u003e\n\u003e They enhance Script with the following capabilities:\n\u003e - decide the taptree of the output\n\u003e - embed some (dynamically computed) data in the output\n\u003e - access the embedded data in the current UTXO (if any)\n\u003e\n\u003e The opcodes below are incomplete, as they only control the output's Script and\n\u003e not the amounts; more on that below.\n\u003e\n\u003e Other than that, the semantics should be quite close to the \"right\" one for\n\u003e the MATT framework.\n\u003e\n\u003e ### The opcodes\n\u003e\n\u003e case OP_CHECKINPUTCONTRACTVERIFY:\n\u003e {\n\u003e // OP_CHECKINPUTCONTRACTVERIFY is only available in Tapscript\n\u003e if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);\n\u003e // (x d -- )\n\u003e if (stack.size() \u003c 2)\n\u003e return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);\n\u003e valtype\u0026 x = stacktop(-2);\n\u003e valtype\u0026 d = stacktop(-1);\n\u003e if (x.size() != 32 || d.size() != 32)\n\u003e return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);\n\u003e const XOnlyPubKey nakedXOnlyKey{Span\u003cconst unsigned char\u003e{x.data(), x.data() + 32}};\n\u003e const uint256 data(d);\n\u003e if (!execdata.m_internal_key.has_value())\n\u003e return set_error(serror, SCRIPT_ERR_UNKNOWN_ERROR); // TODO\n\u003e // Verify that tweak(lift_x(x), d) equals the internal pubkey\n\u003e if (!execdata.m_internal_key.value().CheckDoubleTweak(nakedXOnlyKey, \u0026data, nullptr))\n\u003e return set_error(serror, SCRIPT_ERR_WRONGCONTRACTDATA);\n\u003e popstack(stack);\n\u003e popstack(stack);\n\u003e }\n\u003e break;\n\u003e case OP_CHECKOUTPUTCONTRACTVERIFY:\n\u003e {\n\u003e // OP_CHECKOUTPUTCONTRACTVERIFY is only available in Tapscript\n\u003e if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);\n\u003e // (out_i x taptree d -- )\n\u003e if (stack.size() \u003c 4)\n\u003e return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);\n\u003e int out_i = CScriptNum(stacktop(-4), fRequireMinimal).getint();\n\u003e valtype\u0026 x = stacktop(-3);\n\u003e valtype\u0026 taptree = stacktop(-2);\n\u003e valtype\u0026 d = stacktop(-1);\n\u003e auto outps = checker.GetTxvOut();\n\u003e // Return error if the evaluation context is unavailable\n\u003e if (!outps)\n\u003e return set_error(serror, SCRIPT_ERR_UNKNOWN_ERROR); // TODO\n\u003e if (x.size() != 32 || taptree.size() != 32 || (d.size() != 0 \u0026\u0026 d.size() != 32))\n\u003e return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);\n\u003e if (out_i \u003c 0 || out_i \u003e= (int)outps-\u003esize())\n\u003e return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);\n\u003e const XOnlyPubKey nakedXOnlyKey{Span\u003cconst unsigned char\u003e{x.data(), x.data() + 32}};\n\u003e const uint256 data(d);\n\u003e const uint256 *data_ptr = (d.size() == 0 ? nullptr : \u0026data);\n\u003e const uint256 merkle_tree(taptree);\n\u003e CScript scriptPubKey = outps-\u003eat(out_i).scriptPubKey;\n\u003e if (scriptPubKey.size() != 1 + 1 + 32 || scriptPubKey[0] != OP_1 || scriptPubKey[1] != 32)\n\u003e return set_error(serror, SCRIPT_ERR_WRONGCONTRACTDATA);\n\u003e const XOnlyPubKey outputXOnlyKey{Span\u003cconst unsigned char\u003e{scriptPubKey.data() + 2, scriptPubKey.data() + 34}};\n\u003e // Verify that taptweak(tweak(lift_x(x), d), taptree) equals the internal pubkey\n\u003e if (!outputXOnlyKey.CheckDoubleTweak(nakedXOnlyKey, data_ptr, \u0026merkle_tree))\n\u003e return set_error(serror, SCRIPT_ERR_WRONGCONTRACTDATA);\n\u003e popstack(stack);\n\u003e popstack(stack);\n\u003e popstack(stack);\n\u003e popstack(stack);\n\u003e }\n\u003e break;\n\u003e\n\u003e ### Commentary\n\u003e\n\u003e CheckDoubleTweak function (implemented in the branch) gets an x-only pubkey,\n\u003e optionally some data, and optionally taptree's merkle root.\n\u003e It verifies that the x-only pubkey being tested equals the given naked pubkey,\n\u003e optionally tweaked with the embedded data, optionally tweaked with the tagged\n\u003e hash of the merkle tree per BIP-0341 [3].\n\u003e Making both the tweaks optional allows to simplify the code, and also to obtain\n\u003e more compact scripts in some spending paths.\n\u003e\n\u003e In words:\n\u003e\n\u003e - OP_CHECKINPUTCONTRACTVERIFY: verify that the current input's internal key\n\u003e contains some embedded data (which would typically be passed through the\n\u003e witness stack)\n\u003e - OP_CHECKOUTPUTCONTRACTVERIFY: verify that a given output is a certain P2TR\n\u003e output script containing the desired embedded data.\n\u003e\n\u003e TBD if the tweaking used for the embedded data tweak should use a tagged hash;\n\u003e omitted for simplicity in this demo implementation.\n\u003e\n\u003e ### Amount preservation\n\u003e\n\u003e In the code above and in the linked demo implementation, the opcodes only\n\u003e operate on the scriptPubkey; a complete implementation would want to make sure\n\u003e that amounts are correctly preserved.\n\u003e\n\u003e The most direct and general way to address this would be to allow direct\n\u003e introspection on the output amounts. This has the complication that output\n\u003e amounts require 64-bits arithmetics, as discussed in the context of other\n\u003e proposals, for example: [4].\n\u003e\n\u003e One more limited approach that works well for many interesting contracts\n\u003e is that of the deferred checks, implemented in OP_VAULT [2].\n\u003e The idea is that all the amounts of the inputs that commit to the same output\n\u003e script with OP_CHECKOUTPUTCONTRACTVERIFY are added together, and the script\n\u003e interpreter requires that the amount of that output is not smaller than the\n\u003e total amount of those inputs. This check is therefore transaction-wide rather\n\u003e than being tested during the input's script evaluation.\n\u003e\n\u003e This behaviour is adequate for vaults and likely suitable for many other\n\u003e applications; however, it's not the most general approach. I didn't try to\n\u003e implement it yet, and defer the decision on the best approach to a later time.\n\u003e\n\u003e ### Extensions\n\u003e\n\u003e The opcodes above are not enough for the full generality of MATT: one would\n\u003e need to add an opcode like OP_SHA256CAT to allow the data embedding to commit\n\u003e to multiple pieces of data.\n\u003e This is not used in today's post, therefore I left it out of these code examples.\n\u003e\n\u003e It would be easy to extend OP_CHECKOUTPUTCONTRACTVERIFY to also apply for\n\u003e an arbitrary input (typically, different from the currently executed one); there\n\u003e are likely use cases for that, allowing to define contracts with more complex\n\u003e cross-input semantics, but I preferred to keep things simple.\n\u003e\n\u003e Of course, one could also entirely replace CICV/COCV with generic full\n\u003e introspection on inputs/output's program, plus opcodes for elliptic curve math\n\u003e and tagged hashes.\n\u003e\n\u003e ##########################\n\u003e # PART 2: Vaults with MATT\n\u003e ##########################\n\u003e\n\u003e In the rest of this post, I will document the first attempt at creating a vault\n\u003e using the opcodes described.\n\u003e\n\u003e While not an attempt at cloning exactly the functionality of OP_VAULT [2],\n\u003e it borrows heavily from the excellent work that was done there.\n\u003e\n\u003e In particular, it also inherits the choice of using OP_CTV as a primitive,\n\u003e building on top of the bitcoin-inquisition's current branch that has already\n\u003e merged OP_CTV. Reasonable vaults would be possible without CTV, but they\n\u003e would be less efficient, particularly in the case of sending to many addresses\n\u003e in a single unvaulting flow.\n\u003e\n\u003e ### Distilling OP_VAULT\n\u003e\n\u003e Abstracting from the implementation details, I mentally model a vault as a\n\u003e simple state machine with 2 states: [V] and [U]:\n\u003e\n\u003e [V]: the initial vault UTXO(s);\n\u003e [U]: the utxo produced by the \"trigger transaction\" during unvaulting.\n\u003e\n\u003e On the typical path: one or more [V] UTXOs are sent to the [U] state, and after\n\u003e a timelock set on [U] expires, [U] is spent to one or several destinations.\n\u003e Crucially, the destination outputs and amounts are already decided when [V] is\n\u003e spent into [U].\n\u003e\n\u003e At any time before the funds are spent from [U], they can always be spent by\n\u003e sending them to some specified recovery path.\n\u003e\n\u003e There are two key elements that are part of OP_VAULT's semantics, and could be\n\u003e generalized:\n\u003e\n\u003e − Forcing the script/amount of the next stepon\n\u003e − Storing some data for future Script's access (in the vault's case, a hash\n\u003e that commits to the final withdrawal transaction).\n\u003e\n\u003e CICV/COCV generalize both to arbitrary scripts (taptrees) and state machines,\n\u003e and to dynamical and witness-dependent data embedded in the pubkey of a P2TR\n\u003e output.\n\u003e\n\u003e ### Vault parameters\n\u003e\n\u003e A contract that represents a vault has the following parameters (hardcoded in\n\u003e the script when the vault is created):\n\u003e\n\u003e - alternate_pk: a key that can be used any time.\n\u003e - spend_delay: the relative timelock before the withdrawal can be finalized;\n\u003e - recover_pk: a pubkey for a P2TR output where funds can be sent at any time.\n\u003e\n\u003e The alternate_pk is a pubkey that can optionally be used as the key-path\n\u003e spending condition for both states [V] and [U]. If such a spending condition is not\n\u003e desired, it can be replaced with a NUMS point, making the key-path unspendable.\n\u003e\n\u003e The spend_delay is the number of blocks that must be mined before the final\n\u003e withdrawal transaction\n\u003e\n\u003e In this example we also use an unvault_pk needed to authorize the unvaulting\n\u003e process (that is, spend [V] into [U]); this could be replaced with any miniscript\n\u003e or other conditions expressible in Script.\n\u003e\n\u003e ### P2TR structure for [V] (vault)\n\u003e\n\u003e internal key: alternate_pk\n\u003e\n\u003e Script 1: \"trigger\"\n\u003e # witness: \u003cout_i\u003e \u003cctv-hash\u003e\n\u003e {\n\u003e \u003calternate_pk\u003e,\n\u003e \u003cmerkle root of U's taptree\u003e,\n\u003e 2, OP_ROLL,\n\u003e OP_CHECKOUTPUTCONTRACTVERIFY,\n\u003e\n\u003e \u003cunvault_pk\u003e\n\u003e OP_CHECKSIG\n\u003e }\n\u003e\n\u003e Script 2: \"recover\"\n\u003e # witness: \u003cout_i\u003e\n\u003e {\n\u003e recover_pk,\n\u003e OP_0, # no data tweak\n\u003e OP_0, # no taptweak\n\u003e OP_CHECKOUTPUTCONTRACTVERIFY,\n\u003e OP_TRUE\n\u003e }\n\u003e\n\u003e The \"trigger\" script requires in the witness an output index and the ctv-hash\n\u003e that describes the withdrawal transaction.\n\u003e COCV forces the output to contain the ctv-hash as embedded data.\n\u003e That's followed by the unvaulting condition − in this example, a simple\n\u003e signature check.\n\u003e\n\u003e The \"recover\" script doesn't require any signature, and it simply forces\n\u003e the output specified in the witness to be a P2TR output with recover_pk as its\n\u003e pubkey.\n\u003e\n\u003e (Omitting the \"recover\" script in [V] would reduce the size of the witness by\n\u003e 32 bytes in the expected case, and might be preferred for some users)\n\u003e\n\u003e ### P2TR structure for [U] (unvaulting state)\n\u003e\n\u003e internal key: alternate_pk (tweaked with ctv_hash)\n\u003e\n\u003e Script 1: \"withdrawal\"\n\u003e # witness: \u003cctv_hash\u003e\n\u003e {\n\u003e OP_DUP,\n\u003e\n\u003e # check that the top of the stack is the\n\u003e # embedded data in the current input\n\u003e \u003calternate_pk\u003e, OP_SWAP,\n\u003e OP_CHECKINPUTCONTRACTVERIFY,\n\u003e\n\u003e # Check timelock\n\u003e \u003cspend_delay\u003e,\n\u003e OP_CHECKSEQUENCEVERIFY,\n\u003e OP_DROP,\n\u003e\n\u003e # Check that the transaction output is as expected\n\u003e OP_CHECKTEMPLATEVERIFY\n\u003e }\n\u003e\n\u003e Script 2: \"recover\"\n\u003e # witness: \u003cout_i\u003e\n\u003e {\n\u003e \u003crecover_pk\u003e,\n\u003e OP_0,\n\u003e OP_0,\n\u003e OP_CHECKOUTPUTCONTRACTVERIFY,\n\u003e OP_TRUE\n\u003e }\n\u003e\n\u003e The \"withdrawal\" finalizes the transaction, by checking that the timelock expired and\n\u003e the outputs satisfy the CTV hash that was committed to in the previous transaction.\n\u003e\n\u003e The \"recover\" script is identical as before.\n\u003e\n\u003e ### Differences with OP_VAULT vaults\n\u003e\n\u003e Here I refer to the latest version of OP_VAULT at the time of writing. [5]\n\u003e It is not a thorough analysis.\n\u003e\n\u003e Unlike the implementation based on OP_VAULT, the [V] utxos don't have an option\n\u003e to add an additional output that is sent back to the same exact vault.\n\u003e Supporting this use case seems to require a more general way of handling the\n\u003e distribution of amounts than what I discussed in the section above: that would\n\u003e in fact need to be generalized to the case of multiple\n\u003e OP_CHECKOUTPUTCONTRACTVERIFY opcodes executed for the same input.\n\u003e\n\u003e By separating the ctv-hash (which is considered \"data\") from the scripts in the\n\u003e taptree, one entirely avoids the need to dynamically create taptrees and\n\u003e replace leaves in the covenant-encumbered UTXOs; in fact, the taptrees of [V]\n\u003e and [U] are already set in stone when [V] utxos are created, and only the\n\u003e \"data\" portion of [U]'s scriptPubKey is dynamically computed. In my opinion,\n\u003e this makes it substantially easier to program \"state machines\" that control the\n\u003e behavior of coins, of which vaults are a special case.\n\u003e\n\u003e I hope you'll find this interesting, and look forward to your comments.\n\u003e\n\u003e Salvatore Ingala\n\u003e\n\u003e [1] - https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2022-November/021223.html\n\u003e [2] - https://github.com/bitcoin/bips/pull/1421\n\u003e [3] - https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki\n\u003e [4] - https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-September/019420.html\n\u003e [5] - https://github.com/bitcoin/bips/blob/7112f308b356cdf0c51d917dbdc1b98e30621f80/bip-0345.mediawiki\n-------------- next part --------------\nAn HTML attachment was scrubbed...\nURL: \u003chttp://lists.linuxfoundation.org/pipermail/bitcoin-dev/attachments/20230501/6f94a2a7/attachment-0001.html\u003e"}
