Skip to content
Merged
Show file tree
Hide file tree
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
130 changes: 130 additions & 0 deletions CHIPs/chip-0042.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
CHIP Number | 0042
:-------------|:----
Title | Protected Single Sided Offers
Description | Describes a way for wallets to securely make and take single sided offers.
Author | [Brandon Haggstrom](https://github.com/Rigidity)
Editor | [Dan Perry](https://github.com/danieljperry)
Comments-URI | [CHIPs repo, PR #143](https://github.com/Chia-Network/chips/pull/143)
Status | Final
Category | Informational
Sub-Category | Guideline
Created | 2025-02-04
Requires | None
Replaces | None
Superseded-By | None

## Abstract

Currently many Chia wallets support making offer files which either offer or request assets for nothing in return. This is useful for giveaways and invoices, where you don't necessarily know the receive address of the other involved party. However, this is currently unsafe and this CHIP aims to resolve that by protecting these single sided offers via intermediate coin spends and BLS aggregate signatures.

## Motivation

The primary use case for single sided offers currently is to create QR codes or NFC chips that contain giveaways embedded into them. In person, someone can scan with their wallet and receive the asset without needing to trade XCH or anything else for it. The maker of the offer can even include fees to simplify the process for people who may not already have assets in their wallet.

### Background on offers

To understand why single sided offers are problematic today, we need to take a step back and look at how making an offer works (simplified a bit):
1. The maker of the offer spends their coins and creates new intermediate "settlement coins" which can be used by the taker to fulfill their end of the payment.
2. These coin spends also assert puzzle announcements to ensure that the settlement payments have been made for the assets they are requesting.
3. The requested payments are included as placeholder coin spends, and the rest of the spend bundle is signed by the maker.
4. The partially constructed and signed spend bundle is encoded as bech32m with the offer prefix.

This offer can then be taken through similar steps:
1. The partial spend bundle is decoded and parsed, and requested payments are separated out from the maker's coin spends.
2. The settlement coins are spent to claim the offered coins and send them to the taker's wallet.
3. The requested payments are fulfilled by spending the taker's coins and (notably) asserting similar puzzle announcements to ensure that the maker's offered assets are actually received by the taker.
4. The taker's coin spends are signed and both the coin spends and signature are aggregated with the original offer file. This is then submitted to the mempool.

### The problem

This works great as long as both the maker and taker are spending coins and asserting the other end of the trade. Let's take a look at what happens if there aren't any requested assets:

1. The maker spends their coins to create intermediate "settlement coins", but doesn't include any announcement assertions because there are no requested payments.
2. The taker sends the settlement coins to their wallet, but they are unable to assert this announcement because they aren't spending any of their own coins.
3. Because the taker didn't spend anything to make this transaction, they don't have a signature to aggregate with the original.

In this scenario, if this transaction to take a single sided offer is submitted to the mempool, anyone can use Replace By Fee to alter the transaction and send the assets to their wallet instead of the original taker (even without violating the "superset rule", since all of the same coins have been spent). A farmer could also in theory do the same thing when constructing a block.

So, even though the original single sided offer was private, someone can come along and steal the giveaway without much work before it settles on-chain.

## Backwards Compatibility

This should be fully backwards compatible with existing offer implementations, with the caveat that users should ideally not accept single sided offers on wallets that don't abide by this CHIP, since they will not be protected in the same way. See the security section for more details.

In most cases, only the taker of the offer will need to do things differently than before.

## Rationale

I've chosen to solve this problem by creating an intermediate coin between the settlement coin and the taker's wallet, which asserts the payments to prevent them from being changed. This intermediate coin uses an aggregated signature to ensure that the original offer's signature isn't revealed anywhere. It also prevents Replace by Fee due to the superset rule.

Similar tricks are already being employed by community projects such as the [warp.green bridge](https://warp.green), since it's relatively unintrusive and doesn't require the maker to do anything differently.

The drawback of this approach is that the intermediate spend(s) add additional complexity and cost to the transaction on-chain, which could add up if there were substantial fee pressure and demand for block space. However, due to the simplicity and backwards compatibility of the solution, I think it's worth the tradeoff, at least for now.

## Specification

### Giveaway Offers

For giveaway offers to be protected, the offer file must remain private and the taker must include one or more coin spends which can assert the payments went through. Additionally, both the maker and taker must include an aggregate signature so that the original offer can't be reconstructed.

If the maker doesn't request any payments in return, the taker will not have an aggregated signature if the offer is taken normally. So, to fulfill the security requirements specified, we need to add an intermediate coin into the mix.

The simplest way to do this is to spend the maker's settlement coin the same way you normally would, by sending it to the taker's p2_puzzle_hash (address). However, we will ephemerally spend the coin we just created, as if this is one of the coins we are using to fulfill payments (even though we don't have any to make). In this case, we assert the settlement puzzle announcements to ensure the maker's payments end up in the right place (the taker's wallet).

By introducing this intermediate coin spend, we are making a signature which can be aggregated with the original offer. Due to this, it's impossible to replace the transaction since the two signatures can't be separated without having access to the original (private) offer off-chain.

And, even if someone were to gain access to that offer, the mempool currently would reject a Replace By Fee attempt due to the superset rule. However, a farmer could still pull it off in that case, so it's not a good idea to rely on this.

See the security section for important details on what to do if this kind of offer is not supported by a wallet.

### Invoice Offers

On the flip side, invoice offers can actually already be made secure by wallet implementations, since the taker wallet can assert their its payments end up in the right place. It's not important that the original offer be private since it's just requesting assets. Therefore, signature subtraction is not a risk either.

However, it does add additional cost to the transaction to create the settlement coins for invoice offers. An alternative implementation for taking these could be to simply _directly send_ to the payment p2_puzzle_hash (address) specified in the offer's requested payments.

It's also worth noting that using an offer file for this use case may not be the best option. You can simply give your address, amount, and memos to the recipient directly or use an alternative solution such as a deep link. These other solutions are viable but out of scope for this CHIP.

### Non-BLS Wallets

Wallets such as vaults introduce additional considerations, since coin spends don't necessarily include aggregated signatures.

If you are creating a single sided giveaway offer which would not otherwise include an aggregated signature on the maker side, the maker needs to employ the same intermediate coin trick laid out above on their end.

However, instead of using the same wallet puzzle, you will need to create a securely and randomly generated BLS secret key which is used for the intermediate coin, in order to provide that aggregated signature.

### Chart

This chart can also help with visually understanding how protected single sided offers work, but is a bit simplified:

![Single Sided Offers Chart](/assets/chip-0042/single_sided_offers.png "Single Sided Offers Chart")

## Test Cases

Sage Wallet has XCH, CAT, and NFT [single sided giveaway offer tests](https://github.com/xch-dev/sage/blob/1c413129b8fe8b3112c4b78f0e9e22ce45771f9f/crates/sage-wallet/src/wallet/offer.rs#L653-L805) at the time of writing this CHIP.

## Reference Implementation

Sage Wallet has an implementation of [single sided giveaway offers](https://github.com/xch-dev/sage/blob/1c413129b8fe8b3112c4b78f0e9e22ce45771f9f/crates/sage-wallet/src/wallet/offer/unlock_assets.rs) at the time of writing this CHIP.

## Security

For wallets it's important they support taking single sided offers as described in the specification in order for it to be secure. However, if a wallet does not wish to protect taking the offer in this way, it should instead reject the user doing so on accident and provide a reasonable error or warning message.

There are no known security considerations needed for existing BLS wallets when _making_ single sided offers, rather than taking them.

It's true that wallets may not securely handle single sided offers at first, until this CHIP is adopted. This is not a new problem, but rather the thing this CHIP aims to solve. Although, encouraging the use of single sided offers will make this risk more meaningful.

Perhaps it makes sense, at least in the short term, to provide a warning in wallets when making single sided offers, that they should only be taken by the subset of wallets that can do so securely.

Someone could create a bot which monitors the mempool for single sided offer acceptances and tries to enact Replace by Fee to steal the giveaways.

## Additional Assets

* [Single Sided Offers Chart](/assets/chip-0042/single_sided_offers.png "Single Sided Offers Chart")

## Copyright
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).



Binary file added assets/chip-0042/single_sided_offers.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading