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

Chain swaps #51

Merged
merged 13 commits into from
Jun 27, 2024
Merged

Chain swaps #51

merged 13 commits into from
Jun 27, 2024

Conversation

dangeross
Copy link
Contributor

@dangeross dangeross commented Jun 14, 2024

This PR adds Chain Swaps. It should include:

  • Full Chain swap Boltz API.
  • Claiming cooperatively / non-cooperatively, including posting the claim tx details to exchange partial signatures.
  • Refunding cooperatively / non-cooperatively.

Tested:

  • LBTC-BTC claim cooperative flow
  • LBTC-BTC refund cooperative/non-cooperative flow
  • BTC-LBTC claim cooperative flow
  • BTC-LBTC refund cooperative/non-cooperative flow

@dangeross dangeross marked this pull request as ready for review June 20, 2024 08:37
@dangeross dangeross changed the title [Draft] Chain swaps Chain swaps Jun 20, 2024
@i5hi
Copy link
Collaborator

i5hi commented Jun 22, 2024

Massive!

Thanks @dangeross !

Will review over the weekend.

@i5hi i5hi self-assigned this Jun 23, 2024
@i5hi
Copy link
Collaborator

i5hi commented Jun 24, 2024

@dangeross couldnt' find tests for this. Attempting to put one together now.

@dangeross
Copy link
Contributor Author

couldnt' find tests for this. Attempting to put one together now.

Thanks @i5hi, let me know if I can help at all.

It would be cool if @michael1011 could confirm the boltz states used in the chain swaps.

@i5hi i5hi requested a review from michael1011 June 24, 2024 05:09
@i5hi
Copy link
Collaborator

i5hi commented Jun 24, 2024

@dangeross When creating a ChainSwap from BTC to LBTC we would need to use the BtcSwapScript/TxV2 structs right?

@i5hi
Copy link
Collaborator

i5hi commented Jun 24, 2024

Additionally, its a bit tricky for the user of the library to know who part of the ChainSwapResponse to use as ChainSwapDetails in this function:

 pub fn chain_from_swap_resp(
        side: Side,
        chain_swap_details: ChainSwapDetails,
        our_pubkey: PublicKey,
        )

There is a claim and lockup - ChainSwapDetails - so maybe better for this function to take the ChainSwapResponse object and let the method handle which of the two ChainSwapDetails to use?

@dangeross
Copy link
Contributor Author

When creating a ChainSwap from BTC to LBTC we would need to use the BtcSwapScript/TxV2 structs right?

The lockup side is BtcSwapScript, the claim side is LBtcSwapScript

@dangeross
Copy link
Contributor Author

so maybe better for this function to take the ChainSwapResponse object and let the method handle which of the two ChainSwapDetails to use?

Could work, maybe also having two wrapper functions, one for claim and one for lockup. E.g. chain_claim_from_swap_resp and chain_lockup_from_swap_resp

@dangeross
Copy link
Contributor Author

If it helps @i5hi, here is our current usage. I think having 2 functions is a better approach, then passing the Side is not required. But the Side or maybe details_type Claim/Lockup need to be stored to switch the order of the musig key aggregation
https://github.com/breez/breez-liquid-sdk/blob/bcb47432608f8b91e40a1806b47c1f496ccc76c6/lib/core/src/model.rs#L369-L403

@i5hi
Copy link
Collaborator

i5hi commented Jun 25, 2024

@dangeross The sign_claim methods now use a new cooperative Cooperative :

    pub fn sign_claim(
        &self,
        keys: &Keypair,
        preimage: &Preimage,
        absolute_fees: Amount,
        is_cooperative: Option<Cooperative>,
    ) -> Result<Transaction, Error> 

The updated tests in liquid/bitcoin_v2.rs have been updated as follows:

                        let tx = claim_tx
                            .sign_claim(
                                &our_keys,
                                &preimage,
                                Amount::from_sat(1000),
                                Some(Cooperative {
                                    boltz_api: &boltz_api_v2,
                                    swap_id: swap_id.clone(),
                                    pub_nonce: None,
                                    partial_sig: None,
                                }),
                            )
                            .unwrap();

This would fail as pub_nonce and partial_sig are not provided.

I went through the breez code base to understand how to use this properly. Can you confirm that this would be correct usage?

                        let claim_tx = LBtcSwapTxV2::new_claim(
                           claim_script.clone(),
                           claim_address.clone(),
                           &ElectrumConfig::default_liquid(),
                           BOLTZ_TESTNET_URL_V2.to_string(),
                           swap_id.clone(),
                       )
                       .unwrap();
                       let refund_tx = BtcSwapTxV2::new_refund(
                           lockup_script.clone(),
                           &refund_address,
                           &ElectrumConfig::default_bitcoin(),
                       )
                       .unwrap();
                       let claim_tx_response =
                           boltz_api_v2.get_chain_claim_tx_details(&swap_id).unwrap();
                       let (partial_sig, pub_nonce) = refund_tx
                           .partial_sig(
                               &our_refund_keys,
                               &claim_tx_response.pub_nonce,
                               &claim_tx_response.transaction_hash,
                           )
                           .unwrap();
                       let tx = claim_tx
                           .sign_claim(
                               &our_claim_keys,
                               &preimage,
                               Amount::from_sat(1000),
                               Some(Cooperative {
                                   boltz_api: &boltz_api_v2,
                                   swap_id: swap_id.clone(),
                                   pub_nonce: Some(pub_nonce),
                                   partial_sig: Some(partial_sig),
                               }),
                           )
                           .unwrap();

@i5hi
Copy link
Collaborator

i5hi commented Jun 25, 2024

I've noticed this actually won't be an issue with LN swaps

            let partial_sig_resp = match self.swap_script.swap_type {
                SwapType::Chain => match (pub_nonce, partial_sig) {
                    (Some(pub_nonce), Some(partial_sig)) => boltz_api.post_chain_claim_tx_details(
                        &swap_id,
                        preimage,
                        pub_nonce,
                        partial_sig,
                        ToSign {
                            pub_nonce: claim_pub_nonce.serialize().to_lower_hex_string(),
                            transaction: claim_tx_hex,
                            index: 0,
                        },
                    ),
                    _ => Err(Error::Protocol(
                        "Chain swap claim needs a partial_sig".to_string(),
                    )),
                },
                SwapType::ReverseSubmarine => boltz_api.get_reverse_partial_sig(
                    &swap_id,
                    &preimage,
                    &claim_pub_nonce,
                    &claim_tx_hex,
                ),
                _ => Err(Error::Protocol(format!(
                    "Cannot get partial sig for {:?} Swap",
                    self.swap_script.swap_type
                ))),
            }?;

So i will not change the the LN swap tests. Let me know if the previous comment is the correct flow for chain swaps.

@dangeross
Copy link
Contributor Author

@i5hi the pub_nonce and partial_sig are only used in the chain swap claim where there is this double exchange of nonce's/sig's. So all other swap types work without these set in Cooperative

@i5hi
Copy link
Collaborator

i5hi commented Jun 25, 2024

And the pub_nonce and partial_sig are from the refund_tx right?

@i5hi
Copy link
Collaborator

i5hi commented Jun 25, 2024

And when refunding they would be from the claim_tx?

@dangeross
Copy link
Contributor Author

dangeross commented Jun 25, 2024

And the pub_nonce and partial_sig are from the refund_tx right?

Yes

And when refunding they would be from the claim_tx?

They are only needed for the claim flow

Copy link
Collaborator

@michael1011 michael1011 left a comment

Choose a reason for hiding this comment

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

Great stuff! Nice to see that you managed to make it work even with our lackluster documentation (coming soon 🤞)

Left some minor comments. Some test cases would be nice too

src/swaps/bitcoin.rs Show resolved Hide resolved
src/swaps/boltzv2.rs Outdated Show resolved Hide resolved
src/swaps/bitcoinv2.rs Outdated Show resolved Hide resolved
src/swaps/bitcoinv2.rs Outdated Show resolved Hide resolved
src/swaps/bitcoinv2.rs Outdated Show resolved Hide resolved
src/swaps/bitcoinv2.rs Outdated Show resolved Hide resolved
@@ -888,8 +1015,9 @@ impl BtcSwapTxV2 {
/// Multiply the size by the fee_rate to get the absolute fees.
pub fn size(&self, keys: &Keypair, preimage: &Preimage) -> Result<usize, Error> {
let dummy_abs_fee = 5_000;
// Can only calculate non-coperative claims
Copy link
Collaborator

Choose a reason for hiding this comment

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

In case there is a need for calculating those, schnorr signatures are always 64 bytes. Could be stubbed

"transaction.server.confirmed".to_string()
}
ChainSwapStates::TransactionClaimed => "transaction.claimed".to_string(),
ChainSwapStates::TransactionClaimPending => "transaction.claim.pending".to_string(),
Copy link
Collaborator

Choose a reason for hiding this comment

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

transaction.claim.pending is for submarine swaps, when we paid the invoice but wait before broadcasting the claim transaction in case you happen to come online to do a key path spend with us

In chain swaps you either:

  • key path spend with us and both claim transactions, ours and yours, are broadcasted
  • you enforce via script path and we do the same thing

So, that status doesn't need to be here

src/swaps/liquidv2.rs Outdated Show resolved Hide resolved
Comment on lines +449 to +460
SwapType::Chain => {
boltz_client
.get_chain_txs(swap_id)?
.user_lock
.ok_or(Error::Protocol(
"No user_lock transaction for Chain Swap available".to_string(),
))?
.transaction
.hex
}
SwapType::ReverseSubmarine => boltz_client.get_reverse_tx(swap_id)?.hex,
SwapType::Submarine => boltz_client.get_submarine_tx(swap_id)?.hex,
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is getting a little confusing. For submarine and chain swaps this would fetch the lockup transaction of the user. For reverse swaps the one of the server. How about returning a struct with optional user and server transaction?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Updated the function name to fetch_lockup_utxo_boltz does that make it more clear?

i5hi and others added 2 commits June 27, 2024 17:06
@i5hi
Copy link
Collaborator

i5hi commented Jun 27, 2024

Damn! I merged michael's update to the function name and it broke the test because its usage needs to be updated.

Sorry guys! I will just merge and fix it on trunk immediately.

Can I get an ACK on doing it this way?

Or @dangeross would you be able to fix it from your branch?

Or recommend another strategy?

@dangeross
Copy link
Contributor Author

ACK @i5hi. Feel free to commit to the PR

@i5hi i5hi merged commit 2cbde83 into SatoshiPortal:trunk Jun 27, 2024
3 of 4 checks passed
@i5hi
Copy link
Collaborator

i5hi commented Jun 27, 2024

Patched on trunk!
Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants