Skip to content

Latest commit

 

History

History
293 lines (220 loc) · 13.4 KB

bip-coshv.mediawiki

File metadata and controls

293 lines (220 loc) · 13.4 KB

  BIP: bip-coshv
  Title: OP_CHECKOUTPUTSHASHVERIFY
  Author: Jeremy Rubin <[email protected]>
  Status: Draft
  Type: Standards Track
  Created:
  License: BSD-3-Clause

Table of Contents

Abstract

This BIP proposes a new opcode, OP_CHECKOUTPUTSHASHVERIFY, to be activated for Tapscript version 0.

The new opcode has applications for transaction congestion control and payment channel instantiation, among others, which are described in the Motivation section of this BIP.

Summary

CHECKOUTPUTSHASHVERIFY uses opcode OP_RESERVED1 (0x89) during Tapscript execution.

CHECKOUTPUTSHASHVERIFY verifies the following conditions:

  • The following script is a minimal data push of 32 bytes
  • There is only one input spent in this transaction
  • The SHA256 double-hash of the serialized outputs matches the value provided
If the operations following CHECKOUTPUTSHASHVERIFY are not a 32-byte data push, it is ignored. Otherwise, if the conditions are not met, execution fails.

Motivation

Covenants -- or restrictions on how a coin may be spent beyond key ownership -- are a highly powerful construct for structuring smart contracts. However, given their complexity and potential for introducing fungibility risks they have not been seriously considered for inclusion in Bitcoin thus far.

This BIP aims to introduce a minimum viable covenant which enables a limited set of practical features. For example:

Congestion Controlled Transactions

When there is a large demand for blockspace it can become very expensive to make payments. By using CHECKOUTPUTSHASHVERIFY, a large volume payment processor may aggregate all their payments into a single O(1) transaction for purposes of confirmation. Then, some time later, the payments can be expanded out of that UTXO when the demand for blockspace is decreased.

Without CHECKOUTPUTSHASHVERIFY, this is still possible to do with Schnorr signatures (even with ECDSA given multiparty schemes). However, it is not possible to do non-interactively, which fundamentally limits the viability of the approach.

To structure a congestion control transaction as user has multiple options -- it can simply be a single step from 1 output to N, or, a user can commit to a tree of outputs using CHECKOUTPUTSHASHVERIFY which permits them to confirm as many payments as they like. Furthermore, the Taproot can commit to variable size expansions -- say, one node which expands by 2, by 4, by 8, etc. This allows a trade off between transaction overhead and immediately available block space. The Merkle tree lookup in that case is O(log(log(N))) extra overhead, but the tree can be Huffman encoded to make it E[O(1)] depending on block demand. Each node of the tree can also attempt to 'opt in' to preferring a Taproot signature based spend, but if participants are offline or malicious the expansion can proceed to smaller groups.

The overall overhead of this approach (without optimizations) is from the perspective of each user O(log(N)) transactions with an expectation of just 1 additional transaction, and 2N from the perspective of the network. However, given the lack of signatures required for such transactions, the actual overhead is less.

The below chart showcases the structure of these transactions in comparison to normal transactions and batched transactions.

A simulation is shown below of what impact this could have on mempool backlog given 5% network adoption, and 50% network adoption. The code for the simulation is provided in this BIP's subdirectory.

Channel Factories

This use case is similar to as above, except instead of payments, the leaf nodes should be set up as a channel (perhaps between the payer and payee or a target of payee's choice).

These channels are already time insensitive for setup, as all punishments can be relative timelocked to the actual instantiation.

This permits instant liquidity on the coins sent using this delayed method.

Wallet Vaults

When greater security is required for cold storage solutions, there can be default Tapscript paths that move funds from one target to another target.

For example, a cold wallet can be set up where one customer support desk can, without further authorization, move a portion of the funds (using multiple pre-set amounts) into a lukewarm wallet operated by an isolated support desk. The support desk can then issue some funds to a hot wallet, and send the remainder back to cold storage with a similar withdrawal mechanism in place.

This is all possible without CHECKOUTPUTSHASHVERIFY, but CHECKOUTPUTSHASHVERIFY eliminates the need for coordination and online signers, as well as reducing the ability for a support desk to improperly move funds.

Furthermore, all such designs can be combined with relative time locks to give time for compliance and risk desks to intervene.

CoinJoin

This sort of approach makes it much easier to set up a trustless CoinJoin.

All participants agree on a single UTXO which commits to its output hash, participants then can fund the transaction with whatever inputs they like.

Then, the transaction can be confirmed.

If desired, the Tapscript path can be usurped by a signature based spend to improve fungibility.

Design

The goal of CHECKOUTPUTSHASHVERIFY is to be minimal impact on the existing codebase -- in the future, as we become aware of more complex but shown to be safe use cases new covenant types might be enabled.

Critically, because this is a Tapscript, it is intended that participants may collaborate to replace the Tapscript path with a signature. This lifts the requirement of the output being spent alone and the exact match of output hashes, if other dependencies (like channel state) can be updated.

Below we'll discuss the rules one-by-one:

The following script is a minimal data push of 32 bytes

CHECKOUTPUTSHASHVERIFY uses the push after the opcode rather than before the opcode. Were CHECKOUTPUTSHASHVERIFY to use the data from the stack it would be possible to construct in script what data is committed to. By using a data lookahead, we ensure that the outputs are known at the time of spending.

The script programmer is still able to conditionalize which hash it is checked on, e.g., OP_IF OP_CHECKOUTPUTSHASHVERIFY <outputs 1> OP_ELSE <outputs 2> OP_ENDIF. However, by keeping the outputs literal hashes we limit the possibilities.

In any case, a user is more likely to, given Tapscript's API, compile any code with multiple OP_CHECKOUTPUTSHASHVERIFY operations into separate branches.

There is only one input spent in this transaction

If we allow more than one input to be spent in the transaction then it would be possible for two outputs to request payment to the same set of outputs, resulting in half the intended payments being discarded. While there are safe ways to allow multiple inputs, the design is much more complicated and the use case less clear.

Furthermore, the restriction on which inputs can be co-spent is critical for payments-channel constructs where a stable TXID is a requirement.

The SHA256 double-hash of the serialized outputs matches the value provided

This is a hash which is already computed so it saves us from extra verification overhead. Thus, OP_CHECKOUTPUTSHASHVERIFY does not impose substantial additional verification overhead.

It is not a concern that exposing this hash on the stack might allow parsing of the outputs, because they are already known exactly at the time of script construction.

Design Tradeoffs and Risks

Covenants have historically been controversial given their potential for fungibility risks -- coins could be minted which have a permanent restriction on how they may or may not be spent.

In the approach presented here, the covenants are severely restricted as follows. All covenants are wrapped with a multisig based key which can preempt the covenant's requirements. Furthermore, the structure of OP_CHECKOUTPUTSHASHVERIFY covenants is such that the outputs must be known exactly at the time of construction. Therefore it is only possible to create covenants which expand in a finite number of steps and is equivalent to, in a safety sense, to the set of transactions which create all the inputs at reachable end states. Furthermore, covenants are restricted to be spendable as a single input only, preventing the 'half spend' problem.

These covenants, as restricted as they are, bear some risks. It is possible that the the preimage provided to OP_CHECKOUTPUTSHASHVERIFY is unknown or the Taproot is constructed with an public key with an unknown private key. Knowing that an address is spendable from is incompatible with sender's ability to spend to any address (especially, OP_RETURN). If a sender needs to know the recipient can remove the covenant before spending, they may request a signature of an challenge string from the recipients. A final risk is the abuse of "forwarding address contracts". A forwarding address is a script which can automatically execute in a predefined way. For example, a hot wallet might have coins which can automatically be moved to a cold storage address after a relative timeout. The issue is that reusing such keys can be very unsafe. For example, suppose one creates an address which forwards 1 BTC to cold storage. Creating an output to this address with less than 1 BTC will be frozen until the Taproot signature path is used. If more than 1 BTC is paid to the address, and the redeemscript is known publicly, then anyone can cause the funds in excess of 1BTC to be paid as a large miner fee. It's possible to later introduce opcodes which commit to how much fee should be spent at max or other restrictions that would make reusable keys safer to use. For now, it is best to not reuse a Taproot key unless you are certain all the branches are compatible with your desired payment. This limitation and risk is not unique to OP_CHECKOUTPUTSHASHVERIFY, Taproot scripts may contain many logical branches that would be unsafe for being spent to multiple times (e.g., a Hash Time Lock branch should be instantiated with unique hashes each time it is used).

More powerful covenants like those proposed by MES16, if ever implemented, would make the OP_CHECKOUTPUTSHASHVERIFY type of covenant superfluous. They would also bring some benefits in terms of improving the ability to adjust for things like fees rather than relying on child-pays-for-parent or other mechanisms. However, these features come at substantially increased complexity and room for unintended behavior. Alternatively, CHECKSIGFROMSTACK or SIGHASH_NOINPUT based covenant designs might be able to implement covenants as well. SIGHASH_NOINPUT bears additional risks that preclude it's viability for inclusion in Bitcoin. CHECKSIGFROMSTACK is more complicated to use that OP_CHECKOUTPUTSHASHVERIFY, and encumbers additional verification overhead absent from OP_CHECKOUTPUTSHASHVERIFY. Given the simplicity of this approach to implement and analyze, and the benefits realizable by user applications, the OP_CHECKOUTPUTSHASHVERIFY approach is proposed.

Specification

The below code is the main logic for verifying OP_CHECKOUTPUTSHASHVERIFY.

    case OP_CHECKOUTPUTSHASHVERIFY:
    {
        // Don't verify before enabled...
        if (flags & SCRIPT_VERIFY_OUTPUTS_HASH) {
            CScript::const_iterator lookahead = pc;
            opcodetype argument;
            // Read ahead one opcode as a lookahead argument
            if (!script.GetOp(lookahead, argument, vchPushValue))
                return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
            // If lookahead argument was exactly 32 bytes, check OutputHash
            // This is so that we can later add different semantics for this opcode
            if (vchPushValue.size() == 32) {
                // Argument should be == 0x20 -- will fail later anyways
                if (!CheckMinimalPush(vchPushValue, argument)) {
                    return set_error(serror, SCRIPT_ERR_MINIMALDATA);
                }
                // If multiple inputs allowed, two inputs with the same OutputsHashVerify
                // would pay only half intended amount!
                if (!checker.CheckOnlyOneInput()) {
                    return set_error(serror, SCRIPT_ERR_OUTPUTSHASHVERIFY);
                }
                // Lastly, check that the outputs hash matches the passed value
                if (!checker.CheckOutputsHash(vchPushValue)) {
                    return set_error(serror, SCRIPT_ERR_OUTPUTSHASHVERIFY);
                }
            }
        }
    }
    break;

Deployment

The deployment is intended to be done with Tapscript https://github.com/sipa/bips/blob/bip-schnorr/bip-tapscript.mediawiki.

Implementations

An implementation and tests are available here: https://github.com/JeremyRubin/bitcoin/tree/congestion-control.

References

Copyright

This document is licensed under the 3-clause BSD license.