spark_rust/wallet/internal_handlers/implementations/
lightning.rs1use 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 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 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 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 let secp = bitcoin::secp256k1::Secp256k1::new();
131 let private_as_pubkey = &leaf.old_signing_private_key.public_key(&secp);
132 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}