spark_rust/wallet/handlers/
swap.rs

1use crate::constants::spark::DEFAULT_TRANSFER_EXPIRY;
2use crate::error::{IoError, NetworkError, SparkSdkError};
3use crate::signer::traits::derivation_path::SparkKeyType;
4use crate::signer::traits::SparkSigner;
5use crate::wallet::internal_handlers::traits::ssp::SspInternalHandlers;
6use crate::wallet::internal_handlers::traits::ssp::SwapLeaf;
7use crate::wallet::internal_handlers::traits::transfer::LeafKeyTweak;
8use crate::wallet::internal_handlers::traits::transfer::TransferInternalHandlers;
9use crate::wallet::leaf_manager::SparkNodeStatus;
10use crate::wallet::utils::bitcoin::{bitcoin_tx_from_bytes, parse_public_key, sighash_from_tx};
11use crate::wallet::utils::bitcoin::{
12    compute_taproot_key_no_script_from_internal_key, parse_secret_key,
13};
14use crate::SparkSdk;
15use bitcoin::key::Secp256k1;
16use spark_cryptography::adaptor_signature::apply_adaptor_to_signature;
17use spark_cryptography::adaptor_signature::generate_adaptor_from_signature;
18use spark_cryptography::adaptor_signature::generate_signature_from_existing_adaptor;
19use spark_protos::spark::query_nodes_request::Source;
20use spark_protos::spark::QueryNodesRequest;
21use spark_protos::spark::TreeNodeIds;
22
23impl<S: SparkSigner + Send + Sync + Clone + 'static> SparkSdk<S> {
24    #[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
25    pub async fn request_leaves_swap(&self, target_amount: u64) -> Result<String, SparkSdkError> {
26        // Lock all availables leaves to provide to SSP. This allows the SSP to aggregate
27        // and optimize the number of leaves.
28        let available_leaves = self
29            .leaf_manager
30            .lock_available_bitcoin_leaves(SparkNodeStatus::Swap);
31
32        // leaf selection for atomic swap
33        let mut leaf_key_tweaks = Vec::with_capacity(available_leaves.leaves.len());
34
35        for leaf in available_leaves.leaves.iter() {
36            let tree_leaf = leaf.get_tree_node()?;
37
38            let old_signing_private_key = self.signer.expose_leaf_secret_key_for_transfer(
39                leaf.get_id().clone(),
40                SparkKeyType::BaseSigning,
41                0,
42                self.config.spark_config.network.to_bitcoin_network(),
43            )?;
44
45            // Generate new private key for the leaf
46            let new_signing_public_key = self.signer.new_ephemeral_keypair()?;
47
48            leaf_key_tweaks.push(LeafKeyTweak {
49                leaf: tree_leaf,
50                old_signing_private_key,
51                new_signing_public_key,
52            });
53        }
54
55        let expiry_time = chrono::Utc::now().timestamp() as u64 + DEFAULT_TRANSFER_EXPIRY;
56
57        let (transfer, refund_signature_map) = self
58            .send_transfer_sign_refunds(
59                &leaf_key_tweaks,
60                &self.config.spark_config.ssp_identity_public_key,
61                expiry_time,
62            )
63            .await?;
64
65        let leaf = transfer.leaves[0].leaf.as_ref().unwrap();
66        let leaf_refund_signature = refund_signature_map[&leaf.id].clone();
67
68        let adaptor_signature =
69            generate_adaptor_from_signature(leaf_refund_signature.as_slice().try_into().unwrap())
70                .unwrap();
71
72        let mut user_leaves = Vec::new();
73        user_leaves.push(SwapLeaf {
74            leaf_id: transfer.leaves[0].leaf.as_ref().unwrap().id.clone(),
75            raw_unsigned_refund_transaction: hex::encode(
76                &transfer.leaves[0].intermediate_refund_tx,
77            ),
78            adaptor_added_signature: hex::encode(adaptor_signature.signature),
79        });
80
81        for leaf in transfer.leaves.iter().skip(1) {
82            let leaf_id = leaf.leaf.as_ref().unwrap().id.clone();
83            let leaf_refund_signature = refund_signature_map[&leaf_id].clone();
84
85            let signature = generate_signature_from_existing_adaptor(
86                &leaf_refund_signature,
87                adaptor_signature.adaptor_private_key.as_slice(),
88            )
89            .unwrap();
90
91            user_leaves.push(SwapLeaf {
92                leaf_id,
93                raw_unsigned_refund_transaction: hex::encode(&leaf.intermediate_refund_tx),
94                adaptor_added_signature: hex::encode(signature),
95            });
96        }
97
98        // total leaf value
99        let total_amount = leaf_key_tweaks
100            .iter()
101            .map(|leaf| leaf.leaf.value)
102            .sum::<u64>();
103
104        let secp = Secp256k1::new();
105        let adaptor_private_key =
106            parse_secret_key(&adaptor_signature.adaptor_private_key.to_vec())?;
107        let adaptor_public_key = adaptor_private_key.public_key(&secp);
108
109        let (request_id, leaves) = self
110            .request_swap_leaves_with_ssp(
111                hex::encode(adaptor_public_key.serialize()),
112                total_amount,
113                target_amount,
114                0,
115                user_leaves,
116            )
117            .await?;
118
119        let mut spark_client = self.config.spark_config.get_spark_connection(None).await?;
120        let network = self.config.spark_config.network;
121
122        for leaf in &leaves {
123            let leaf_id = leaf.leaf_id.clone();
124            let response = spark_client
125                .query_nodes(QueryNodesRequest {
126                    source: Some(Source::NodeIds(TreeNodeIds {
127                        node_ids: vec![leaf_id.clone()],
128                    })),
129                    include_parents: Default::default(),
130                    network: network.marshal_proto(),
131                })
132                .await
133                .map_err(|status| SparkSdkError::from(NetworkError::Status(status)))?
134                .into_inner();
135
136            let node = response
137                .nodes
138                .get(&leaf_id)
139                .ok_or(SparkSdkError::from(NetworkError::InvalidResponse))?;
140
141            #[cfg(feature = "telemetry")]
142            tracing::trace!(
143                leaf_id = leaf_id.clone(),
144                "Leaf balance returned for SSP swap: {:?}",
145                node.value
146            );
147
148            let node_tx = bitcoin_tx_from_bytes(&node.node_tx)?;
149            let refund_tx_bytes = hex::decode(&leaf.raw_unsigned_refund_transaction)
150                .map_err(|err| SparkSdkError::from(IoError::Decoding(err)))?;
151            let refund_tx = bitcoin_tx_from_bytes(&refund_tx_bytes)?;
152
153            let sighash = sighash_from_tx(&refund_tx, 0, &node_tx.output[0])?;
154
155            // First, parse the public key from the node
156            let verifying_public_key = parse_public_key(&node.verifying_public_key)?;
157
158            let taproot_key = compute_taproot_key_no_script_from_internal_key(
159                &verifying_public_key.x_only_public_key().0.serialize(),
160            )?;
161
162            let adaptor_signature_bytes = hex::decode(&leaf.adaptor_added_signature)
163                .map_err(|err| SparkSdkError::from(IoError::Decoding(err)))?;
164
165            let _ = apply_adaptor_to_signature(
166                &taproot_key,
167                &sighash,
168                &adaptor_signature_bytes,
169                &adaptor_private_key.secret_bytes(),
170            )
171            .unwrap();
172        }
173
174        let transfer_id = transfer.id.clone();
175        self.send_transfer_tweak_key(transfer.clone(), &leaf_key_tweaks, &refund_signature_map)
176            .await?;
177
178        // TODO: print the UUID in the response
179        let _completion = self
180            .complete_leaves_swap_with_ssp(
181                hex::encode(adaptor_private_key.secret_bytes()),
182                transfer_id,
183                request_id.clone(),
184            )
185            .await?;
186
187        self.claim_transfers().await?;
188
189        // Unlock and delete old leaves.
190        let leaf_ids = available_leaves
191            .leaves
192            .iter()
193            .map(|leaf| leaf.get_id().clone())
194            .collect();
195        self.leaf_manager
196            .unlock_leaves(available_leaves.unlocking_id.unwrap(), &leaf_ids, true)?;
197
198        // return the leaf ID with the amount that was swapped
199        let leaf_id = leaves[0].leaf_id.clone();
200
201        Ok(leaf_id)
202    }
203}