spark_sdk/wallet/handlers/
lightning.rs

1use std::collections::HashMap;
2
3use lightning_invoice::Bolt11Invoice;
4use rand::rngs::OsRng;
5use spark_cryptography::secret_sharing::shamir_new::VSS;
6use spark_protos::spark::StorePreimageShareRequest;
7
8use crate::{
9    error::SparkSdkError,
10    signer::traits::{secp256k1::KeygenMethod, SparkSigner},
11    wallet::{
12        internal_handlers::traits::{
13            leaves::LeavesInternalHandlers,
14            lightning::LightningInternalHandlers,
15            ssp::SspInternalHandlers,
16            transfer::{LeafKeyTweak, TransferInternalHandlers},
17        },
18        leaf_manager::LeafNodeStatus,
19    },
20    SparkSdk,
21};
22
23impl<S: SparkSigner + Send + Sync + Clone + 'static> SparkSdk<S> {
24    pub async fn pay_lightning_invoice(
25        &self,
26        invoice: String,
27        amount_sats: u64,
28    ) -> Result<String, SparkSdkError> {
29        // parse the invoice
30        let bolt11_invoice: Bolt11Invoice = invoice.parse().unwrap();
31
32        // get the invoice amount in sats
33        let invoice_amount_msats =
34            bolt11_invoice
35                .amount_milli_satoshis()
36                .ok_or(SparkSdkError::InvalidInput(
37                    "Invoice missing amount".to_string(),
38                ))?;
39
40        let invoice_amount_sats = invoice_amount_msats / 1000;
41
42        // return error if the user requested to pay a 0-amount invoice
43        if amount_sats == 0 || invoice_amount_sats == 0 {
44            return Err(SparkSdkError::InvalidInput(
45                "Amount must be greater than 0".to_string(),
46            ));
47        }
48
49        // get the payment hash from the invoice
50        let payment_hash = bolt11_invoice.payment_hash().to_string();
51
52        // prepare leaves
53        let leaf_selection_response = self
54            .prepare_leaves_for_amount(amount_sats, None, LeafNodeStatus::InTransfer)
55            .await?;
56        let unlocking_id = leaf_selection_response.unlocking_id.clone().unwrap();
57        let leaves = leaf_selection_response.leaves;
58
59        // prepare leaf tweaks
60        let identity_pubkey = self.get_identity_public_key();
61        let network = self.get_network();
62        let mut leaf_tweaks = Vec::new();
63        for leaf in &leaves {
64            let tree_node = leaf.marshal_to_tree_node(identity_pubkey, &network);
65
66            let old_signing_private_key = self
67                .signer
68                .sensitive_expose_secret_key_from_pubkey(&leaf.signing_public_key, false)?;
69            let new_signing_public_key = self.signer.new_secp256k1_keypair(KeygenMethod::Random)?;
70
71            let leaf_tweak = LeafKeyTweak {
72                leaf: tree_node,
73                old_signing_private_key,
74                new_signing_public_key,
75            };
76            leaf_tweaks.push(leaf_tweak);
77        }
78
79        // swap the leaves for the preimage
80        let payment_hash_bytes = hex::decode(&payment_hash).unwrap();
81        let swap_response = self
82            .swap_nodes_for_preimage(
83                leaf_tweaks.clone(),
84                identity_pubkey.to_vec(),
85                payment_hash_bytes,
86                Some(invoice),
87                0, // TODO: this must use the estimated fee, right?
88                false,
89            )
90            .await?;
91
92        println!("Swap response: {:?}", swap_response);
93
94        // start the transfer
95        let transfer_data = swap_response.transfer.unwrap();
96        let transfer = self
97            .send_transfer_tweak_key(transfer_data, &leaf_tweaks, &HashMap::new())
98            .await?;
99
100        println!("Transfer: {:?}", transfer);
101
102        // request Lightning send with the SSP
103        let lightning_send_response = self
104            .request_lightning_send_with_ssp(transfer.id, payment_hash)
105            .await?;
106
107        println!("Lightning send response: {:?}", lightning_send_response);
108
109        // delete the leaves after the transfer
110        let leaf_ids_to_remove: Vec<String> = leaves.iter().map(|l| l.id.clone()).collect();
111        self.leaf_manager
112            .delete_leaves_after_transfer(&unlocking_id, &leaf_ids_to_remove)
113            .await?;
114
115        println!("Leaves deleted");
116
117        Ok(lightning_send_response)
118    }
119
120    pub async fn create_lightning_invoice(
121        &self,
122        amount_sats: i64,
123        memo: Option<String>,
124        expiry_seconds: Option<i32>,
125    ) -> Result<String, SparkSdkError> {
126        // generate a preimage and hash it
127        // don't create the key in the signer since this is very short-lived.
128        let preimage_sk = bitcoin::secp256k1::SecretKey::new(&mut OsRng);
129        let preimage_bytes = preimage_sk.secret_bytes();
130        let payment_hash = sha256::digest(&preimage_bytes);
131        let payment_hash_bytes = hex::decode(&payment_hash)?;
132
133        // create the invoice
134        let (invoice, fees) = self
135            .create_invoice_with_ssp(
136                amount_sats,
137                payment_hash,
138                expiry_seconds,
139                memo,
140                self.config.spark_config.network.to_bitcoin_network(),
141            )
142            .await?;
143
144        // split the preimage into shares
145        let signing_operators = self.config.spark_config.spark_operators.clone();
146
147        // distribute the preimage shares to the operators
148        // TODO: parallelize this
149        let t = self.config.spark_config.threshold as usize;
150        let n = signing_operators.len();
151        let vss = VSS::new(t, n).unwrap();
152        let shares = vss
153            .split_from_secret_key(&preimage_sk)
154            .map_err(|e| SparkSdkError::CryptoError(format!("Failed to split preimage: {}", e)))?;
155
156        for operator in &signing_operators {
157            let operator_id = operator.id;
158            let share = &shares[operator_id as usize];
159
160            let mut operator_client = self
161                .config
162                .spark_config
163                .get_spark_connection(Some(operator_id))
164                .await?;
165
166            let mut request = tonic::Request::new(StorePreimageShareRequest {
167                payment_hash: payment_hash_bytes.clone(),
168                preimage_share: Some(share.marshal_proto()),
169                threshold: self.config.spark_config.threshold as u32,
170                invoice_string: invoice.clone(),
171                user_identity_public_key: self.get_identity_public_key().to_vec(),
172            });
173            self.add_authorization_header_to_request(&mut request, Some(operator_id));
174
175            operator_client.store_preimage_share(request).await?;
176        }
177
178        Ok(invoice)
179    }
180}