Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft for Extra Data / HASH160 Support #1587

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 184 additions & 0 deletions bip-0119.2.mediawiki
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
<pre>
BIP: 119.2 (Pending Assignment)
Layer: Consensus (soft fork)
Title: CHECKTEMPLATEVERIFY V2
Author: Jeremy Rubin <[email protected]>
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0119
Status: Draft
Type: Standards Track
Created: 2024-05-06
License: BSD-3-Clause
</pre>

==Abstract==

This BIP proposes extensions to OP_CHECKTEMPLATEVERIFY BIP-119 as enhancements
for LN Symmetry based constructions which use CTV and CSFS.


==Summary==

Adds a 20-byte CTV hash using HASH160 digest to save 12 bytes per script. Adds
a 1-byte flag (with no bits set) that allows for arbitrary data to be committed
to inside the CTV hash.

==Motivation==

LN Symmetry prefers scripts as small as possible. Allowing the use of HASH160
can allow users to reduce fees in exchange for minor loss of security.

LN Symmetry also sometimes requires additional committed data for data
availability purposes. When using BIP-118 APO, this extra data can be
(although, according to BIP-341/342 should not be) placed in the Annex. As CTV
does not commit to the annex, additional data can be committed to in an
OP_RETURN. However, OP_RETURNs are inefficient as they require 8 bytes of
value plus a few VarInts to encode sizes, and use non discounted blockspace.
Instead, committing to with CTV extra values on the stack can avoid these
overheads.


==Detailed Specification==
Copy link
Contributor

@ProofOfKeags ProofOfKeags May 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the things I'm having trouble following here is precisely why this extra byte allows for Extra Data to be committed to. As far as I can tell the extra byte ensures that there are at least two stack elements but doesn't actually verify what the first stack element is.

I assume that CTV's natural semantics already result in committing to the contents of that second stack element but the mechanism is not obvious to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this equation might help:

Sha256(CTVHASH(TX) || sha256(stack[-2])) == stack[-1][0:32]

more or less, check that the 32 bytes on the back of the stack match if you took the CTV hash and hashed it with some extra data.

The point is to enable signing stack[-1][0:32] with CSFS, which then also covers the extra data, allowing you to stuff arbitrary extra data into the witness.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes now I see it. I got lost in the noise and didn't see the -2 at the end here.

I get it now. Thanks for explaining!


The below code is the main logic for verifying CHECKTEMPLATEVERIFY, described
in pythonic pseudocode, with these modifications. The canonical specification
for the semantics of OP_CHECKTEMPLATEVERIFY as implemented in C++ in the
context of Bitcoin Core can be seen in the reference implementation (TODO).

The execution of the opcode is as follows:
<source lang="python">
def execute_bip_119_v2(self):
# Before soft-fork activation / failed activation
# continue to treat as NOP4
if not self.flags.script_verify_default_check_template_verify_hash:
# Potentially set for node-local policy to discourage premature use
if self.flags.script_verify_discourage_upgradable_nops:
return self.errors_with(errors.script_err_discourage_upgradable_nops)
return self.return_as_nop()

# CTV always requires at least one stack argument
if len(self.stack) < 1:
return self.errors_with(errors.script_err_invalid_stack_operation)

# CTV only verifies the hash against a 32 byte argument
if len(self.stack[-1]) == 32:
# Ensure the precomputed data required for anti-DoS is available,
# or cache it on first use
if self.context.precomputed_ctv_data == None:
self.context.precomputed_ctv_data = self.context.tx.get_default_check_template_precomputed_data()

# If the hashes do not match, return error
if stack[-1] != self.context.tx.get_default_check_template_hash(self.context.nIn, self.context.precomputed_ctv_data):
return self.errors_with(errors.script_err_template_mismatch)

return self.return_as_nop()

# verifies the hash against a 20 byte argument
if len(self.stack[-1]) == 20:

# Before soft-fork activation / failed activation
# continue to treat as NOP4
if not self.flags.script_verify_default_check_template_verify_hash_v2:
# Potentially set for node-local policy to discourage premature use
if self.flags.script_verify_discourage_upgradable_nops:
return self.errors_with(errors.script_err_discourage_upgradable_nops)
return self.return_as_nop()


# Ensure the precomputed data required for anti-DoS is available,
# or cache it on first use
if self.context.precomputed_ctv_data == None:
self.context.precomputed_ctv_data = self.context.tx.get_default_check_template_precomputed_data()

# If the hashes do not match, return error
if stack[-1] != ripemd(self.context.tx.get_default_check_template_hash(self.context.nIn, self.context.precomputed_ctv_data)):
return self.errors_with(errors.script_err_template_mismatch)

return self.return_as_nop()

# verifies the hash against a 21 byte argument and extra data
if len(self.stack[-1]) == 21:
# CTV+data always requires at least two stack argument
if len(self.stack) < 2:
return self.errors_with(errors.script_err_invalid_stack_operation)

# Before soft-fork activation / failed activation
# continue to treat as NOP4
if not self.flags.script_verify_default_check_template_verify_hash_v2 or self.stack[-1][-1] != 0:
# Potentially set for node-local policy to discourage premature use
if self.flags.script_verify_discourage_upgradable_nops:
return self.errors_with(errors.script_err_discourage_upgradable_nops)
return self.return_as_nop()


# Ensure the precomputed data required for anti-DoS is available,
# or cache it on first use
if self.context.precomputed_ctv_data == None:
self.context.precomputed_ctv_data = self.context.tx.get_default_check_template_precomputed_data()

# If the hashes do not match, return error
if stack[-1][:-1] != ripemd(sha256(self.context.tx.get_default_check_template_hash(self.context.nIn, self.context.precomputed_ctv_data) + sha256(stack[-2]))):
return self.errors_with(errors.script_err_template_mismatch)

return self.return_as_nop()

# verifies the hash against a 33 byte argument and extra data
if len(self.stack[-1]) == 33:
# CTV+data always requires at least two stack argument
if len(self.stack) < 2:
return self.errors_with(errors.script_err_invalid_stack_operation)

# Before soft-fork activation / failed activation
# continue to treat as NOP4
if not self.flags.script_verify_default_check_template_verify_hash_v2 or self.stack[-1][-1] != 0:
# Potentially set for node-local policy to discourage premature use
if self.flags.script_verify_discourage_upgradable_nops:
return self.errors_with(errors.script_err_discourage_upgradable_nops)
return self.return_as_nop()


# Ensure the precomputed data required for anti-DoS is available,
# or cache it on first use
if self.context.precomputed_ctv_data == None:
self.context.precomputed_ctv_data = self.context.tx.get_default_check_template_precomputed_data()

# If the hashes do not match, return error
if stack[-1][:-1] != sha256(self.context.tx.get_default_check_template_hash(self.context.nIn, self.context.precomputed_ctv_data) + sha256(stack[-2])):
return self.errors_with(errors.script_err_template_mismatch)

return self.return_as_nop()

# future upgrade can add semantics for this opcode with different length args
# so discourage use when applicable
if self.flags.script_verify_discourage_upgradable_nops:
return self.errors_with(errors.script_err_discourage_upgradable_nops)
else:
return self.return_as_nop()
</source>


No additional DoS protection or caching is required, as the hashing overhead of
the new opcode is similar to that of a regular OP_SHA256 or OP_HASH160.

==Deployment==

N/A
==Reference Implementation==

A reference implementation and tests are pending after conceptual agreement.


===Alternatives===

Should `OP_CAT` be added to Bitcoin, one would be able to use script fragments
that sign the CTV hash concatenated with the extra data, which would be
equivalent to this.

Should `CSFS` sign an arbitrary number of stack elements as a vector
commitment, this would also be equivalent.

This BIP could be pared down to just provide the 20-byte version for
efficiency.

==Copyright==

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