spark_rust/wallet/handlers/
create_tree.rs1use std::sync::Arc;
2
3use bitcoin::{
4 absolute::LockTime, key::Secp256k1, transaction::Version, Address, OutPoint, ScriptBuf,
5 Transaction, TxIn, TxOut,
6};
7use parking_lot::RwLock;
8use spark_protos::{
9 common::SignatureIntent,
10 spark::{
11 FinalizeNodeSignaturesRequest, NodeSignatures, SigningJob, StartTreeCreationRequest,
12 TreeNode, Utxo,
13 },
14};
15
16use crate::{
17 error::{CryptoError, SparkSdkError, TransactionError},
18 signer::{default_signer::marshal_frost_commitments, traits::SparkSigner},
19 wallet::{
20 internal_handlers::traits::create_tree::CreateTreeInternalHandlers,
21 leaf_manager::SparkLeaf,
22 utils::{
23 bitcoin::{
24 bitcoin_tx_from_bytes, parse_public_key, serialize_bitcoin_transaction,
25 sighash_from_tx,
26 },
27 sequence::initial_sequence,
28 },
29 },
30 SparkSdk,
31};
32
33pub struct CreateTreeSdkResponse {
34 pub nodes: Vec<TreeNode>,
35}
36
37impl<S: SparkSigner + Send + Sync + Clone + 'static> SparkSdk<S> {
38 #[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
87 async fn create_tree(
88 &self,
89 deposit_transaction: Option<Transaction>,
90 parent_node: Option<Arc<RwLock<TreeNode>>>,
91 vout: u32,
92 split_level: u32,
93 parent_signing_public_key: Vec<u8>,
94 ) -> Result<CreateTreeSdkResponse, SparkSdkError> {
95 let time_start = std::time::Instant::now();
97 let deposit_address_response = self
98 .generate_deposit_address_for_tree(
99 deposit_transaction.clone(),
100 parent_node.clone(),
101 vout,
102 parent_signing_public_key.clone(),
103 split_level,
104 )
105 .await?;
106 let duration = time_start.elapsed();
107 #[cfg(feature = "telemetry")]
108 tracing::debug!(duration = ?duration, "generate_deposit_address_for_tree");
109
110 let time_start = std::time::Instant::now();
112 let finalize_tree_response = self
113 .finalize_tree_creation(
114 deposit_transaction,
115 parent_node,
116 vout,
117 deposit_address_response.tree,
118 )
119 .await?;
120 let duration = time_start.elapsed();
121 #[cfg(feature = "telemetry")]
122 tracing::debug!(duration = ?duration, "finalize_tree_creation");
123
124 let finalize_tree_response = finalize_tree_response.finalize_tree_response;
125 let nodes = finalize_tree_response.nodes;
126
127 Ok(CreateTreeSdkResponse { nodes })
128 }
129
130 pub(crate) async fn create_tree_root(
131 &self,
132 signing_pubkey_bytes: Vec<u8>,
133 verifying_pubkey_bytes: Vec<u8>,
134 deposit_tx_bytes: Vec<u8>,
135 vout: u32,
136 ) -> Result<CreateTreeSdkResponse, SparkSdkError> {
137 let deposit_tx = bitcoin_tx_from_bytes(&deposit_tx_bytes)?;
138
139 let mut root_tx = Transaction {
140 version: Version::TWO,
141 lock_time: LockTime::ZERO,
142 input: vec![],
143 output: vec![],
144 };
145
146 let output = deposit_tx
147 .output
148 .get(vout as usize)
149 .ok_or(SparkSdkError::from(TransactionError::InvalidDepositTx(
150 format!("Invalid deposit transaction: {}", vout),
151 )))?;
152
153 let script = output.script_pubkey.clone();
154 let amount = output.value;
155
156 let txid = deposit_tx.compute_txid();
157 root_tx.input.push(TxIn {
158 previous_output: OutPoint { txid, vout },
159 script_sig: ScriptBuf::new(),
160 sequence: Default::default(),
161 witness: Default::default(),
162 });
163
164 root_tx.output.push(TxOut {
165 script_pubkey: script,
166 value: amount,
167 });
168
169 let root_nonce_commitment = self.signer.new_frost_signing_noncepair()?;
171
172 let root_tx_sighash = sighash_from_tx(&root_tx, 0, output)?;
174
175 let mut refund_tx = Transaction {
177 version: Version::TWO,
178 lock_time: LockTime::ZERO,
179 input: vec![],
180 output: vec![],
181 };
182
183 let root_txid = root_tx.compute_txid();
184 refund_tx.input.push(TxIn {
185 previous_output: OutPoint {
186 txid: root_txid,
187 vout: 0,
188 },
189 script_sig: ScriptBuf::new(),
190 sequence: initial_sequence(),
191 witness: Default::default(),
192 });
193
194 let secp = Secp256k1::new();
196 let signing_pubkey = parse_public_key(&signing_pubkey_bytes)?;
197 let refund_address = Address::p2tr(
198 &secp,
199 signing_pubkey.x_only_public_key().0,
200 None,
201 self.config.spark_config.network.to_bitcoin_network(),
202 );
203 let refund_script = refund_address.script_pubkey();
204
205 refund_tx.output.push(TxOut {
206 script_pubkey: refund_script,
207 value: amount,
208 });
209
210 let refund_nonce_commitment = self.signer.new_frost_signing_noncepair()?;
212
213 let refund_tx_sighash = sighash_from_tx(&refund_tx, 0, &root_tx.output[0])?;
215
216 let root_tx_bytes = serialize_bitcoin_transaction(&root_tx)?;
218 let refund_tx_bytes = serialize_bitcoin_transaction(&refund_tx)?;
219
220 let request = StartTreeCreationRequest {
222 identity_public_key: self.get_spark_address()?.serialize().to_vec(),
223 on_chain_utxo: Some(Utxo {
224 vout,
225 raw_tx: deposit_tx_bytes,
226 network: self.config.spark_config.network.marshal_proto(),
227 }),
228 root_tx_signing_job: Some(SigningJob {
229 raw_tx: root_tx_bytes.clone(),
230 signing_public_key: signing_pubkey_bytes.clone(),
231 signing_nonce_commitment: Some(marshal_frost_commitments(&root_nonce_commitment)?),
232 }),
233 refund_tx_signing_job: Some(SigningJob {
234 raw_tx: refund_tx_bytes.clone(),
235 signing_public_key: signing_pubkey_bytes.clone(),
236 signing_nonce_commitment: Some(marshal_frost_commitments(
237 &refund_nonce_commitment,
238 )?),
239 }),
240 };
241
242 let tree_creation_response = self
243 .config
244 .spark_config
245 .call_with_retry(
246 request,
247 |mut client, req| Box::pin(async move { client.start_tree_creation(req).await }),
248 None,
249 )
250 .await?;
251
252 let node_id = tree_creation_response
253 .root_node_signature_shares
254 .clone()
255 .ok_or(SparkSdkError::from(
256 CryptoError::MissingRootNodeSignatureShares,
257 ))?
258 .node_id
259 .clone();
260
261 let sigantures = self.signer.sign_root_creation(
262 signing_pubkey_bytes.clone(),
263 verifying_pubkey_bytes.clone(),
264 root_tx_bytes.clone(),
265 refund_tx_bytes.clone(),
266 root_tx_sighash.to_vec(),
267 refund_tx_sighash.to_vec(),
268 root_nonce_commitment,
269 refund_nonce_commitment,
270 tree_creation_response,
271 )?;
272
273 let request = FinalizeNodeSignaturesRequest {
274 intent: SignatureIntent::Creation as i32,
275 node_signatures: vec![NodeSignatures {
276 node_id,
277 node_tx_signature: sigantures[0].clone(),
278 refund_tx_signature: sigantures[1].clone(),
279 }],
280 };
281
282 let finalize_node_signatures_response = self
283 .config
284 .spark_config
285 .call_with_retry(
286 request,
287 |mut client, req| {
288 Box::pin(async move { client.finalize_node_signatures(req).await })
289 },
290 None,
291 )
292 .await?;
293
294 let finalized_root_node = finalize_node_signatures_response.nodes[0].clone();
295
296 self.leaf_manager
297 .insert_leaves(vec![SparkLeaf::Bitcoin(finalized_root_node.clone())], false)?;
298
299 let response = CreateTreeSdkResponse {
300 nodes: vec![finalized_root_node],
301 };
302
303 Ok(response)
304 }
305}