spark_rust/wallet/internal_handlers/implementations/
lightning.rs

1use crate::error::{network::NetworkError, SparkSdkError};
2use crate::signer::traits::SparkSigner;
3use crate::wallet::internal_handlers::traits::commitments::StatechainCommitmentsInternalHandlers;
4use crate::wallet::internal_handlers::traits::lightning::LightningInternalHandlers;
5use crate::wallet::internal_handlers::traits::transfer::LeafKeyTweak;
6use crate::wallet::utils::bitcoin::{
7    bitcoin_tx_from_bytes, serialize_bitcoin_transaction, sighash_from_tx,
8};
9use crate::wallet::utils::sequence::next_sequence;
10use crate::wallet::utils::transaction::create_refund_tx;
11use crate::SparkSdk;
12use bitcoin::hashes;
13use bitcoin::hashes::Hash;
14use bitcoin::secp256k1::PublicKey;
15use lightning_invoice::Bolt11Invoice;
16use spark_protos::spark::initiate_preimage_swap_request::Reason;
17use spark_protos::spark::InitiatePreimageSwapRequest;
18use spark_protos::spark::InitiatePreimageSwapResponse;
19use spark_protos::spark::InvoiceAmount;
20use spark_protos::spark::InvoiceAmountProof;
21use spark_protos::spark::RequestedSigningCommitments;
22use spark_protos::spark::SigningCommitments;
23use spark_protos::spark::StartSendTransferRequest;
24use spark_protos::spark::UserSignedRefund;
25use tonic::async_trait;
26use tonic::Request;
27use uuid::Uuid;
28
29#[cfg(test)]
30use spark_protos::spark::ProvidePreimageRequest;
31#[cfg(test)]
32use spark_protos::spark::Transfer;
33
34#[async_trait]
35impl<S: SparkSigner + Send + Sync + Clone + 'static> LightningInternalHandlers<S> for SparkSdk<S> {
36    #[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
37    async fn swap_nodes_for_preimage(
38        &self,
39        leaves: Vec<LeafKeyTweak>,
40        receiver_pubkey: &PublicKey,
41        payment_hash: &hashes::sha256::Hash,
42        invoice: &Bolt11Invoice,
43        invoice_amount_sats: u64,
44        fee_sats: u64,
45        is_inbound_payment: bool,
46    ) -> Result<InitiatePreimageSwapResponse, SparkSdkError> {
47        let mut spark_client = self.config.spark_config.get_spark_connection(None).await?;
48
49        // get signing commitments
50        let node_ids: Vec<String> = leaves.iter().map(|l| l.leaf.id.clone()).collect();
51        let spark_commitments = self.get_spark_signing_commitments(node_ids).await?;
52
53        // get user signed refunds
54        let user_signed_refunds = self
55            .sign_lightning_refunds(leaves, spark_commitments, receiver_pubkey)
56            .await?;
57
58        let transfer_id = Uuid::now_v7().to_string();
59        let reason = if is_inbound_payment {
60            Reason::Receive
61        } else {
62            Reason::Send
63        };
64
65        let mut initiate_preimage_swap_request = Request::new(InitiatePreimageSwapRequest {
66            payment_hash: payment_hash.to_byte_array().to_vec(),
67            user_signed_refunds,
68            reason: reason as i32,
69            invoice_amount: Some(InvoiceAmount {
70                invoice_amount_proof: Some(InvoiceAmountProof {
71                    bolt11_invoice: invoice.to_string(),
72                }),
73                value_sats: invoice_amount_sats,
74            }),
75            transfer: Some(StartSendTransferRequest {
76                transfer_id: transfer_id.clone(),
77                owner_identity_public_key: self.get_spark_address()?.serialize().to_vec(),
78                receiver_identity_public_key: receiver_pubkey.serialize().to_vec(),
79                expiry_time: Default::default(),
80                leaves_to_send: Default::default(),
81                key_tweak_proofs: Default::default(),
82            }),
83            receiver_identity_public_key: receiver_pubkey.serialize().to_vec(),
84            fee_sats,
85        });
86
87        self.add_authorization_header_to_request(&mut initiate_preimage_swap_request, None);
88        let response = spark_client
89            .initiate_preimage_swap(initiate_preimage_swap_request)
90            .await
91            .map_err(|status| SparkSdkError::from(NetworkError::Status(status)))?;
92
93        Ok(response.into_inner())
94    }
95
96    async fn sign_lightning_refunds(
97        &self,
98        leaves: Vec<LeafKeyTweak>,
99        spark_commitments: Vec<RequestedSigningCommitments>,
100        receiver_pubkey: &PublicKey,
101    ) -> Result<Vec<UserSignedRefund>, SparkSdkError> {
102        // sign refunds. TODO: In JS SDK, this is the `sign_refunds` function
103        let mut user_signed_refunds = Vec::with_capacity(leaves.len());
104        let bitcoin_network = self.config.spark_config.network.to_bitcoin_network();
105        for (i, leaf) in leaves.iter().enumerate() {
106            let node_tx = bitcoin_tx_from_bytes(&leaf.leaf.node_tx)?;
107            let node_outpoint = bitcoin::OutPoint {
108                txid: node_tx.compute_txid(),
109                vout: 0,
110            };
111
112            let curr_refund_tx = bitcoin_tx_from_bytes(&leaf.leaf.refund_tx)?;
113            let next_sequence = next_sequence(curr_refund_tx.input[0].sequence.0);
114            let amount_sats = curr_refund_tx.output[0].value;
115
116            let new_refund_tx = create_refund_tx(
117                next_sequence,
118                node_outpoint,
119                amount_sats,
120                receiver_pubkey,
121                bitcoin_network,
122            );
123
124            let sighash = sighash_from_tx(&new_refund_tx, 0, &node_tx.output[0])?;
125
126            let self_commitment = self.signer.new_frost_signing_noncepair()?;
127            let spark_commitment = spark_commitments[i].signing_nonce_commitments.clone();
128
129            // todo: clean start
130            let secp = bitcoin::secp256k1::Secp256k1::new();
131            let private_as_pubkey = &leaf.old_signing_private_key.public_key(&secp);
132            // todo: clean end
133
134            let user_signature_share = self.signer.sign_frost_new(
135                sighash.to_vec(),
136                private_as_pubkey.serialize().to_vec(),
137                leaf.leaf.verifying_public_key.clone(),
138                self_commitment,
139                spark_commitment.clone(),
140                None,
141            )?;
142
143            user_signed_refunds.push(UserSignedRefund {
144                node_id: leaf.leaf.id.clone(),
145                refund_tx: serialize_bitcoin_transaction(&new_refund_tx)?,
146                user_signature: user_signature_share,
147                user_signature_commitment: Some(marshal_frost_commitments(&self_commitment)?),
148                signing_commitments: Some(SigningCommitments {
149                    signing_commitments: spark_commitment,
150                }),
151                network: self.config.spark_config.network.marshal_proto(),
152            });
153        }
154
155        Ok(user_signed_refunds)
156    }
157
158    #[cfg(test)]
159    async fn provide_preimage(&self, preimage: Vec<u8>) -> Result<Transfer, SparkSdkError> {
160        use crate::error::{IoError, ValidationError};
161
162        let mut spark_client = self.config.spark_config.get_spark_connection(None).await?;
163        let payment_hash = sha256::digest(&preimage);
164        let payment_hash_bytes = hex::decode(&payment_hash)
165            .map_err(|err| SparkSdkError::from(IoError::Decoding(err)))?;
166
167        let request = Request::new(ProvidePreimageRequest {
168            payment_hash: payment_hash_bytes,
169            preimage,
170            identity_public_key: Default::default(),
171        });
172        let response_outer = spark_client
173            .provide_preimage(request)
174            .await
175            .map_err(|status| SparkSdkError::from(NetworkError::Status(status)))?;
176        let response = response_outer.into_inner();
177
178        if response.transfer.is_none() {
179            return Err(SparkSdkError::from(ValidationError::InvalidInput {
180                field: "Transfer not found".to_string(),
181            }));
182        }
183
184        Ok(response.transfer.unwrap())
185    }
186}
187
188use crate::common_types::types::frost::FrostSigningCommitments;
189use spark_protos::common::SigningCommitment as ProtoSigningCommitments;
190
191pub(crate) fn marshal_frost_commitments(
192    commitments: &FrostSigningCommitments,
193) -> Result<ProtoSigningCommitments, SparkSdkError> {
194    let hiding = commitments.hiding().serialize().unwrap();
195    let binding = commitments.binding().serialize().unwrap();
196
197    Ok(ProtoSigningCommitments { hiding, binding })
198}