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
33#[derive(Debug)]
34pub struct CreateTreeSdkResponse {
35 pub nodes: Vec<TreeNode>,
36}
37
38impl<S: SparkSigner + Send + Sync + Clone + 'static> SparkSdk<S> {
39 #[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
66 async fn create_tree(
67 &self,
68 deposit_transaction: Option<Transaction>,
69 parent_node: Option<Arc<RwLock<TreeNode>>>,
70 vout: u32,
71 split_level: u32,
72 parent_signing_public_key: Vec<u8>,
73 ) -> Result<CreateTreeSdkResponse, SparkSdkError> {
74 let time_start = std::time::Instant::now();
76 let deposit_address_response = self
77 .generate_deposit_address_for_tree(
78 deposit_transaction.clone(),
79 parent_node.clone(),
80 vout,
81 parent_signing_public_key.clone(),
82 split_level,
83 )
84 .await?;
85 let duration = time_start.elapsed();
86 #[cfg(feature = "telemetry")]
87 tracing::debug!(duration = ?duration, "generate_deposit_address_for_tree");
88
89 let time_start = std::time::Instant::now();
91 let finalize_tree_response = self
92 .finalize_tree_creation(
93 deposit_transaction,
94 parent_node,
95 vout,
96 deposit_address_response.tree,
97 )
98 .await?;
99 let duration = time_start.elapsed();
100 #[cfg(feature = "telemetry")]
101 tracing::debug!(duration = ?duration, "finalize_tree_creation");
102
103 let finalize_tree_response = finalize_tree_response.finalize_tree_response;
104 let nodes = finalize_tree_response.nodes;
105
106 Ok(CreateTreeSdkResponse { nodes })
107 }
108
109 pub(crate) async fn create_tree_root(
110 &self,
111 signing_pubkey_bytes: Vec<u8>,
112 verifying_pubkey_bytes: Vec<u8>,
113 deposit_tx_bytes: Vec<u8>,
114 vout: u32,
115 ) -> Result<CreateTreeSdkResponse, SparkSdkError> {
116 let deposit_tx = bitcoin_tx_from_bytes(&deposit_tx_bytes)?;
117
118 let mut root_tx = Transaction {
119 version: Version::TWO,
120 lock_time: LockTime::ZERO,
121 input: vec![],
122 output: vec![],
123 };
124
125 let output = deposit_tx
126 .output
127 .get(vout as usize)
128 .ok_or(SparkSdkError::from(TransactionError::InvalidDepositTx(
129 format!("Invalid deposit transaction: {}", vout),
130 )))?;
131
132 let script = output.script_pubkey.clone();
133 let amount = output.value;
134
135 let txid = deposit_tx.compute_txid();
136 root_tx.input.push(TxIn {
137 previous_output: OutPoint { txid, vout },
138 script_sig: ScriptBuf::new(),
139 sequence: Default::default(),
140 witness: Default::default(),
141 });
142
143 root_tx.output.push(TxOut {
144 script_pubkey: script,
145 value: amount,
146 });
147
148 let root_nonce_commitment = self.signer.generate_frost_signing_commitments()?;
150
151 let root_tx_sighash = sighash_from_tx(&root_tx, 0, output)?;
153
154 let mut refund_tx = Transaction {
156 version: Version::TWO,
157 lock_time: LockTime::ZERO,
158 input: vec![],
159 output: vec![],
160 };
161
162 let root_txid = root_tx.compute_txid();
163 refund_tx.input.push(TxIn {
164 previous_output: OutPoint {
165 txid: root_txid,
166 vout: 0,
167 },
168 script_sig: ScriptBuf::new(),
169 sequence: initial_sequence(),
170 witness: Default::default(),
171 });
172
173 let secp = Secp256k1::new();
175 let signing_pubkey = parse_public_key(&signing_pubkey_bytes)?;
176 let refund_address = Address::p2tr(
177 &secp,
178 signing_pubkey.x_only_public_key().0,
179 None,
180 self.config.spark_config.network.to_bitcoin_network(),
181 );
182 let refund_script = refund_address.script_pubkey();
183
184 refund_tx.output.push(TxOut {
185 script_pubkey: refund_script,
186 value: amount,
187 });
188
189 let refund_nonce_commitment = self.signer.generate_frost_signing_commitments()?;
191
192 let refund_tx_sighash = sighash_from_tx(&refund_tx, 0, &root_tx.output[0])?;
194
195 let root_tx_bytes = serialize_bitcoin_transaction(&root_tx)?;
197 let refund_tx_bytes = serialize_bitcoin_transaction(&refund_tx)?;
198
199 let request = StartTreeCreationRequest {
201 identity_public_key: self.get_spark_address()?.serialize().to_vec(),
202 on_chain_utxo: Some(Utxo {
203 vout,
204 raw_tx: deposit_tx_bytes,
205 network: self.config.spark_config.network.marshal_proto(),
206 }),
207 root_tx_signing_job: Some(SigningJob {
208 raw_tx: root_tx_bytes.clone(),
209 signing_public_key: signing_pubkey_bytes.clone(),
210 signing_nonce_commitment: Some(marshal_frost_commitments(&root_nonce_commitment)?),
211 }),
212 refund_tx_signing_job: Some(SigningJob {
213 raw_tx: refund_tx_bytes.clone(),
214 signing_public_key: signing_pubkey_bytes.clone(),
215 signing_nonce_commitment: Some(marshal_frost_commitments(
216 &refund_nonce_commitment,
217 )?),
218 }),
219 };
220
221 let tree_creation_response = self
222 .config
223 .spark_config
224 .call_with_retry(
225 request,
226 |mut client, req| Box::pin(async move { client.start_tree_creation(req).await }),
227 None,
228 )
229 .await?;
230
231 let node_id = tree_creation_response
232 .root_node_signature_shares
233 .clone()
234 .ok_or(SparkSdkError::from(
235 CryptoError::MissingRootNodeSignatureShares,
236 ))?
237 .node_id
238 .clone();
239
240 let sigantures = self.signer.sign_root_creation(
241 signing_pubkey_bytes.clone(),
242 verifying_pubkey_bytes.clone(),
243 root_tx_bytes.clone(),
244 refund_tx_bytes.clone(),
245 root_tx_sighash.to_vec(),
246 refund_tx_sighash.to_vec(),
247 root_nonce_commitment,
248 refund_nonce_commitment,
249 tree_creation_response,
250 )?;
251
252 let request = FinalizeNodeSignaturesRequest {
253 intent: SignatureIntent::Creation as i32,
254 node_signatures: vec![NodeSignatures {
255 node_id,
256 node_tx_signature: sigantures[0].clone(),
257 refund_tx_signature: sigantures[1].clone(),
258 }],
259 };
260
261 let finalize_node_signatures_response = self
262 .config
263 .spark_config
264 .call_with_retry(
265 request,
266 |mut client, req| {
267 Box::pin(async move { client.finalize_node_signatures(req).await })
268 },
269 None,
270 )
271 .await?;
272
273 let finalized_root_node = finalize_node_signatures_response.nodes[0].clone();
274
275 self.leaf_manager
276 .insert_leaves(vec![SparkLeaf::Bitcoin(finalized_root_node.clone())], false)?;
277
278 let response = CreateTreeSdkResponse {
279 nodes: vec![finalized_root_node],
280 };
281
282 Ok(response)
283 }
284}