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

RCP-250113A: Unify single-use seals #16

Open
dr-orlovsky opened this issue Jan 15, 2025 · 10 comments
Open

RCP-250113A: Unify single-use seals #16

dr-orlovsky opened this issue Jan 15, 2025 · 10 comments
Labels
C-pushback Consensus-breaking change epic Epic task covering multiple steps of implementation S-implemented
Milestone

Comments

@dr-orlovsky
Copy link
Member

dr-orlovsky commented Jan 15, 2025

author: @dr-orlovsky
layers: consensus
targets: v0.12
breaking: consensus

Background

RGB since v0.7 (introduction of taproot-based commitment scheme called tapret, released in June 2022) allows two commitment schemes. The reason was the following:

  1. tapret commitments save on transaction cost and privacy;
  2. but OP_RETURN based commitments opret are needed for Lightning and hardware wallets.

The immediate issue appeared: if we have two commitment schemes, nothing prevents from creating two distinct single-use seals under each of commitment schemes using the same UTXO. And with that a wallet gets blocked and have no choice other than to burn some assets (all assets under one of commitment schemes). Thus, we had to allow two, not one commitment and anchor per bitcoin witness transaction, so transaction may have both tapret and opret commitments present.

In autumn 2023 in Viareggio we also have found a vulnerability of simultaneous use of tapret and opret in v0.10. So, before Viareggio 2023, in v0.9 and v0.10 we thought “well, we just need to prohibit a wallet to create different seal types”. But it was naive. The real problem is that an adversary can do an inflation attack, by creating two commitments in one transaction and never exposing them both: the first in taproot and the other in an op-Return output following taproot (since he can claim the first taproot do not have tapret, while it actually has).

So the moment you have two commitment schemes you must assume they are both present in the same transaction. I spent three months fighting it, got v0.11 with HUGE complexity of dealing with different double commitment cases [1], - and we spent whole 2024 testing and debugging it.

In Lugano last Autumn we decided to make contracts monotonic on commitments, so we can be sure there are no inflation bugs possible. In v0.12 I have implemented it, and everything was fine (and much simpler) in the consensus layer (so each contract is either opret or tapret), but it has stroke back in wallet: tapret wallets need to work with both opret and tapret contracts, and other types of wallets - just with opret. Right now in v0.12 contracts commit to the seal type via generic constant parameter - which is a safety measure. It is impossible to put contracts of different seal types into the same wallet, preventing from completing tests and releasing v0.12.

Motivation

  1. Have a single RGB-related deterministic bitcoin commitment (and anchor) per witness transaction, and not two
  2. Simplify the audit and formal analysis by getting rid of multiple commitment schemes and supporting just one (per blockchain)
  3. Reduce codebase
  4. Reduce attack surface

Proposal

Unification means that from now on in v0.12 there will be not two commitment schemes (seal closiing methods), but a single one. Thus, neither contracts nor seal definitions commit to a closing method, and receiver doesn't need to provide one via invoice. Witness transaction will always have just a single anchor and commitment - and not two as before. This is a great simplification, since reduces attack surface by a lot, and simplifies a lot of code, business logic and workflows.

The single unified commitment scheme can be described as "look for the first explicit commitment in OP_RETURN output or assume tapret commitment in the first taproot output".

The algorithm details are the following:

  1. iterate over tx outputs
  2. if an output is OP_RETURN, check opret commitment to all the seals (remember seals definitions are commitment-agnostic). Stop
  3. if an output is taproot, assume tapret commitment, and require proof. Stop
  4. if neither, process to the next output

Rationale

Unification of commitments results in:

  • removal of a concept of a seal closing method/commitment scheme (since just one exists);
  • removal of contract commitments to the seal closing method;
  • removal of method field in seal definition (anyway already happened in v0.2);
  • removal of method from invoices;
  • differentiation of contracts into two unrelated sets (opret and tapret), which would be never able to interact (basically two independent "RGB protocols");
  • differentiation between tapret and opret wallets, providing a single RGB wallet API;
  • all contracts will have access to Lightning network (before it was not possible for tapret contracts);
  • all contracts will have access a simplified use of hardware wallets via bitcoin apps (before it was not possible for tapret contracts).

FAQ

  • No, this doesn't introduce requirement for a specific ordering of outputsl; this requirement was as well present before for opret and tapret schemes;
  • No, this doesn't mean a client software needs to change; since it is related to a new v0.12 version, which is a consensus breaking and requires changes to software integration anyway.

Implementation

Tracking issue for the implementation in v0.12 is RGB-WG/rgb#275

Appendix

[1] The complexities of two commitment schemes

image

[2] Ideas tried alognside the idea of unification

image

@dr-orlovsky dr-orlovsky added epic Epic task covering multiple steps of implementation C-pushback Consensus-breaking change S-implemented labels Jan 15, 2025
@dr-orlovsky dr-orlovsky added this to the v0.12 milestone Jan 15, 2025
@dr-orlovsky dr-orlovsky moved this to In review in RGB release v0.12 Jan 15, 2025
@St333p
Copy link

St333p commented Jan 15, 2025

One possible downside of this proposal is forward compatibility in case different seal close methods will be developed in the future. I think we cannot exclude that new bitcoin TX output formats (or new protocols) may be developed and that they could require creating new close methods to have RGB work on them.

This may be solved by having contracts commit to a set of supported close methods, which for now will be {tapret, opret} in the vast majority of cases. This way new close methods won't affect existing contracts and it will be possible to introduce them in a backwards compatible way.

The algorithm to identify the output in which commitment is found would become:

  1. iterate over contracts that are involved in the tx
  2. iterate over tx outputs
  3. iterate over close methods committed by the contract
  4. if an output corresponds to the close method (e.g.: op-return<->opret, p2tr<->tapret):
    • check the commitment to all the seals involving the current contract
    • proceed to the next contract
  5. if neither, proceed to the next output

As proposed by @fedsten, one could also achieve more flexibility by committing to a more specific seal closing logic than just a set of commitment schemes, for instance:

  • LastTapretThenOpret: pick the last taproot output if there is one, otherwise pick the last opreturn
  • FirstOpretTapretKeytweak: pick the first output that is either opreturn, taproot or wpkh (with key tweak)

These two approaches probably result in different levels of added complexity to the code, it's not entirely clear to me which one is better.

@dr-orlovsky
Copy link
Member Author

dr-orlovsky commented Jan 15, 2025

The whole point of a proposal is to remove even the ability to have multiple commitment methods in RGB. This was preventing us from moving stable for the last two years.

Strong concept NACK to allowing different seal closing methods in different contracts: it invalidates the whole value of seal unification.

Anyway, different seal close methods are simply different and independent RGB protocols. Thus, if Bitcoin changes, a new protocols may be created.

There is no backward compatibility issue since the contract commits to the protocol at creation time; once created new seal methods can't be added anyway.

@adambor
Copy link

adambor commented Jan 15, 2025

ACK - I think this is a clever solution for the opret/tapret dichotomy issue and it simplifies reasoning about possible attacks.

Just throwing in some more (quite possibly unnecessary) ideas here:

1. Proving that taproot output doesn't contain any commitment

According to Constructing and spending Taproot outputs section of BIP341 wiki:

If the spending conditions do not require a script path, the output key should commit to an unspendable script path instead of having no script path. This can be achieved by computing the output key point as Q = P + int(hashTapTweak(bytes(P)))G.

Not sure if this is implemented in practice, but if yes, it could be used to prove that a given taproot output doesn't contain any commitment and should therefore be skipped in the iteration.

2. Skipping OP_RETURNs of invalid size

i.e. if the OP_RETURN output script is not of size 34 (OP_RETURN OP_PUSH32 <32 bytes commitment>) we can also skip it, as it for sure cannot contain a valid commitment. This might be useful for some scritpless swap protocols swapping between assets that use OP_RETURN (such as Runes, Omni) and RGB assets.

@dr-orlovsky
Copy link
Member Author

Yeah, basically one can always prove that a specific transaction output doesn't contain a valid DBP - in for both OP_RETURN and P2TR outputs. For OP_RETURN one just need to provide the source data (or, in case of length check, no data are needed). For P2TR, one can show that there is no script tree, or that it doesn't contain on level 2 any DBC script (this is already done using TapretProof structure anyway).

But I think such "proving no commitment is present" are useful for fallback seals, - but should not be used in the happy path, since they need consensus validation more complex with no benefit (giving fallback mechanism).

@fedsten
Copy link
Member

fedsten commented Jan 16, 2025

The algorithm details are the following:
iterate over tx outputs
if an output is OP_RETURN, check opret commitment to all the seals (remember seals definitions are commitment-agnostic). Stop
if an output is taproot, assume tapret commitment, and require proof. Stop
if neither, process to the next output

Regarding the algorithm I suggest to change it as follow:

  1. First iterates over all outputs and check if any of them is an OP_RETURN
  2. If there is an OP_RETURN, check the opret commitment. Stop
  3. If there is no OP_RETURN, iterate again until you find a Taroot output and check the tarpet commitment

The advantage of this is that it is possible to have a transaction with several Taproot output and an opret commitment without the need for the OP_RETURN to be the first output. This can be useful to leverage the simplicity of opret also when dealing with L2 protocols that use Taproot outputs and require some specific output ordering mechanism that may not allow to keep the OP_RETURN as first output (e.g. lexicographic ordering). A practical application of this can be to be able to use a future version of a lightning implementation that uses Taproot outputs while still being able to use opret.

The downside is that it will make it much harder to use RGB together in the same transaction with other OP_RETURN based protocols that don't support MPC. Personally I don't see a use case for this, but if other have a use case in mind and see this as a too big limitation than this proposal is probably not ideal.

@dr-orlovsky
Copy link
Member Author

I agree, @fedsten

Regarding multiple OP_RETURN outputs: they are non-standard, meaning such transactions won't normally be relayed. This means RGB opret was never compatible with other OP_RETURN-using protocols.

Yes, one can publish tx with multiple OP_RETURN outputs directly through miner, and RGB consensus need to account for such possibility (this is why we take the first OP_RETURN output), but practically it was never compatible with other OP_RETURN protocols.

@adambor
Copy link

adambor commented Jan 16, 2025

I would oppose the approach proposed by @fedsten

The downside is that it will make it much harder to use RGB together in the same transaction with other OP_RETURN based protocols that don't support MPC. Personally I don't see a use case for this, but if other have a use case in mind and see this as a too big limitation than this proposal is probably not ideal.

This will for sure break all scriptless (or PSBT single-transaction based) swap protocols between OP_RETURN based protocols and RGB.

This works with the original algorithm as one can just use tapret commitment for RGB in an output that is placed before an OP_RETURN of the other protocol (such as Runes), but in the version that @fedsten proposed this will no longer be possible, as OP_RETURN would just take precedence and no RGB commitment could be made, ruling out any swaps between OP_RETURN and RGB based assets.

@zoedberg
Copy link

@dr-orlovsky could you please explain your statement

Yeah, basically one can always prove that a specific transaction output doesn't contain a valid DBP - in for both OP_RETURN and P2TR outputs. For OP_RETURN one just need to provide the source data (or, in case of length check, no data are needed)

I don't understand how one could prove that an OP_RETURN doesn't contain a valid DBC by only checking the source data. An attacker could create 2 different consignments as source data, each one associated to a different but valid DBC, and give them to 2 different receivers. The receivers would not be able to detect that one of the 2 OP_RETURN outputs is containing a valid DBC. IMO a length check is needed when there are multiple OP_RETURN outputs.

@St333p
Copy link

St333p commented Jan 21, 2025

Let me try to summarize and give a structure to what's on the table for this topic.

  1. Initial proposal by @dr-orlovsky: hardcode the seal closing strategy at the protocol level
    • rgb anchor is in the first output that is either taproot or op_return
    • we can discuss alternative strategies (e.g: op_returns first, then taproots; with or without data size check)
      • this may be necessary to support taproot-based LN channels, which relies on lexicographic ordering of outputs
    • whatever we choose, some protocol enhancements will require backwards incompatible changes, e.g:
      • support possible new close methods, that may arise for instance with future bitcoin opcodes
      • compatibility with some other protocol (swaps with Runes/Omni, something that has not launched yet)
    • very simple logic, we avoid the complexity of having more than one commitment in the same transaction
  2. My proposal: hardcode seal closing strategy but commit to the set of close methods in contract
    • rgb anchor is in the first output that is of one of the types that the contract commits to
    • we can discuss alternative strategies (e.g: having a priority associated to close methods; with or without data size check)
    • we may implement new close methods in the future and assets need to opt-in to it
    • we may still find incompatibilities with some other protocol (swaps with Runes/Omni, something that has not launched yet)
    • logic becomes a bit more complex, but we'd have no code duplication:
      • there can be more than one commitment in the same transaction, but it's easy to identify it for a given contract
      • there can't be more than one seal type for every contract, which may lead to inflation bugs in case of mistakes
      • code that identifies the output containing the anchor applies to every transaction (no duplication)
  3. @fedsten's proposal: contracts commit to the seal closing strategy in form of an enum
    • complete flexibility on the logic that identifies the anchor output
    • future proof regarding future close methods and protocol compatibility, at the cost of contract reissuance
    • more complex, but still probably a lot simpler than current v0.11:
      • there can be more commitments in a tx (but again, for different contracts, so no double spends should be possible)
      • every supported closing strategy (variant of the enum) needs to come with a method that identifies the anchor output in a tx deterministically
      • every supported close method (opret, tapret, ...) only needs to implement commitment extraction from the identified output

First and foremost: are we on the same page up to here? If we're not, then some of the proposals may need a bit more detail

Assuming we are, here is my opinion:

  • if we could come up with a closing strategy that is solid enough to not preclude any future use-cases, I'd go for proposal 1. Less is more.
  • however, we can't predict what could happen in the future:
    • maybe a new bicoin opcode will allow a smarter commitment than both tapret and opret
    • maybe a protocol that doesn't even exist yet will be incompatible with RGB because of the specific strategy we chose
  • between 2. and 3. I don't have a strong opinion, I guess it's a tradeoff between protocol complexity and extensibility.
  • in any case, protocol developers will have control over what will be possible in the future, by explicitely allowing a different seal closing strategy that the contract devs may choose. So, even if we go for maximum flexibility, we'll always have the choice to not support some future closing strategy if the benefits won't outweigh the added complexity.
  • in case we are lucky and will never need to change the initially defined seal closing strategy, 1. and 3. are simpler than 2. since they guarantee there is always a single anchor output

@dr-orlovsky
Copy link
Member Author

It was agreed on a joint call to leave things as in the initial proposal: the first either OP_RETURN or P2TR output may only have the deterministic biticoin commitment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-pushback Consensus-breaking change epic Epic task covering multiple steps of implementation S-implemented
Projects
Status: In review
Development

No branches or pull requests

5 participants