# OP\_PAIRCOMMIT as a candidate for addition to LNhance

**URL:** <https://delvingbitcoin.org/t/op-paircommit-as-a-candidate-for-addition-to-lnhance/1216>\
**Category:** Protocol Design\
**Created:** [October 25, 2024, 2:31pm UTC](https://delvingbitcoin.org/t/op-paircommit-as-a-candidate-for-addition-to-lnhance/1216 "2024-10-25T14:31:23Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![moonsettler](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/moonsettler/32/38_2.png) [@moonsettler](https://delvingbitcoin.org/u/moonsettler)\
**Post date:** [October 25, 2024, 2:31pm UTC](https://delvingbitcoin.org/t/op-paircommit-as-a-candidate-for-addition-to-lnhance/1216/1 "2024-10-25T14:31:23Z")

</div>

Hi All,

Looking for some feedback on what I ended up with for PC.

The idea is that the number of SHA256 iterations should be minimized in the most likely use case we can optimize for, which is LN-Symmetry. Since the Tag can be pre-computed as a mid-state it would only take 1 or 2 hash cycles in validation for the LN-Symmetry use case.

In case of a 7 byte balance commitment + 32 byte CTV hash (no HTLCs in-flight), the total preimage size is 55 bytes. Which should make it fit into a single block with the SHA256 length commitment.

In case there are 2x 32 byte CTV hash commitments, the first 64 byte block is comprised of those hashes, and the second block is the vectors’ size and total length commitment, which would be a largely 0 filled block with a very few bits set to 1.

It’s a particular concern for LN-Symmetry with CTV that the concatenation of the two preimages allows for length redistribution attacks, because CTV is only defined for 32 byte templates and will act as NOP for different template sizes for upgradeability.

The question is, is it a good idea to increase the number of bits expected to changed in the preimage in case of stack element resizing attacks?

```c++
const HashWriter HASHER_PAIRCOMMIT{TaggedHash("PairCommit")};

namespace {
/* uint32_t hash function using primes 0x3B9ACA07 multiplier and 0x7FFFFFFF modulo
 * expected to change on average ~16 bits in output for a single bit change in input */
__inline uint32_t uint32_t_hash_x3B9ACA07(const uint32_t& i)
{
    static const uint64_t p = 0x3B9ACA07;
    static const uint32_t m = 0x7FFFFFFF;

    return (p * i) % m;
}
}

/* PairCommitHash preimage is expected to change over 32 bits on average in case of
 * length redistribution between the two input vectors */
uint256 PairCommitHash(const std::vector<unsigned char>& x1, const std::vector<unsigned char>& x2)
{
    const uint32_t x1_size = x1.size();
    const uint32_t x2_size = x2.size();
    const uint32_t x1_sh = uint32_t_hash_x3B9ACA07(x1_size);
    const uint32_t x2_sh = uint32_t_hash_x3B9ACA07(x2_size);

    HashWriter ss{HASHER_PAIRCOMMIT};
    ss << x1
       << x2
       << x1_size
       << x1_sh
       << x2_size
       << x2_sh;

    return ss.GetSHA256();
}

```

Link: [OP\_PAIRCOMMIT alternative with hash\_x3B9ACA07 by moonsettler · Pull Request #7 · lnhance/bitcoin · GitHub](https://github.com/lnhance/bitcoin/pull/7/files)

---

<div class="post-metadata">

**Author:** ![moonsettler](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/moonsettler/32/38_2.png) [@moonsettler](https://delvingbitcoin.org/u/moonsettler)\
**Post date:** [October 25, 2024, 2:38pm UTC](https://delvingbitcoin.org/t/op-paircommit-as-a-candidate-for-addition-to-lnhance/1216/2 "2024-10-25T14:38:12Z")

</div>

From the prelimiary spec, where PC fits in LN-Symmetry:

### Vector Commitments

`OP_PAIRCOMMIT` can be used to commit to a vector of stack elements in a way that is not vulnerable to various forms of witness malleability especially when used in conjunction with `OP_CHECKSIGFROMSTACK` and `OP_INTERNALKEY`, since SHA256 implicitly commits to size of the stack elements, making the script cleaner, and simpler. If `OP_CAT` was used naively, the contract could be easily broken since `OP_CHECKTEMPLATEVERIFY`is only defined for 32 byte parameters.

```plaintext
# S = 500000000
# IK -> A+B
<sig> <state-n-recovery-data> <state-n-hash> | CTV PC IK CSFSV <S+1> CLTV

```

before funding sign first state template:

```plaintext
# state-n-hash { nLockTime(S+n), out(contract, amount(A)+amount(B)) }
# settlement-n-hash { nSequence(2w), out(A, amount(A)), out(B, amount(B)) }
# state-n-recovery-data { settlement-n-hash or state-n-balance }

# contract for state n < m
IF
  <sig> <state-m-recovery-data> <state-m-hash> | CTV PC IK CSFSV <S+n+1> CLTV
ELSE
  <settlement-n-hash> CTV
ENDIF

```

---

<div class="post-metadata">

**Author:** ![1440000bytes](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/1440000bytes/32/301_2.png) [@1440000bytes](https://delvingbitcoin.org/u/1440000bytes)\
**Post date:** [October 25, 2024, 5:57pm UTC](https://delvingbitcoin.org/t/op-paircommit-as-a-candidate-for-addition-to-lnhance/1216/3 "2024-10-25T17:57:14Z")

</div>

I am sorry but these new opcodes added to proposals will only delay or cancel them forever. I understand you like 2 things combined on the stack, OP\_CAT etc. but this brings a lot of complexity in something that needs to be kept simple.

If we ever get OP\_CAT, maybe this could be possible with OP\_SHA256 and OP\_CAT at that moment.

Its better if we get OP\_CTV activated first instead of trying to build the next Great Covenant Proposal.

---

<div class="post-metadata">

**Author:** ![moonsettler](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/moonsettler/32/38_2.png) [@moonsettler](https://delvingbitcoin.org/u/moonsettler)\
**Post date:** [October 25, 2024, 7:06pm UTC](https://delvingbitcoin.org/t/op-paircommit-as-a-candidate-for-addition-to-lnhance/1216/4 "2024-10-25T19:06:11Z")

</div>

It would be possible with OP\_CAT to closely emulate OP\_PAIRCOMMIT, they are more complicated and or more expensive. We have redundancies in bitcoin script for much smaller optimizations.

```plaintext
<x1> <x2>
OP_SWAP
OP_SIZE
<0x00000001>
OP_ADD
OP_TOALTSTACK
OP_SWAP
OP_SIZE
<0x00000001>
OP_ADD
OP_TOALTSTACK
OP_CAT
OP_FROMALTSTACK
OP_CAT
OP_FROMALTSTACK
OP_CAT
OP_HASH256

```

or simply

```plaintext
<x1> <x2>
OP_HASH256
OP_SWAP
OP_HASH256
OP_SWAP
OP_HASH256

```

---

<div class="post-metadata">

**Author:** ![moonsettler](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/moonsettler/32/38_2.png) [@moonsettler](https://delvingbitcoin.org/u/moonsettler)\
**Post date:** [October 25, 2024, 7:09pm UTC](https://delvingbitcoin.org/t/op-paircommit-as-a-candidate-for-addition-to-lnhance/1216/5 "2024-10-25T19:09:29Z")

</div>

But the question was really not about this, but rather if it makes any sense to do a mini-hashing of the sizes instead of static padding, for making the preimage more mutable when you redistribute bytes between the stack elements?

The “original” variant:

```c++
const HashWriter HASHER_PAIRCOMMIT{TaggedHash("PairCommit")};

uint256 PairCommitHash(const std::vector<unsigned char>& x1, const std::vector<unsigned char>& x2)
{
    // PAD is 0x00000001 in little endian serializaton
    const uint32_t PAD = 0x01000000;

    HashWriter ss{HASHER_PAIRCOMMIT};
    ss << x1
       << x2
       << uint32_t(x1.size()) << PAD
       << uint32_t(x2.size()) << PAD;

    return ss.GetSHA256();
}

```

---

<div class="post-metadata">

**Author:** ![moonsettler](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/moonsettler/32/38_2.png) [@moonsettler](https://delvingbitcoin.org/u/moonsettler)\
**Post date:** [October 25, 2024, 9:50pm UTC](https://delvingbitcoin.org/t/op-paircommit-as-a-candidate-for-addition-to-lnhance/1216/7 "2024-10-25T21:50:45Z")

</div>

It’s okay, but I’m looking for opinions on the way these pieces are hashed, specifically from a security and efficiency pov.

---

<div class="post-metadata">

**Author:** ![ajtowns](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/ajtowns/32/417_2.png) [@ajtowns](https://delvingbitcoin.org/u/ajtowns)\
**Post date:** [October 28, 2024, 11:16am UTC](https://delvingbitcoin.org/t/op-paircommit-as-a-candidate-for-addition-to-lnhance/1216/8 "2024-10-28T11:16:51Z")

</div>

> [@moonsettler](#):
>
> The idea is that the number of SHA256 iterations should be minimized in the most likely use case we can optimize for, which is LN-Symmetry.

Not really seeing that as particularly important to minimise, personally; the difference between hashing 64 bytes and 256 bytes is pretty minor, compared to the checksig operation(s) you also have. I’d just write `[BAL] [CTVHASH] SHA256 SWAP SHA256 CAT SHA256`.

> [@moonsettler](#):
>
> In case of a 7 byte balance commitment + 32 byte CTV hash (no HTLCs in-flight), the total preimage size is 55 bytes. Which should make it fit into a single block with the SHA256 length commitment.

> [@moonsettler](#):
>
> It’s a particular concern for LN-Symmetry with CTV that the concatenation of the two preimages allows for length redistribution attacks, because CTV is only defined for 32 byte templates and will act as NOP for different template sizes for upgradeability.

This seems as much an argument against doing upgradeability that way as anything else; but if you do want that, and want to minimise hashing as well for whatever reason, then writing `[BAL] [CTVHASH] SIZE 32 EQUALVERIFY CAT SHA256` seems like it would solve the problem.

If you want to support CTV upgradeability prior to knowing what that upgradeability will do, you could do `[BAL] [CTVHASH] SIZE DUP VERIFY DUP 127 LESSTHANOREQUAL VERIFY SWAP CAT SWAP CAT SHA256` to construct `sha256(1B: <size(CTVHASH)>; 0B-127B: <CTVHASH>; <BAL>)`. (Having CTV error if the hash is 0 bytes would let you avoid the `DUP VERIFY` step and might be a reasonable update to the BIP)

---

<div class="post-metadata">

**Author:** ![moonsettler](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/moonsettler/32/38_2.png) [@moonsettler](https://delvingbitcoin.org/u/moonsettler)\
**Post date:** [October 28, 2024, 11:58am UTC](https://delvingbitcoin.org/t/op-paircommit-as-a-candidate-for-addition-to-lnhance/1216/9 "2024-10-28T11:58:17Z")

</div>

> [@ajtowns](#):
>
> Having CTV error if the hash is 0 bytes would let you avoid the `DUP VERIFY` step and might be a reasonable update to the BIP

Yes, don’t think there is a legitimate reason to have a 0 size argument for CTV. Thank you for the suggestion!

Btw the PC code on that branch is broken, this is in good shape now:

> <https://github.com/lnhance/bitcoin/pull/6>
>
> \<!--
> \*\*\* Please remove the following help text before submitting: \*\*\*
> 
> Pull r…equests without a rationale and clear improvement may be closed
> immediately.
> 
> GUI-related pull requests should be opened against
> https://github.com/bitcoin-core/gui
> first. See CONTRIBUTING.md
> \--\>
> 
> \<!--
> Please provide clear motivation for your patch and explain how it improves
> Bitcoin Core user experience or Bitcoin Core developer experience
> significantly:
> 
> \* Any test improvements or new tests that improve coverage are always welcome.
> \* All other changes should have accompanying unit tests (see \`src/test/\`) or
> functional tests (see \`test/\`). Contributors should note which tests cover
> modified code. If no tests exist for a region of modified code, new tests
> should accompany the change.
> \* Bug fixes are most welcome when they come with steps to reproduce or an
> explanation of the potential issue as well as reasoning for the way the bug
> was fixed.
> \* Features are welcome, but might be rejected due to design or scope issues.
> If a feature is based on a lot of dependencies, contributors should first
> consider building the system outside of Bitcoin Core, if possible.
> \* Refactoring changes are only accepted if they are required for a feature or
> bug fix or otherwise improve developer experience significantly. For example,
> most "code style" refactoring changes require a thorough explanation why they
> are useful, what downsides they have and why they \*significantly\* improve
> developer experience or avoid serious programming bugs. Note that code style
> is often a subjective matter. Unless they are explicitly mentioned to be
> preferred in the \[developer notes\](/doc/developer-notes.md), stylistic code
> changes are usually rejected.
> \--\>
> 
> \<!--
> Bitcoin Core has a thorough review process and even the most trivial change
> needs to pass a lot of eyes and requires non-zero or even substantial time
> effort to review. There is a huge lack of active reviewers on the project, so
> patches often sit for a long time.
> \--\>

PS: not gonna lie, the way `valtype` is serialized to `HashWriter` in bitcoin caught me off-guard.

---

<div class="post-metadata">

**Author:** ![moonsettler](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/moonsettler/32/38_2.png) [@moonsettler](https://delvingbitcoin.org/u/moonsettler)\
**Post date:** [October 29, 2024, 9:38am UTC](https://delvingbitcoin.org/t/op-paircommit-as-a-candidate-for-addition-to-lnhance/1216/10 "2024-10-29T09:38:24Z")

</div>

@ajtowns could you please delete or archive this topic? I want a redo with less confusing information for a PAIRCOMMIT thread.

The code is out of draft and it’s not this variant, also updated the spec, which I should start the topic with.

---

<div class="post-metadata">

**Author:** ![ajtowns](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/ajtowns/32/417_2.png) [@ajtowns](https://delvingbitcoin.org/u/ajtowns)\
**Post date:** [October 29, 2024, 10:52am UTC](https://delvingbitcoin.org/t/op-paircommit-as-a-candidate-for-addition-to-lnhance/1216/11 "2024-10-29T10:52:30Z")

</div>

Don’t want to delete threads/replies unless they’re spam/non-constructive; probably not going to archive things in general. If you want to open a new topic, that’s fine. I think it’s best to approach this as a conversation/presentation rather than a new edition of a book – ie, “am I saying something new that’s interesting and worthwhile”, rather than “whoops, I made a few errors, and would like to update a section, better republish the whole thing”. If you want somewhere so you can say “here’s the current comprehensive doc” better to publish things as a BIP/BINANA, a gist, or in the docs/ directory on your repo, I think?

---

<div class="post-metadata">

**Author:** ![moonsettler](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/moonsettler/32/38_2.png) [@moonsettler](https://delvingbitcoin.org/u/moonsettler)\
**Post date:** [October 29, 2024, 11:36am UTC](https://delvingbitcoin.org/t/op-paircommit-as-a-candidate-for-addition-to-lnhance/1216/12 "2024-10-29T11:36:58Z")

</div>

Alright, here is the current state of OP\_PAIRCOMMIT spec, making a PR against BINANA was actually the original plan once I’m done.

> <https://gist.github.com/moonsettler/d7f1fb88e3e54ee7ecb6d69ff126433b>

---

<div class="post-metadata">

**Author:** ![moonsettler](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/moonsettler/32/38_2.png) [@moonsettler](https://delvingbitcoin.org/u/moonsettler)\
**Post date:** [November 23, 2024, 3:41pm UTC](https://delvingbitcoin.org/t/op-paircommit-as-a-candidate-for-addition-to-lnhance/1216/13 "2024-11-23T15:41:32Z")

</div>

To try to keep the [BIP](https://gist.github.com/moonsettler/d7f1fb88e3e54ee7ecb6d69ff126433b) as short and to the point as possible, an in depth discussion of the design rationale especially regarding alternatives was omitted. I’m posting these here, so that they are available for those interested.

## Background

LNhance at it’s core is `CTV` + `CSFS` with the primary intent of not only enabling more scalable less interactive timeout tree and covenant pool constructions, but also to enable LN-Symmetry (formerly known as eltoo). `IKEY` was added to access the internal public key from the control block (which would be a 2-of-2 MuSig key in case of a lightning channel allowing for cooperative closes on the taproot keypath) making the contract more efficient.

As I recall, later on @instagibbs discovered in an attempt to implement Symmetry, that one of the main benefits of not having to keep all backups is lost when the channel peer can not reconstruct the script that spends an intermediate state pushed on chain. While most pieces are deterministic, the specific distribution of funds for that particular state is not. We call this the “data availability problem” of LN-Symmetry. For APO the alternative solution discussed was to use the taproot annex.

## Rationale

If `OP_CAT` was available, it could be used to combine multiple stack elements, that get verified with `OP_CHECKSIGFROMSTACK` as a valid state update.

`OP_PAIRCOMMIT` solves this specific problem without introducing a wide range of potentially controversial new behaviors, such as novel 2-way peg mechanisms.

### Alternatives discussed

#### OP\_CAT

`OP_CAT` allows for fine grained introspection possibly bigint operations and extending the arithmetic capabilities of bitcoin script using lookup tables.

#### SHA256 streaming opcodes

These would predictably allow for the same functionality as `OP_CAT` for introspection purposes, since verification of a computation is largely equivalent with carrying it out. Bigint and new arithmetic operations would be hard or even impossible.

Naively implemented they relax the script limitations on what is possible both the limitation of stack element size that can get hashed with CAT only and without CAT it allows for custom construction of ‘sighashes’ like CTV templates or with CSFS pretty much everything CAT enables in terms of introspection.

#### Merkle operation opcodes

These would be of very limited general use and hard to rationalize without OP\_CAT. Their complexity and resource cost is hard to justified for vector commitments only.

#### ‘Kitty’ CAT (result or inputs limited in size)

The original idea would have limited the maximum size of `OP_CAT` output to a size that is smaller than the smallest sighash preimage, thus disabling the introspection capabilities and trivial ways to extend the arithmetic repertoire of bitcoin script. This turned out to be an awkward, arbitrary and offering weak .

#### OP\_CHECKTEMPLATEVERIFY committing to the taproot annex in tapscript

A CTV template can be considered a sighash, however relaxing the relay policy to take advantage of this change would make various endogenous asset protocols more efficient, and therefore be controversial. There is also no consensus on how to use or how to structure the annex.

#### OP\_CHECKSIGFROMSTACK on n elements as message

This was previously discussed and also implemented, it complicates the code and is a pretty arbitrary coupling of behaviors.

#### OP\_VECTORCOMMIT

The obvious generalized solution for committing to n \> 2 stack elements, however it involves looping and hard to argue about setting the proper limits to it. It could be forked in later with for example `OP_CHECKCONTRACTVERIFY`.

It’s impossible to predict what would be more optimal for the user a) leave the items on the stack and only consume the number of items, or b) consume the items from the stack.

We could probably do `<vch1> .. <vchn> <n> VECTORCOMMIT` where n as a signed char can be `2..127` or `-2..-127`, and if it’s positive the stack elements are left on stack, if negative they are consumed.

### Possible future improvements

LNhance + `OP_CHECKCONTRACTVERIFY` (aka `CCV` the centerpiece of MATT by @salvatoshi) or `OP_VAULT/RECOVER` (aka BIP-345 by @jamesob) would enable good vaults with flexible amount withdrawal and immediate re-vault of change. The both assume `OP_CHECKTEMPLATEVERIFY` as an available building block, and `OP_CHECKCONTRACTVERIFY` especially benefits from `OP_PAIRCOMMIT` as a means to carry multiple stack elements.
