spark_rust/wallet/handlers/
lightning.rs1use bitcoin::secp256k1::PublicKey;
2use lightning_invoice::Bolt11Invoice;
3use rand::rngs::OsRng;
4use spark_cryptography::secret_sharing::shamir_new::VSS;
5use spark_protos::spark::StorePreimageShareRequest;
6use std::{collections::HashMap, str::FromStr};
7
8use crate::{
9 constants::spark::LIGHTSPARK_SSP_IDENTITY_PUBLIC_KEY,
10 error::{CryptoError, IoError, NetworkError, SparkSdkError, ValidationError},
11 signer::traits::{derivation_path::SparkKeyType, SparkSigner},
12 wallet::internal_handlers::traits::{
13 leaves::LeavesInternalHandlers,
14 lightning::LightningInternalHandlers,
15 ssp::SspInternalHandlers,
16 transfer::{LeafKeyTweak, TransferInternalHandlers},
17 },
18 SparkSdk,
19};
20
21impl<S: SparkSigner + Send + Sync + Clone + 'static> SparkSdk<S> {
22 #[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
23 pub async fn pay_lightning_invoice(&self, invoice: &String) -> Result<String, SparkSdkError> {
24 let invoice = Bolt11Invoice::from_str(invoice)
25 .map_err(|err| SparkSdkError::from(IoError::Bolt11InvoiceDecoding(err.to_string())))?;
26
27 let amount_sats = get_invoice_amount_sats(&invoice)?;
29 if amount_sats == 0 {
30 return Err(SparkSdkError::from(ValidationError::InvalidInput {
31 field: "Amount must be greater than 0".to_string(),
32 }));
33 }
34
35 let payment_hash = invoice.payment_hash();
37
38 let leaf_selection_response = self.prepare_leaves_for_amount(amount_sats).await?;
40
41 let unlocking_id = leaf_selection_response.unlocking_id.clone().unwrap();
42 let leaves = leaf_selection_response.leaves;
43
44 let network = self.get_network();
48 let mut leaf_tweaks = Vec::with_capacity(leaves.len());
49 for leaf in &leaves {
50 let tree_node = leaf.get_tree_node()?;
51 let old_signing_private_key = self.signer.expose_leaf_secret_key_for_transfer(
52 leaf.get_id().clone(),
53 SparkKeyType::BaseSigning,
54 0,
55 network.to_bitcoin_network(),
56 )?;
57
58 let new_signing_public_key = self.signer.new_ephemeral_keypair()?;
59
60 let leaf_tweak = LeafKeyTweak {
61 leaf: tree_node,
62 old_signing_private_key,
63 new_signing_public_key,
64 };
65 leaf_tweaks.push(leaf_tweak);
66 }
67
68 let ssp_public_key = PublicKey::from_str(LIGHTSPARK_SSP_IDENTITY_PUBLIC_KEY)
70 .map_err(|err| SparkSdkError::from(CryptoError::Secp256k1(err)))?;
71
72 let swap_response = self
73 .swap_nodes_for_preimage(
74 leaf_tweaks.clone(),
75 &ssp_public_key,
76 payment_hash,
77 &invoice,
78 amount_sats,
79 0, false,
81 )
82 .await?;
83
84 #[cfg(feature = "telemetry")]
85 tracing::trace!(swap_response = ?swap_response, "swap_nodes_for_preimage");
86
87 if swap_response.transfer.is_none() {
89 return Err(SparkSdkError::from(ValidationError::InvalidInput {
90 field: "Swap response did not contain a transfer".to_string(),
91 }));
92 }
93
94 let transfer = swap_response.transfer.unwrap();
95
96 let transfer = self
98 .send_transfer_tweak_key(transfer, &leaf_tweaks, &HashMap::new())
99 .await?;
100 #[cfg(feature = "telemetry")]
101 tracing::trace!(transfer = ?transfer, "send_transfer_tweak_key");
102
103 let lightning_send_response = self
105 .request_lightning_send_with_ssp(invoice.to_string(), payment_hash.to_string())
106 .await?;
107 #[cfg(feature = "telemetry")]
108 tracing::trace!(lightning_send_response = ?lightning_send_response, "request_lightning_send_with_ssp");
109
110 let leaf_ids_to_remove: Vec<String> = leaves.iter().map(|l| l.get_id().clone()).collect();
112 self.leaf_manager
113 .unlock_leaves(unlocking_id.clone(), &leaf_ids_to_remove, true)?;
114 #[cfg(feature = "telemetry")]
115 tracing::trace!(unlocking_id = ?unlocking_id, leaf_ids_to_remove = ?leaf_ids_to_remove, "unlock_leaves");
116
117 Ok(lightning_send_response)
118 }
119
120 #[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
124 pub async fn create_lightning_invoice(
125 &self,
126 amount_sats: u64,
127 memo: Option<String>,
128 expiry_seconds: Option<i32>,
129 ) -> Result<Bolt11Invoice, SparkSdkError> {
130 let expiry_seconds = expiry_seconds.unwrap_or(60 * 60 * 24 * 30);
132
133 let preimage_sk = bitcoin::secp256k1::SecretKey::new(&mut OsRng);
136 let preimage_bytes = preimage_sk.secret_bytes();
137 let payment_hash = sha256::digest(&preimage_bytes);
138 let payment_hash_bytes = hex::decode(&payment_hash)
139 .map_err(|err| SparkSdkError::from(IoError::Decoding(err)))?;
140
141 let (invoice, _fees) = self
144 .create_invoice_with_ssp(
145 amount_sats,
146 payment_hash,
147 expiry_seconds,
148 memo,
149 self.config.spark_config.network.to_bitcoin_network(),
150 )
151 .await?;
152
153 let t = self.config.spark_config.threshold as usize;
156 let n = self.config.spark_config.spark_operators.len();
157 let vss = VSS::new(t, n).unwrap();
158 let shares = vss.split_from_secret_key(&preimage_sk).map_err(|e| {
159 SparkSdkError::from(CryptoError::InvalidInput {
160 field: format!("Failed to split preimage: {}", e),
161 })
162 })?;
163
164 let verification_shares = vss.reconstruct(&shares[..t]).unwrap();
166 if verification_shares.to_bytes().to_vec() != preimage_sk.secret_bytes().to_vec() {
167 return Err(SparkSdkError::from(ValidationError::InvalidInput {
168 field: "Verification failed: shares do not reconstruct to the preimage".to_string(),
169 }));
170 }
171
172 let signing_operators = self.config.spark_config.spark_operators.clone();
173 let identity_pubkey = self.get_spark_address()?;
174
175 let futures = signing_operators.iter().map(|operator| {
176 let operator_id = operator.id;
177 let share = &shares[operator_id as usize];
178 let payment_hash = payment_hash_bytes.clone();
179 let invoice_str = invoice.clone();
180 let threshold = self.config.spark_config.threshold;
181 let config = self.config.clone();
182
183 async move {
184 let mut spark_client = config
185 .spark_config
186 .get_spark_connection(Some(operator_id))
187 .await
188 .map_err(|e| tonic::Status::internal(format!("Connection error: {}", e)))?;
189
190 let mut request = tonic::Request::new(StorePreimageShareRequest {
191 payment_hash,
192 preimage_share: Some(share.marshal_proto()),
193 threshold,
194 invoice_string: invoice_str,
195 user_identity_public_key: identity_pubkey.serialize().to_vec(),
196 });
197 self.add_authorization_header_to_request(&mut request, Some(operator_id));
198 spark_client.store_preimage_share(request).await
199 }
200 });
201
202 futures::future::try_join_all(futures)
203 .await
204 .map_err(|e| SparkSdkError::from(NetworkError::Status(e)))?;
205
206 Bolt11Invoice::from_str(&invoice).map_err(|err| {
207 SparkSdkError::from(ValidationError::InvalidBolt11Invoice(err.to_string()))
208 })
209 }
210}
211
212fn get_invoice_amount_sats(invoice: &Bolt11Invoice) -> Result<u64, SparkSdkError> {
213 let invoice_amount_msats = invoice
214 .amount_milli_satoshis()
215 .ok_or(SparkSdkError::Validation(
216 ValidationError::InvalidBolt11Invoice(invoice.to_string()),
217 ))?;
218
219 Ok(invoice_amount_msats / 1000)
220}