spark_rust/wallet/handlers/
create_tree.rs1use std::sync::Arc;
2
3use bitcoin::{
4 absolute::LockTime, key::Secp256k1, transaction::Version, Address, OutPoint, ScriptBuf,
5 Sequence, 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 constants::spark::INITIAL_TIME_LOCK,
18 error::{CryptoError, NetworkError, SparkSdkError, TransactionError},
19 signer::{default_signer::marshal_frost_commitments, traits::SparkSigner},
20 wallet::{
21 internal_handlers::traits::create_tree::CreateTreeInternalHandlers,
22 leaf_manager::SparkLeaf,
23 utils::bitcoin::{
24 bitcoin_tx_from_bytes, parse_public_key, serialize_bitcoin_transaction, sighash_from_tx,
25 },
26 },
27 SparkSdk,
28};
29
30pub struct CreateTreeSdkResponse {
31 pub nodes: Vec<TreeNode>,
32}
33
34impl<S: SparkSigner + Send + Sync + Clone + 'static> SparkSdk<S> {
35 #[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
84 async fn create_tree(
85 &self,
86 deposit_transaction: Option<Transaction>,
87 parent_node: Option<Arc<RwLock<TreeNode>>>,
88 vout: u32,
89 split_level: u32,
90 parent_signing_public_key: Vec<u8>,
91 ) -> Result<CreateTreeSdkResponse, SparkSdkError> {
92 let time_start = std::time::Instant::now();
94 let deposit_address_response = self
95 .generate_deposit_address_for_tree(
96 deposit_transaction.clone(),
97 parent_node.clone(),
98 vout,
99 parent_signing_public_key.clone(),
100 split_level,
101 )
102 .await?;
103 let duration = time_start.elapsed();
104 #[cfg(feature = "telemetry")]
105 tracing::debug!(duration = ?duration, "generate_deposit_address_for_tree");
106
107 let time_start = std::time::Instant::now();
109 let finalize_tree_response = self
110 .finalize_tree_creation(
111 deposit_transaction,
112 parent_node,
113 vout,
114 deposit_address_response.tree,
115 )
116 .await?;
117 let duration = time_start.elapsed();
118 #[cfg(feature = "telemetry")]
119 tracing::debug!(duration = ?duration, "finalize_tree_creation");
120
121 let finalize_tree_response = finalize_tree_response.finalize_tree_response;
122 let nodes = finalize_tree_response.nodes;
123
124 Ok(CreateTreeSdkResponse { nodes })
125 }
126
127 pub(crate) async fn create_tree_root(
128 &self,
129 signing_pubkey_bytes: Vec<u8>,
130 verifying_pubkey_bytes: Vec<u8>,
131 deposit_tx_bytes: Vec<u8>,
132 vout: u32,
133 ) -> Result<CreateTreeSdkResponse, SparkSdkError> {
134 let deposit_tx = bitcoin_tx_from_bytes(&deposit_tx_bytes)?;
135
136 let mut root_tx = Transaction {
137 version: Version::TWO,
138 lock_time: LockTime::ZERO,
139 input: vec![],
140 output: vec![],
141 };
142
143 let output = deposit_tx
144 .output
145 .get(vout as usize)
146 .ok_or(SparkSdkError::from(TransactionError::InvalidDepositTx(
147 format!("Invalid deposit transaction: {}", vout),
148 )))?;
149
150 let script = output.script_pubkey.clone();
151 let amount = output.value;
152
153 let txid = deposit_tx.compute_txid();
154 root_tx.input.push(TxIn {
155 previous_output: OutPoint { txid, vout },
156 script_sig: ScriptBuf::new(),
157 sequence: Default::default(),
158 witness: Default::default(),
159 });
160
161 root_tx.output.push(TxOut {
162 script_pubkey: script,
163 value: amount,
164 });
165
166 let root_nonce_commitment = self.signer.new_frost_signing_noncepair()?;
168
169 let root_tx_sighash = sighash_from_tx(&root_tx, 0, output)?;
171
172 let mut refund_tx = Transaction {
174 version: Version::TWO,
175 lock_time: LockTime::ZERO,
176 input: vec![],
177 output: vec![],
178 };
179
180 let sequence = (1 << 30) | INITIAL_TIME_LOCK;
183
184 let root_txid = root_tx.compute_txid();
185 refund_tx.input.push(TxIn {
186 previous_output: OutPoint {
187 txid: root_txid,
188 vout: 0,
189 },
190 script_sig: ScriptBuf::new(),
191 sequence: Sequence(sequence),
192 witness: Default::default(),
193 });
194
195 let secp = Secp256k1::new();
197 let signing_pubkey = parse_public_key(&signing_pubkey_bytes)?;
198 let refund_address = Address::p2tr(
199 &secp,
200 signing_pubkey.x_only_public_key().0,
201 None,
202 self.config.spark_config.network.to_bitcoin_network(),
203 );
204 let refund_script = refund_address.script_pubkey();
205
206 refund_tx.output.push(TxOut {
207 script_pubkey: refund_script,
208 value: amount,
209 });
210
211 let refund_nonce_commitment = self.signer.new_frost_signing_noncepair()?;
213
214 let refund_tx_sighash = sighash_from_tx(&refund_tx, 0, &root_tx.output[0])?;
216
217 let mut spark_client = self.config.spark_config.get_spark_connection(None).await?;
219
220 let root_tx_bytes = serialize_bitcoin_transaction(&root_tx)?;
221 let refund_tx_bytes = serialize_bitcoin_transaction(&refund_tx)?;
222
223 let mut request = tonic::Request::new(StartTreeCreationRequest {
225 identity_public_key: self.get_spark_address()?.serialize().to_vec(),
226 on_chain_utxo: Some(Utxo {
227 vout,
228 raw_tx: deposit_tx_bytes,
229 network: self.config.spark_config.network.marshal_proto(),
230 }),
231 root_tx_signing_job: Some(SigningJob {
232 raw_tx: root_tx_bytes.clone(),
233 signing_public_key: signing_pubkey_bytes.clone(),
234 signing_nonce_commitment: Some(marshal_frost_commitments(&root_nonce_commitment)?),
235 }),
236 refund_tx_signing_job: Some(SigningJob {
237 raw_tx: refund_tx_bytes.clone(),
238 signing_public_key: signing_pubkey_bytes.clone(),
239 signing_nonce_commitment: Some(marshal_frost_commitments(
240 &refund_nonce_commitment,
241 )?),
242 }),
243 });
244 self.add_authorization_header_to_request(&mut request, None);
245
246 let response = spark_client
247 .start_tree_creation(request)
248 .await
249 .map_err(|status| SparkSdkError::from(NetworkError::Status(status)))?;
250
251 let tree_creation_response = response.into_inner();
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 mut request = tonic::Request::new(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 self.add_authorization_header_to_request(&mut request, None);
282 let finalized_root = spark_client
283 .finalize_node_signatures(request)
284 .await
285 .map_err(|status| SparkSdkError::from(NetworkError::Status(status)))?;
286 let finalized_root = finalized_root.into_inner();
287
288 let finalized_root_node = finalized_root.nodes[0].clone();
289
290 self.leaf_manager
291 .insert_leaves(vec![SparkLeaf::Bitcoin(finalized_root_node.clone())], false)?;
292
293 let response = CreateTreeSdkResponse {
294 nodes: vec![finalized_root_node],
295 };
296
297 Ok(response)
298 }
299}