1use super::traits::derivation_path::SparkSignerDerivationPath;
2use super::traits::ecdsa::SparkSignerEcdsa;
3use super::traits::ecies::SparkSignerEcies;
4use super::traits::frost::SparkSignerFrost;
5use super::traits::frost_signing::SparkSignerFrostSigning;
6use super::traits::secp256k1::SparkSignerSecp256k1;
7use super::traits::shamir::SparkSignerShamir;
8use super::traits::SparkSigner;
9use crate::common_types::types::frost::FrostNonce;
10use crate::common_types::types::frost::FrostNonceCommitment;
11use crate::common_types::types::frost::FrostSigningCommitments;
12use crate::common_types::types::frost::FrostSigningNonces;
13use crate::common_types::types::hex_decode;
14use crate::common_types::types::hex_encode;
15use crate::common_types::types::HashbrownMap;
16use crate::common_types::types::RwLock;
17use crate::common_types::types::Secp256k1;
18use crate::common_types::types::Secp256k1Message;
19use crate::common_types::types::SparkRange;
20use crate::common_types::types::Transaction;
21use crate::common_types::types::Uuid;
22use crate::common_types::types::{PublicKey, SecretKey};
23use crate::constants::spark::frost::FROST_USER_IDENTIFIER;
24use crate::constants::spark::frost::FROST_USER_KEY_PACKAGE_MIN_SIGNERS;
25use crate::constants::spark::frost::FROST_USER_SIGNING_ROLE;
26use crate::error::{CryptoError, SparkSdkError, ValidationError};
27use crate::wallet::internal_handlers::traits::create_tree::DepositAddressTree;
28use crate::wallet::internal_handlers::traits::transfer::LeafKeyTweak;
29use crate::wallet::internal_handlers::traits::transfer::LeafRefundSigningData;
30use crate::wallet::utils::bitcoin::bitcoin_tx_from_bytes;
31use crate::wallet::utils::bitcoin::serialize_bitcoin_transaction;
32use crate::wallet::utils::bitcoin::sighash_from_tx;
33use crate::wallet::utils::bitcoin::sighash_from_tx_new;
34use crate::wallet::utils::sequence::next_sequence;
35use crate::wallet::utils::transaction::ephemeral_anchor_output;
36use crate::SparkNetwork;
37use bitcoin::bip32::Xpriv;
38use bitcoin::key::Keypair;
39use bitcoin::secp256k1::ecdsa::Signature;
40use bitcoin::secp256k1::All;
41use bitcoin::Network;
42use spark_cryptography::derivation_path::derive_spark_key;
43use spark_cryptography::derivation_path::get_derivation_path_with_key_type;
44use spark_cryptography::derivation_path::get_secret_key_with_key_type;
45use spark_cryptography::derivation_path::SparkDerivationPath;
46use spark_cryptography::derivation_path::SparkKeyType;
47use spark_cryptography::secp::subtract_secret_keys;
48use spark_cryptography::secret_sharing::from_bytes_to_k256_scalar;
49use spark_cryptography::secret_sharing::split_secret_with_proofs;
50use spark_cryptography::secret_sharing::VerifiableSecretShare;
51use spark_cryptography::signing::aggregate_frost;
52use spark_cryptography::signing::sign_frost;
53use spark_protos::common::SigningCommitment as SparkOperatorCommitment;
54use spark_protos::common::SigningCommitment;
55use spark_protos::frost::AggregateFrostRequest;
56use spark_protos::frost::AggregateFrostResponse;
57use spark_protos::frost::FrostSigningJob;
58use spark_protos::frost::SignFrostRequest;
59use spark_protos::frost::SignFrostResponse;
60use spark_protos::spark::LeafRefundTxSigningResult;
61use spark_protos::spark::NodeSignatures;
62use spark_protos::spark::RequestedSigningCommitments;
63use spark_protos::spark::SigningKeyshare;
64use spark_protos::spark::SigningResult;
65use std::collections::HashMap;
66use std::sync::Arc;
67use tonic::async_trait;
68
69#[derive(Clone)]
77pub struct DefaultSigner {
78 master_seed: Vec<u8>,
80
81 pub nonce_commitments: Arc<RwLock<HashbrownMap<String, String>>>,
84
85 pub public_keys_to_secret_keys: Arc<RwLock<HashbrownMap<PublicKey, SecretKey>>>,
88
89 pub network: SparkNetwork,
91
92 secp: Secp256k1<All>,
93}
94
95impl SparkSignerDerivationPath for DefaultSigner {
96 fn get_deposit_signing_key(&self, network: Network) -> Result<PublicKey, SparkSdkError> {
97 let secret_key = get_secret_key_with_key_type(
98 &self.master_seed,
99 0,
100 network,
101 SparkKeyType::Deposit,
102 &self.secp,
103 )
104 .map_err(|e| SparkSdkError::from(CryptoError::SparkCryptographyError(e)))?;
105
106 let public_key = secret_key.public_key(&self.secp);
107
108 self.insert_to_keypair_map(&public_key, &secret_key)?;
109
110 Ok(public_key)
111 }
112
113 fn derive_spark_key(
114 &self,
115 leaf_id: String,
116 account: u32,
117 key_type: SparkKeyType,
118 network: Network,
119 ) -> Result<Keypair, SparkSdkError> {
120 let secret_key =
121 derive_spark_key(Some(leaf_id), account, &self.master_seed, key_type, network)
122 .map_err(|e| SparkSdkError::from(CryptoError::SparkCryptographyError(e)))?;
123
124 let keypair = Keypair::from_secret_key(&self.secp, &secret_key);
125 self.insert_to_keypair_map(&keypair.public_key(), &secret_key)?;
126
127 Ok(keypair)
128 }
129
130 fn get_identity_derivation_path(
131 account_index: u32,
132 ) -> Result<SparkDerivationPath, SparkSdkError> {
133 get_derivation_path_with_key_type(account_index, SparkKeyType::Identity)
134 .map_err(|e| SparkSdkError::from(CryptoError::SparkCryptographyError(e)))
135 }
136}
137
138impl SparkSignerSecp256k1 for DefaultSigner {
139 #[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
140 fn get_identity_public_key(
141 &self,
142 account_index: u32,
143 network: Network,
144 ) -> Result<PublicKey, SparkSdkError> {
145 let seed_bytes = self.load_master_seed()?;
146 let seed = match Xpriv::new_master(network, &seed_bytes) {
147 Ok(seed) => seed,
148 Err(_) => return Err(SparkSdkError::from(CryptoError::InvalidSeed)),
149 };
150
151 let identity_derivation_path = Self::get_identity_derivation_path(account_index)?;
152
153 let identity_key = seed
154 .derive_priv(&self.secp, &*identity_derivation_path)
155 .map_err(|_| {
156 SparkSdkError::from(CryptoError::ChildKeyDerivationError {
157 derivation_path: format!("{:?}", identity_derivation_path),
158 })
159 })?;
160
161 let identity_key = identity_key.private_key.public_key(&self.secp);
162
163 Ok(identity_key)
164 }
165
166 #[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
167 fn insert_secp256k1_keypair_from_secret_key(
168 &self,
169 secret_key: &SecretKey,
170 ) -> Result<PublicKey, SparkSdkError> {
171 let public_key = PublicKey::from_secret_key(&self.secp, secret_key);
172
173 self.insert_to_keypair_map(&public_key, secret_key)?;
174
175 Ok(public_key)
176 }
177
178 #[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
179 fn new_ephemeral_keypair(&self) -> Result<PublicKey, SparkSdkError> {
180 let secret_key = SecretKey::new(&mut SparkRange);
181 let public_key = secret_key.public_key(&self.secp);
182
183 self.insert_to_keypair_map(&public_key, &secret_key)?;
184
185 Ok(public_key)
186 }
187
188 #[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
189 fn subtract_secret_keys_given_pubkeys(
190 &self,
191 target_pubkey: &PublicKey,
192 source_pubkey: &PublicKey,
193 save_new_key: bool,
194 ) -> Result<PublicKey, SparkSdkError> {
195 if target_pubkey == source_pubkey {
196 return Err(SparkSdkError::from(ValidationError::InvalidInput {
197 field: "Target and source public keys are the same".to_string(),
198 }));
199 }
200
201 let target_secret_key = &self.get_secret_key_from_pubkey(target_pubkey)?;
202 let source_secret_key = &self.get_secret_key_from_pubkey(source_pubkey)?;
203
204 let result_secret_key = subtract_secret_keys(target_secret_key, source_secret_key)
205 .map_err(|e| SparkSdkError::from(CryptoError::Secp256k1(e)))?;
206
207 let result_public_key = PublicKey::from_secret_key(&self.secp, &result_secret_key);
208
209 if save_new_key {
210 self.insert_to_keypair_map(&result_public_key, &result_secret_key)?;
211 }
212
213 Ok(result_public_key)
214 }
215
216 #[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
217 fn sensitive_expose_secret_key_from_pubkey(
218 &self,
219 public_key: &PublicKey,
220 delete_after_exposing: bool,
221 ) -> Result<SecretKey, SparkSdkError> {
222 let secret_key = self.get_secret_key_from_pubkey(public_key)?;
223
224 if delete_after_exposing {
225 self.evict_from_keypair_map(public_key)?;
226 }
227
228 Ok(secret_key)
229 }
230}
231
232#[cfg(test)]
233mod default_signer_secp256k1_tests {
234 use super::*;
235 use crate::error::SparkSdkError;
236
237 use bip32::Language;
238
239 type WrappedSigner = Arc<DefaultSigner>;
240
241 const TEST_NETWORK: SparkNetwork = SparkNetwork::Regtest;
242
243 async fn create_default_signer() -> Result<WrappedSigner, SparkSdkError> {
245 let rng = SparkRange;
246 let mnemonic = bip32::Mnemonic::random(rng, Language::English);
247 let master_seed = mnemonic.to_seed("").as_bytes().to_vec();
248 let signer = DefaultSigner::from_master_seed(&master_seed, TEST_NETWORK).await?;
249
250 Ok(signer)
251 }
252
253 #[tokio::test]
254 async fn test_get_identity_public_key() -> Result<(), SparkSdkError> {
255 let signer = create_default_signer().await?;
256 signer
257 .get_identity_public_key(0, Network::Regtest)
258 .expect("failed to get identity pk");
259
260 Ok(())
261 }
262
263 #[tokio::test]
264 async fn test_new_secp256k1_keypair() -> Result<(), SparkSdkError> {
265 let signer = create_default_signer().await?;
266
267 let leaf_id = Uuid::new_v4().to_string();
268 let account_index = 0;
269 let key_type = SparkKeyType::BaseSigning;
270 let network = Network::Regtest;
271
272 let keypair = signer.derive_spark_key(leaf_id, account_index, key_type, network)?;
273 let pk = keypair.public_key();
274 let pubkey_bytes = pk.serialize().to_vec();
275 assert_eq!(pubkey_bytes.len(), 33);
276
277 let identity_pubkey = signer.get_identity_public_key(0, Network::Regtest)?;
278 assert_ne!(identity_pubkey, pk);
279
280 Ok(())
281 }
282
283 #[tokio::test]
284 async fn test_insert_secp256k1_keypair_from_secret_key() -> Result<(), SparkSdkError> {
285 let signer = create_default_signer().await?;
286 let secret_key = SecretKey::new(&mut SparkRange);
288 let pubkey = signer.insert_secp256k1_keypair_from_secret_key(&secret_key)?;
289
290 let retrieved_secret_key =
292 signer.sensitive_expose_secret_key_from_pubkey(&pubkey, false)?;
293 assert_eq!(retrieved_secret_key, secret_key);
294
295 Ok(())
296 }
297
298 #[tokio::test]
299 async fn test_subtract_secret_keys_given_pubkeys() -> Result<(), SparkSdkError> {
300 let account_index = 0;
301 let key_type = SparkKeyType::BaseSigning;
302 let network = Network::Regtest;
303
304 let leaf_id_1 = Uuid::new_v4().to_string();
305 let leaf_id_2 = Uuid::new_v4().to_string();
306
307 let signer = create_default_signer().await?;
308
309 let keypair1 = signer.derive_spark_key(leaf_id_1, account_index, key_type, network)?;
311 let keypair2 = signer.derive_spark_key(leaf_id_2, account_index, key_type, network)?;
312
313 let pk1 = keypair1.public_key();
314 let pk2 = keypair2.public_key();
315
316 let new_pubkey = signer.subtract_secret_keys_given_pubkeys(&pk1, &pk2, true)?;
318
319 let _ = signer.sensitive_expose_secret_key_from_pubkey(&new_pubkey, false)?;
321
322 Ok(())
323 }
324}
325
326const SPLIT_SECRET_ERROR: &str = "Failed to split secret: ";
327#[async_trait]
328impl SparkSignerShamir for DefaultSigner {
329 fn split_with_verifiable_secret_sharing(
330 &self,
331 message_bytes: Vec<u8>,
332 threshold: usize,
333 num_shares: usize,
334 ) -> Result<Vec<VerifiableSecretShare>, SparkSdkError> {
335 let message_as_scalar = from_bytes_to_k256_scalar(&message_bytes).map_err(|e| {
337 SparkSdkError::from(CryptoError::InvalidInput {
338 field: format!("{} {}", SPLIT_SECRET_ERROR, e),
339 })
340 })?;
341 let shares =
342 split_secret_with_proofs(&message_as_scalar, threshold, num_shares).map_err(|e| {
343 SparkSdkError::from(ValidationError::InvalidInput {
344 field: format!("{} {}", SPLIT_SECRET_ERROR, e),
345 })
346 })?;
347
348 Ok(shares)
349 }
350
351 fn split_from_public_key_with_verifiable_secret_sharing(
352 &self,
353 public_key: &PublicKey,
354 threshold: usize,
355 num_shares: usize,
356 ) -> Result<Vec<VerifiableSecretShare>, SparkSdkError> {
357 let secret_key = self.get_secret_key_from_pubkey(public_key)?;
358 let shares = self.split_with_verifiable_secret_sharing(
359 secret_key.secret_bytes().to_vec(),
360 threshold,
361 num_shares,
362 )?;
363 Ok(shares)
364 }
365}
366
367#[cfg(test)]
368mod default_signer_shamir_tests {
369 use super::*;
370 use crate::error::SparkSdkError;
371 use bip32::Language;
372 use rand::rngs::OsRng;
373 use std::sync::Arc;
374
375 type WrappedSigner = Arc<DefaultSigner>;
376
377 const TEST_NETWORK: SparkNetwork = SparkNetwork::Regtest;
378
379 async fn create_shamir_test_signer() -> Result<WrappedSigner, SparkSdkError> {
381 let rng = OsRng;
382 let mnemonic = bip32::Mnemonic::random(rng, Language::English);
383 let master_seed = mnemonic.to_seed("").as_bytes().to_vec();
384 let signer = DefaultSigner::from_master_seed(&master_seed, TEST_NETWORK).await?;
385 Ok(signer)
386 }
387
388 #[tokio::test]
389 #[ignore]
390 async fn test_split_with_verifiable_secret_sharing() -> Result<(), SparkSdkError> {
391 let signer = create_shamir_test_signer().await?;
392 let message = b"hello world".to_vec();
393
394 let threshold = 2;
396 let num_shares = 3;
397
398 let shares = signer.split_with_verifiable_secret_sharing(message, threshold, num_shares)?;
400
401 assert_eq!(shares.len(), num_shares);
403
404 Ok(())
405 }
406}
407
408impl SparkSignerEcdsa for DefaultSigner {
409 fn sign_message_ecdsa_with_identity_key<T: AsRef<[u8]>>(
410 &self,
411 message: T,
412 apply_hashing: bool,
413 network: Network,
414 ) -> Result<Signature, SparkSdkError> {
415 let payload_hash = if apply_hashing {
418 sha256::digest(message.as_ref())
419 } else {
420 hex::encode(message.as_ref())
421 };
422
423 let hash_bytes = hex::decode(&payload_hash).unwrap();
424 let message = Secp256k1Message::from_digest_slice(&hash_bytes)?;
425
426 let identity_secret_key = self.get_identity_secret_key(0, network)?;
428
429 Ok(self.secp.sign_ecdsa(&message, &identity_secret_key))
430 }
431
432 fn sign_message_ecdsa_with_key<T: AsRef<[u8]>>(
433 &self,
434 message: T,
435 public_key_for_signing_key: &PublicKey,
436 apply_hashing: bool,
437 ) -> Result<Signature, SparkSdkError> {
438 let payload_hash = if apply_hashing {
439 sha256::digest(message.as_ref())
440 } else {
441 hex::encode(message.as_ref())
442 };
443
444 let secret_key = self.get_secret_key_from_pubkey(public_key_for_signing_key)?;
445
446 let message = Secp256k1Message::from_digest_slice(&hex::decode(&payload_hash).unwrap())?;
447
448 Ok(self.secp.sign_ecdsa(&message, &secret_key))
449 }
450}
451
452#[cfg(test)]
453mod default_signer_ecdsa_tests {
454 use super::*;
455 use crate::common_types::types::Digest;
456 use crate::common_types::types::Secp256k1Message;
457 use crate::common_types::types::Sha256;
458 use bip32::Language;
459
460 type WrappedSigner = Arc<DefaultSigner>;
461
462 const TEST_NETWORK: SparkNetwork = SparkNetwork::Regtest;
463
464 async fn create_ecdsa_test_signer() -> Result<WrappedSigner, SparkSdkError> {
466 let rng = SparkRange;
467 let mnemonic = bip32::Mnemonic::random(rng, Language::English);
468 let master_seed = mnemonic.to_seed("").as_bytes().to_vec();
469 let signer = DefaultSigner::from_master_seed(&master_seed, TEST_NETWORK).await?;
470 Ok(signer)
471 }
472
473 #[tokio::test]
474 async fn test_sign_message_ecdsa_with_identity_key() -> Result<(), SparkSdkError> {
475 let signer = create_ecdsa_test_signer().await?;
476
477 let message = b"this is a test message";
479 let signature =
480 signer.sign_message_ecdsa_with_identity_key(message, true, Network::Regtest)?;
481
482 let identity_pk = signer.get_identity_public_key(0, Network::Regtest)?;
484 let msg_hash = Sha256::digest(message);
485 let message_for_verify = Secp256k1Message::from_digest_slice(&msg_hash).map_err(|e| {
486 SparkSdkError::from(CryptoError::InvalidInput {
487 field: format!("Failed to parse message: {e}"),
488 })
489 })?;
490
491 let ctx = Secp256k1::verification_only();
493 ctx.verify_ecdsa(&message_for_verify, &signature, &identity_pk)
494 .map_err(|_e| {
495 SparkSdkError::from(CryptoError::InvalidInput {
496 field: "Signature verification failed".to_string(),
497 })
498 })?;
499
500 Ok(())
501 }
502
503 #[tokio::test]
504 async fn test_sign_message_ecdsa_with_key() -> Result<(), SparkSdkError> {
505 use rand::thread_rng;
506
507 let secp = Secp256k1::new();
509 let mut rng = thread_rng();
510 let keypair = bitcoin::secp256k1::Keypair::new(&secp, &mut rng);
511
512 let signer = create_ecdsa_test_signer().await?;
514
515 signer.insert_to_keypair_map(&keypair.public_key(), &keypair.secret_key())?;
517
518 let message = b"this is a test message";
519 let _ = signer.sign_message_ecdsa_with_key(message, &keypair.public_key(), true)?;
520
521 Ok(())
522 }
523}
524
525impl SparkSignerEcies for DefaultSigner {
526 fn encrypt_secret_key_with_ecies(
527 &self,
528 receiver_public_key: &PublicKey,
529 message: &PublicKey,
530 ) -> Result<Vec<u8>, SparkSdkError> {
531 let secret_key = self.get_secret_key_from_pubkey(message)?;
532
533 let message = spark_cryptography::ecies::encrypt_ecies(
534 receiver_public_key,
535 &secret_key.secret_bytes(),
536 )?;
537
538 Ok(message)
539 }
540
541 fn decrypt_secret_key_with_ecies<T>(
542 &self,
543 ciphertext: T,
544 network: Network,
545 ) -> Result<SecretKey, SparkSdkError>
546 where
547 T: AsRef<[u8]>,
548 {
549 let secret_key = self.get_identity_secret_key(0, network)?;
550 let message = spark_cryptography::ecies::decrypt_ecies(secret_key, ciphertext.as_ref())?;
551
552 let secret_key = SecretKey::from_slice(&message).unwrap();
553 Ok(secret_key)
554 }
555}
556
557#[cfg(test)]
558mod default_signer_ecies_tests {
559 use super::*;
560 use crate::error::SparkSdkError;
561 use bip32::Language;
562 use rand::rngs::OsRng;
563 use std::sync::Arc;
564
565 type WrappedSigner = Arc<DefaultSigner>;
566
567 const TEST_NETWORK: SparkNetwork = SparkNetwork::Regtest;
568
569 async fn create_ecies_test_signer() -> Result<WrappedSigner, SparkSdkError> {
571 let mnemonic = bip32::Mnemonic::random(OsRng, Language::English);
572 let master_seed = mnemonic.to_seed("").as_bytes().to_vec();
573 let signer = DefaultSigner::from_master_seed(&master_seed, TEST_NETWORK).await?;
574 Ok(signer)
575 }
576
577 #[tokio::test]
578 async fn test_ecies_encrypt_decrypt_round_trip() -> Result<(), SparkSdkError> {
579 let signer = create_ecies_test_signer().await?;
580
581 let secp = Secp256k1::new();
583 let message_as_secret_key = SecretKey::new(&mut OsRng);
584 let message_as_pubkey = PublicKey::from_secret_key(&secp, &message_as_secret_key);
585 signer.insert_to_keypair_map(&message_as_pubkey, &message_as_secret_key)?;
586
587 let receiver_public_key = signer.get_identity_public_key(0, Network::Regtest)?;
588
589 let ciphertext =
591 signer.encrypt_secret_key_with_ecies(&receiver_public_key, &message_as_pubkey)?;
592 let decrypted_key = signer.decrypt_secret_key_with_ecies(&ciphertext, Network::Regtest)?;
593
594 assert_eq!(
596 decrypted_key, message_as_secret_key,
597 "Decrypted key did not match the original"
598 );
599
600 Ok(())
601 }
602}
603
604#[async_trait]
605impl SparkSignerFrost for DefaultSigner {
606 fn generate_frost_signing_commitments(&self) -> Result<FrostSigningCommitments, SparkSdkError> {
607 let mut rng = SparkRange;
608 let binding_sk = SecretKey::new(&mut rng);
609 let hiding_sk = SecretKey::new(&mut rng);
610
611 let binding = FrostNonce::deserialize(&binding_sk.secret_bytes()).unwrap();
612 let hiding = FrostNonce::deserialize(&hiding_sk.secret_bytes()).unwrap();
613
614 let nonces =
615 frost_secp256k1_tr_unofficial::round1::SigningNonces::from_nonces(hiding, binding);
616 let commitments = nonces.commitments();
617
618 let nonces_bytes = nonces.serialize().unwrap();
619 let commitment_bytes = commitments.serialize().unwrap();
620
621 self.insert_to_noncepair_map(commitment_bytes, nonces_bytes)?;
622
623 Ok(*commitments)
624 }
625
626 fn sensitive_expose_nonces_from_commitments<T>(
627 &self,
628 signing_commitments: &T,
629 ) -> Result<FrostSigningNonces, SparkSdkError>
630 where
631 T: AsRef<[u8]>,
632 {
633 let signing_commitment_hex = hex_encode(signing_commitments.as_ref());
634 let signing_nonces = self
635 .nonce_commitments
636 .read()
637 .get(&signing_commitment_hex)
638 .cloned()
639 .ok_or_else(|| {
640 SparkSdkError::from(ValidationError::InvalidInput {
641 field: "Nonce commitments not found".to_string(),
642 })
643 })?;
644
645 let signing_nonces_bytes = hex_decode(signing_nonces).unwrap();
646 let signing_nonces = FrostSigningNonces::deserialize(&signing_nonces_bytes).unwrap(); Ok(signing_nonces)
648 }
649}
650
651impl SparkSignerFrostSigning for DefaultSigner {
652 fn sign_frost(
653 &self,
654 signing_jobs: Vec<FrostSigningJob>,
655 ) -> Result<SignFrostResponse, SparkSdkError> {
656 sign_frost(&SignFrostRequest {
657 signing_jobs,
658 role: FROST_USER_SIGNING_ROLE,
659 })
660 .map_err(|e| {
661 SparkSdkError::from(CryptoError::FrostSigning {
662 job_id: e.to_string(),
663 })
664 })
665 }
666
667 fn aggregate_frost(
668 &self,
669 request: AggregateFrostRequest,
670 ) -> Result<AggregateFrostResponse, SparkSdkError> {
671 aggregate_frost(&request).map_err(|_| SparkSdkError::from(CryptoError::FrostAggregation))
672 }
673
674 fn sign_created_tree_in_bfs_order(
675 &self,
676 tx: Transaction,
677 vout: u32,
678 internal_tree_root: Arc<RwLock<DepositAddressTree>>,
679 request_tree_root: spark_protos::spark::CreationNode,
680 creation_result_tree_root: spark_protos::spark::CreationResponseNode,
681 ) -> Result<(Vec<NodeSignatures>, Vec<Vec<u8>>), SparkSdkError> {
682 #[derive(Clone)]
683 struct QueueItem {
684 parent_tx: Transaction,
685 vout: u32,
686 internal_node: Arc<RwLock<DepositAddressTree>>,
687 creation_node: spark_protos::spark::CreationNode,
688 creation_response_node: spark_protos::spark::CreationResponseNode,
689 }
690
691 let mut queue = std::collections::VecDeque::new();
692 let mut node_signatures = vec![];
693
694 queue.push_back(QueueItem {
695 parent_tx: tx,
696 vout,
697 internal_node: internal_tree_root,
698 creation_node: request_tree_root,
699 creation_response_node: creation_result_tree_root,
700 });
701
702 let mut signing_public_keys = vec![];
703
704 while let Some(current) = queue.pop_front() {
705 let node_prevout_index = current.vout as usize;
706 let node_signing_input_index = 0;
707
708 let internal_node = current.internal_node.read().clone();
710 let creation_node = current.creation_node.clone();
711 let node_signing_job = creation_node.node_tx_signing_job.clone().unwrap();
712 let serialized_node_transaction = node_signing_job.raw_tx;
713 let user_node_commitments = node_signing_job.signing_nonce_commitment.clone().unwrap();
714 let (spark_node_signature_shares, spark_node_public_shares, _, spark_node_commitments) =
715 get_signature_data_from_signing_result(
716 ¤t.creation_response_node.node_tx_signing_result,
717 )?;
718
719 let signing_job = self.prepare_frost_signing_job(
720 internal_node.signing_public_key.clone(),
721 Some(serialize_marshalized_frost_commitments(
722 &user_node_commitments,
723 )?),
724 spark_node_commitments.clone(),
725 serialized_node_transaction.clone(),
726 node_prevout_index,
727 node_signing_input_index,
728 ¤t.parent_tx.clone(),
729 vec![],
730 internal_node.verification_key.clone().unwrap(),
731 )?;
732
733 let node_signature = self.sign_frost(vec![signing_job.clone()]).unwrap();
735 let user_node_signature_share = node_signature.results[&signing_job.job_id]
736 .signature_share
737 .clone();
738
739 let aggregate_response = self
740 .aggregate_frost(spark_protos::frost::AggregateFrostRequest {
741 message: signing_job.message,
742 signature_shares: spark_node_signature_shares,
743 public_shares: spark_node_public_shares,
744 verifying_key: signing_job.verifying_key.clone(),
745 commitments: spark_node_commitments,
746 user_commitments: signing_job.user_commitments,
747 user_public_key: signing_job.verifying_key,
748 user_signature_share: user_node_signature_share,
749 adaptor_public_key: vec![],
750 })
751 .unwrap();
752
753 let mut node_signature = spark_protos::spark::NodeSignatures {
754 node_id: current.creation_response_node.node_id.clone(),
755 node_tx_signature: aggregate_response.signature,
756 ..Default::default()
757 };
758
759 if let Some(refund_signing_job) = current.creation_node.refund_tx_signing_job {
761 let refund_prevout_index = 0;
763 let refund_signing_input_index = 0_usize;
764
765 let serialized_refund_transaction = refund_signing_job.raw_tx;
767 let user_refund_commitments = refund_signing_job.signing_nonce_commitment.unwrap();
768 let (
769 spark_refund_signature_shares,
770 spark_refund_public_shares,
771 _,
772 spark_refund_commitments,
773 ) = get_signature_data_from_signing_result(
774 ¤t.creation_response_node.refund_tx_signing_result,
775 )?;
776
777 let refund_signing_job = self.prepare_frost_signing_job(
778 internal_node.signing_public_key,
779 Some(serialize_marshalized_frost_commitments(
780 &user_refund_commitments,
781 )?),
782 spark_refund_commitments.clone(),
783 serialized_refund_transaction,
784 refund_prevout_index,
785 refund_signing_input_index,
786 &bitcoin_tx_from_bytes(&serialized_node_transaction)?,
787 vec![],
788 internal_node.verification_key.clone().unwrap(),
789 )?;
790
791 let refund_signature = self.sign_frost(vec![refund_signing_job.clone()])?;
792 let user_refund_signature_share = refund_signature.results
793 [&refund_signing_job.job_id]
794 .signature_share
795 .clone();
796
797 let aggregate_response = self
798 .aggregate_frost(spark_protos::frost::AggregateFrostRequest {
799 message: refund_signing_job.message,
800 signature_shares: spark_refund_signature_shares,
801 public_shares: spark_refund_public_shares,
802 verifying_key: refund_signing_job.verifying_key.clone(),
803 commitments: spark_refund_commitments,
804 user_commitments: refund_signing_job.user_commitments,
805 user_public_key: refund_signing_job.verifying_key,
806 user_signature_share: user_refund_signature_share,
807 adaptor_public_key: vec![],
808 })
809 .unwrap();
810
811 node_signature.refund_tx_signature = aggregate_response.signature;
812 }
813 node_signatures.push(node_signature);
814
815 for (i, child) in current.creation_node.children.into_iter().enumerate() {
817 queue.push_back(QueueItem {
818 parent_tx: bitcoin_tx_from_bytes(&serialized_node_transaction)?,
819 vout: i as u32,
820 internal_node: current.internal_node.read().children[i].clone(),
821 creation_node: child,
822 creation_response_node: current.creation_response_node.children[i].clone(),
823 });
824 }
825
826 signing_public_keys.push(current.internal_node.read().signing_public_key.clone());
827 }
828
829 Ok((node_signatures, signing_public_keys))
830 }
831
832 fn sign_transfer_refunds(
833 &self,
834 leaf_data_map: &HashMap<String, LeafRefundSigningData>,
835 operator_signing_results: &Vec<LeafRefundTxSigningResult>,
836 adaptor_public_key: Vec<u8>,
837 ) -> Result<Vec<spark_protos::spark::NodeSignatures>, SparkSdkError> {
838 let mut user_signing_jobs = Vec::new();
839 let mut job_to_aggregate_request_map = HashMap::new();
840 let mut job_to_leaf_map = HashMap::new();
841
842 for operator_result in operator_signing_results {
843 let signing_input_index = 0;
844 let prevout_to_use = 0;
845
846 let leaf_data = leaf_data_map.get(&operator_result.leaf_id).ok_or_else(|| {
848 SparkSdkError::from(ValidationError::InvalidInput {
849 field: "Leaf data not found".to_string(),
850 })
851 })?;
852
853 let refund_tx_ = leaf_data.refund_tx.as_ref().unwrap();
855 let serialized_refund_tx = serialize_bitcoin_transaction(refund_tx_)?;
856
857 let (
858 spark_refund_signature_shares,
859 spark_refund_public_shares,
860 _,
861 spark_refund_commitments,
862 ) = get_signature_data_from_signing_result(&operator_result.refund_tx_signing_result)?;
863
864 let commitment_hiding = leaf_data.commitment.hiding.clone();
865 let commitment_binding = leaf_data.commitment.binding.clone();
866 let signing_commitments =
867 frost_secp256k1_tr_unofficial::round1::SigningCommitments::new(
868 FrostNonceCommitment::deserialize(&commitment_hiding).unwrap(),
869 FrostNonceCommitment::deserialize(&commitment_binding).unwrap(),
870 );
871
872 let signing_job = self.prepare_frost_signing_job(
874 leaf_data.signing_public_key.serialize(),
875 Some(signing_commitments.serialize().unwrap()),
876 spark_refund_commitments.clone(),
877 serialized_refund_tx,
878 prevout_to_use,
879 signing_input_index,
880 &leaf_data.tx,
881 adaptor_public_key.clone(),
882 operator_result.verifying_key.clone(),
883 )?;
884 let signing_job_id = signing_job.job_id.clone();
885 user_signing_jobs.push(signing_job.clone());
886
887 job_to_leaf_map.insert(signing_job_id.clone(), operator_result.leaf_id.clone());
888
889 job_to_aggregate_request_map.insert(
891 signing_job_id.clone(),
892 AggregateFrostRequest {
893 message: signing_job.message,
894 signature_shares: spark_refund_signature_shares,
895 public_shares: spark_refund_public_shares,
896 verifying_key: operator_result.verifying_key.clone(),
897 commitments: spark_refund_commitments,
898 user_commitments: signing_job.user_commitments,
899 user_public_key: leaf_data.signing_public_key.serialize().to_vec(),
900 user_signature_share: vec![],
901 adaptor_public_key: vec![],
902 },
903 );
904 }
905
906 let user_signatures = self.sign_frost(user_signing_jobs)?;
908
909 let mut node_signatures = Vec::new();
911 for (job_id, user_signature) in user_signatures.results {
912 let mut request = job_to_aggregate_request_map
913 .remove(&job_id)
914 .ok_or_else(|| {
915 SparkSdkError::from(ValidationError::InvalidInput {
916 field: "Job ID not found".to_string(),
917 })
918 })?;
919
920 request.user_signature_share = user_signature.signature_share;
921
922 let response = match self.aggregate_frost(request) {
923 Ok(response) => response,
924 Err(e) => {
925 return Err(SparkSdkError::from(CryptoError::InvalidInput {
926 field: format!("Failed to aggregate refund: {}", e).to_string(),
927 }));
928 }
929 };
930
931 node_signatures.push(spark_protos::spark::NodeSignatures {
932 node_id: job_to_leaf_map[&job_id].clone(),
933 refund_tx_signature: response.signature,
934 node_tx_signature: Vec::new(), });
936 }
937
938 Ok(node_signatures)
939 }
940
941 fn sign_for_lightning_swap(
942 &self,
943 leaves: &Vec<LeafKeyTweak>,
944 signing_commitments: &Vec<RequestedSigningCommitments>,
945 receiver_identity_pubkey: PublicKey,
946 ) -> Result<
947 (
948 SignFrostResponse,
949 Vec<Vec<u8>>,
950 Vec<ProtoSigningCommitments>,
951 ),
952 SparkSdkError,
953 > {
954 let mut signing_jobs = Vec::new();
955 let mut refund_txs = vec![];
956
957 let mut user_commitments = Vec::with_capacity(leaves.len());
958
959 for (i, leaf) in leaves.iter().enumerate() {
960 let node_tx = bitcoin_tx_from_bytes(&leaf.leaf.node_tx)?;
961
962 let node_outpoint = bitcoin::OutPoint {
963 txid: node_tx.compute_txid(),
964 vout: 0,
965 };
966
967 let current_refund_tx = bitcoin_tx_from_bytes(&leaf.leaf.refund_tx)?;
968
969 let next_sequence = next_sequence(current_refund_tx.input[0].sequence.0);
970
971 let amount_sats = node_tx.output[0].value;
972
973 let refund_tx = create_refund_tx(
974 next_sequence,
975 node_outpoint,
976 amount_sats,
977 &receiver_identity_pubkey,
978 self.network.to_bitcoin_network(),
979 &self.secp,
980 )?;
981
982 let refund_tx_buf = serialize_bitcoin_transaction(&refund_tx)?;
983 refund_txs.push(refund_tx_buf);
984
985 let sighash = sighash_from_tx(&refund_tx, 0, &node_tx.output[0])?;
986
987 let user_commitment = self.generate_frost_signing_commitments()?;
988 let user_nonce = self
989 .sensitive_expose_nonces_from_commitments(&user_commitment.serialize().unwrap())?;
990
991 let marshalized_frost_nonces = marshal_frost_nonces(&user_nonce)?;
992 let marshalized_frost_commitments = marshal_frost_commitments(&user_commitment)?;
993
994 user_commitments.push(marshalized_frost_commitments.clone());
995
996 let signing_secret_key =
997 self.sensitive_expose_secret_key_from_pubkey(&leaf.new_signing_public_key, false)?;
998 let key_package = create_user_key_package(&signing_secret_key.secret_bytes());
999
1000 signing_jobs.push(FrostSigningJob {
1001 job_id: leaf.leaf.id.clone(),
1002 message: sighash.to_vec(),
1003 key_package: Some(key_package),
1004 verifying_key: leaf.leaf.verifying_public_key.clone(),
1005 nonce: Some(marshalized_frost_nonces),
1006 user_commitments: Some(marshalized_frost_commitments),
1007 commitments: signing_commitments[i].signing_nonce_commitments.clone(),
1008 adaptor_public_key: vec![],
1009 });
1010 }
1011
1012 let signing_results = self.sign_frost(signing_jobs)?;
1014
1015 Ok((signing_results, refund_txs, user_commitments))
1016 }
1017
1018 fn sign_root_creation(
1019 &self,
1020 signing_pubkey_bytes: Vec<u8>,
1021 verifying_pubkey_bytes: Vec<u8>,
1022 _root_tx_bytes: Vec<u8>, _refund_tx_bytes: Vec<u8>, root_tx_sighash: Vec<u8>,
1025 refund_tx_sighash: Vec<u8>,
1026 root_nonce_commitment: FrostSigningCommitments,
1027 refund_nonce_commitment: FrostSigningCommitments,
1028 tree_creation_response: spark_protos::spark::StartTreeCreationResponse,
1029 ) -> Result<Vec<Vec<u8>>, SparkSdkError> {
1030 let signature_data = tree_creation_response
1032 .root_node_signature_shares
1033 .unwrap()
1034 .clone();
1035 let root_signature_data = signature_data.node_tx_signing_result.unwrap();
1036 let root_signature_shares = root_signature_data.signature_shares.clone();
1037 let root_nonce_commitments = root_signature_data.signing_nonce_commitments.clone();
1038 let root_pubkeys = root_signature_data.public_keys.clone();
1039 let refund_signature_data = signature_data.refund_tx_signing_result.unwrap();
1040 let refund_signature_shares = refund_signature_data.signature_shares.clone();
1041 let refund_nonce_commitments = refund_signature_data.signing_nonce_commitments.clone();
1042 let refund_pubkeys = refund_signature_data.public_keys.clone();
1043
1044 let signing_key = self.sensitive_expose_secret_key_from_pubkey(
1045 &PublicKey::from_slice(&signing_pubkey_bytes)?,
1046 false,
1047 )?;
1048 let key_package = create_user_key_package(&signing_key.secret_bytes());
1049
1050 let root_nonce_commitments_bytes = root_nonce_commitment.serialize().unwrap();
1051 let refund_nonce_commitments_bytes = refund_nonce_commitment.serialize().unwrap();
1052
1053 let root_nonce =
1054 self.sensitive_expose_nonces_from_commitments(&root_nonce_commitments_bytes)?;
1055 let refund_nonce =
1056 self.sensitive_expose_nonces_from_commitments(&refund_nonce_commitments_bytes)?;
1057
1058 let node_job_id = Uuid::now_v7().to_string();
1060 let node_signing_job = FrostSigningJob {
1061 job_id: node_job_id.clone(),
1062 message: root_tx_sighash.clone(),
1063 commitments: root_nonce_commitments.clone(),
1064 key_package: Some(key_package.clone()),
1065 verifying_key: verifying_pubkey_bytes.clone(),
1066 nonce: Some(marshal_frost_nonces(&root_nonce)?),
1067 user_commitments: Some(marshal_frost_commitments(&root_nonce_commitment)?),
1068 adaptor_public_key: vec![],
1069 };
1070
1071 let refund_job_id = Uuid::now_v7().to_string();
1072 let refund_signing_job = FrostSigningJob {
1073 job_id: refund_job_id.clone(),
1074 message: refund_tx_sighash.clone(),
1075 commitments: refund_nonce_commitments.clone(),
1076 key_package: Some(key_package),
1077 verifying_key: verifying_pubkey_bytes.clone(),
1078 nonce: Some(marshal_frost_nonces(&refund_nonce)?),
1079 user_commitments: Some(marshal_frost_commitments(&refund_nonce_commitment)?),
1080 adaptor_public_key: vec![],
1081 };
1082
1083 let signing_results = self.sign_frost(vec![node_signing_job, refund_signing_job])?;
1084
1085 let signature_results = signing_results.results;
1087 let user_root_signature_share = signature_results[&node_job_id].signature_share.clone();
1088 let user_refund_signature_share = signature_results[&refund_job_id].signature_share.clone();
1089
1090 let root_aggregation_request = AggregateFrostRequest {
1091 message: root_tx_sighash,
1092 signature_shares: root_signature_shares,
1093 public_shares: root_pubkeys,
1094 verifying_key: verifying_pubkey_bytes.clone(),
1095 commitments: root_nonce_commitments,
1096 user_commitments: Some(marshal_frost_commitments(&root_nonce_commitment)?),
1097 user_public_key: signing_pubkey_bytes.clone(),
1098 user_signature_share: user_root_signature_share,
1099 adaptor_public_key: vec![],
1100 };
1101 let refund_aggregation_request = AggregateFrostRequest {
1102 message: refund_tx_sighash,
1103 signature_shares: refund_signature_shares,
1104 public_shares: refund_pubkeys,
1105 verifying_key: verifying_pubkey_bytes.clone(),
1106 commitments: refund_nonce_commitments,
1107 user_commitments: Some(marshal_frost_commitments(&refund_nonce_commitment)?),
1108 user_public_key: signing_pubkey_bytes,
1109 user_signature_share: user_refund_signature_share,
1110 adaptor_public_key: vec![],
1111 };
1112
1113 let complete_root_signature = self.aggregate_frost(root_aggregation_request)?;
1114 let complete_refund_signature = self.aggregate_frost(refund_aggregation_request)?;
1115
1116 let root_sig = complete_root_signature.signature;
1117 let refund_sig = complete_refund_signature.signature;
1118
1119 Ok(vec![root_sig, refund_sig])
1120 }
1121
1122 fn sign_frost_new(
1123 &self,
1124 message: Vec<u8>,
1125 private_as_pubkey: Vec<u8>,
1126 verifying_key: Vec<u8>,
1127 self_commitment: FrostSigningCommitments,
1128 spark_commitments: HashMap<String, SigningCommitment>,
1129 adaptor_public_key: Option<Vec<u8>>,
1130 ) -> Result<Vec<u8>, SparkSdkError> {
1131 let signing_private_key = self.sensitive_expose_secret_key_from_pubkey(
1133 &PublicKey::from_slice(&private_as_pubkey)?,
1134 false,
1135 )?;
1136 let self_commtiment_bytes = self_commitment.serialize().map_err(|e| {
1137 SparkSdkError::from(CryptoError::FrostSigning {
1138 job_id: e.to_string(),
1139 })
1140 })?;
1141 let nonce = self.sensitive_expose_nonces_from_commitments(&self_commtiment_bytes)?;
1142
1143 let key_package = create_user_key_package(&signing_private_key.secret_bytes());
1145
1146 let adaptor_public_key = adaptor_public_key.unwrap_or_default();
1148
1149 let job_id = Uuid::now_v7().to_string();
1151 let signing_job = FrostSigningJob {
1152 job_id: job_id.clone(),
1153 message,
1154 key_package: Some(key_package),
1155 verifying_key,
1156 nonce: Some(marshal_frost_nonces(&nonce)?),
1157 commitments: spark_commitments,
1158 user_commitments: Some(marshal_frost_commitments(&self_commitment)?),
1159 adaptor_public_key,
1160 };
1161
1162 let sign_frost_request = SignFrostRequest {
1163 signing_jobs: vec![signing_job],
1164 role: FROST_USER_SIGNING_ROLE,
1165 };
1166
1167 let signature = match sign_frost(&sign_frost_request) {
1169 Ok(signature) => signature,
1170 Err(e) => {
1171 return Err(SparkSdkError::from(CryptoError::FrostSigning {
1172 job_id: e.to_string(),
1173 }));
1174 }
1175 };
1176
1177 let signature = signature.results.get(&job_id);
1179 if signature.is_none() {
1180 return Err(SparkSdkError::from(CryptoError::FrostSignatureNotFound {
1181 job_id,
1182 }));
1183 }
1184
1185 Ok(signature.unwrap().signature_share.clone())
1186 }
1187}
1188
1189#[async_trait]
1190impl SparkSigner for DefaultSigner {
1191 type WrappedSigner = Arc<Self>;
1192
1193 async fn from_mnemonic(
1197 mnemonic: &str,
1198 network: SparkNetwork,
1199 ) -> Result<Self::WrappedSigner, SparkSdkError> {
1200 let seed_bytes = bip39::Mnemonic::parse(mnemonic)
1202 .map_err(|e| {
1203 SparkSdkError::from(CryptoError::InvalidInput {
1204 field: e.to_string(),
1205 })
1206 })?
1207 .to_seed("");
1208 Self::from_master_seed(&seed_bytes, network).await
1211 }
1212
1213 async fn from_master_seed(
1214 master_seed: &[u8],
1215 network: SparkNetwork,
1216 ) -> Result<Self::WrappedSigner, SparkSdkError> {
1217 let nonce_commitments = HashbrownMap::new();
1218 let public_keys_to_secret_keys = HashbrownMap::new();
1219
1220 let commitments_map = Arc::new(RwLock::new(nonce_commitments));
1221 let public_keys_map = Arc::new(RwLock::new(public_keys_to_secret_keys));
1222
1223 Ok(Arc::new(Self {
1224 master_seed: master_seed.to_vec(),
1225 nonce_commitments: commitments_map,
1226 public_keys_to_secret_keys: public_keys_map,
1227 network,
1228 secp: Secp256k1::new(),
1229 }))
1230 }
1231}
1232
1233const INVALID_SECRET_KEY_ERROR: &str =
1234 "Could not find secret key in the signer space. Public key used as the index: ";
1235impl DefaultSigner {
1236 pub(crate) fn load_master_seed(&self) -> Result<Vec<u8>, SparkSdkError> {
1244 Ok(self.master_seed.clone())
1245 }
1246
1247 pub(crate) fn get_identity_secret_key(
1258 &self,
1259 account_index: u32,
1260 network: Network,
1261 ) -> Result<SecretKey, SparkSdkError> {
1262 let master_seed_bytes = self.load_master_seed()?;
1263 let master_seed = Xpriv::new_master(network, &master_seed_bytes)
1264 .map_err(|_| SparkSdkError::from(CryptoError::InvalidSeed))?;
1265
1266 let identity_derivation_path = Self::get_identity_derivation_path(account_index)?;
1267
1268 let identity_key = master_seed
1269 .derive_priv(&self.secp, &*identity_derivation_path)
1270 .map_err(|_| {
1271 SparkSdkError::from(CryptoError::ChildKeyDerivationError {
1272 derivation_path: format!("{:?}", identity_derivation_path),
1273 })
1274 })?;
1275
1276 Ok(identity_key.private_key)
1277 }
1278
1279 pub(crate) fn get_secret_key_from_pubkey(
1290 &self,
1291 public_key: &PublicKey,
1292 ) -> Result<SecretKey, SparkSdkError> {
1293 self.public_keys_to_secret_keys
1294 .read()
1295 .get(public_key)
1296 .cloned()
1297 .ok_or_else(|| {
1298 SparkSdkError::from(ValidationError::InvalidInput {
1299 field: format!("{} {}", INVALID_SECRET_KEY_ERROR, public_key).to_string(),
1300 })
1301 })
1302 }
1303
1304 pub(crate) fn insert_to_keypair_map(
1316 &self,
1317 public_key: &PublicKey,
1318 secret_key: &SecretKey,
1319 ) -> Result<(), SparkSdkError> {
1320 self.public_keys_to_secret_keys
1321 .write()
1322 .insert(*public_key, *secret_key);
1323
1324 Ok(())
1325 }
1326
1327 pub(crate) fn evict_from_keypair_map(
1338 &self,
1339 public_key: &PublicKey,
1340 ) -> Result<(), SparkSdkError> {
1341 self.public_keys_to_secret_keys.write().remove(public_key);
1342
1343 Ok(())
1344 }
1345
1346 pub(crate) fn insert_to_noncepair_map<T: AsRef<[u8]>, U: AsRef<[u8]>>(
1358 &self,
1359 nonce_commitment: T,
1360 nonce: U,
1361 ) -> Result<(), SparkSdkError> {
1362 let nonce_commitment_hex = hex_encode(nonce_commitment);
1363 let nonce_hex = hex_encode(nonce);
1364
1365 self.nonce_commitments
1366 .write()
1367 .insert(nonce_commitment_hex.clone(), nonce_hex.clone());
1368
1369 Ok(())
1370 }
1371
1372 #[allow(clippy::too_many_arguments)]
1402 fn prepare_frost_signing_job<T: AsRef<[u8]>>(
1403 &self,
1404 signing_public_key: T,
1405 user_frost_commitments: Option<Vec<u8>>,
1406 spark_frost_commitments: HashMap<String, SparkOperatorCommitment>,
1407 serialized_bitcoin_tx: Vec<u8>,
1408 prevout_to_use: usize,
1409 signing_input_index: usize,
1410 parent_tx: &Transaction,
1411 adaptor_public_key: Vec<u8>,
1412 verifying_key: Vec<u8>,
1413 ) -> Result<FrostSigningJob, SparkSdkError> {
1414 let job_id = generate_signing_job_id();
1415
1416 let signing_secret_key = self.sensitive_expose_secret_key_from_pubkey(
1418 &PublicKey::from_slice(signing_public_key.as_ref())?,
1419 false,
1420 )?;
1421
1422 let commitments = if let Some(commitments) = user_frost_commitments.as_deref() {
1424 commitments.to_vec()
1425 } else {
1426 let commitments = self.generate_frost_signing_commitments()?;
1427 commitments.serialize().unwrap().clone()
1428 };
1429 let frost_nonces = self.sensitive_expose_nonces_from_commitments(&commitments)?;
1430
1431 let marshalized_frost_nonces = marshal_frost_nonces(&frost_nonces)?;
1433 let marshalized_frost_commitments = marshal_frost_commitments(frost_nonces.commitments())?;
1434
1435 let key_package = create_user_key_package(&signing_secret_key.secret_bytes());
1436 let transaction = bitcoin_tx_from_bytes(&serialized_bitcoin_tx)?;
1437
1438 let sighash = sighash_from_tx_new(
1439 &transaction,
1440 signing_input_index,
1441 &parent_tx.output[prevout_to_use],
1442 )?;
1443 let message = sighash.to_vec();
1444
1445 Ok(FrostSigningJob {
1446 job_id,
1447 message,
1448 key_package: Some(key_package),
1449 verifying_key,
1450 nonce: Some(marshalized_frost_nonces),
1451 commitments: spark_frost_commitments,
1452 user_commitments: Some(marshalized_frost_commitments),
1453 adaptor_public_key,
1454 })
1455 }
1456}
1457
1458pub(crate) fn create_user_key_package(
1469 signing_secret_key: &[u8],
1470) -> spark_protos::frost::KeyPackage {
1471 let user_identifier = FROST_USER_IDENTIFIER;
1472 let secp = Secp256k1::new();
1473 let secret_key = SecretKey::from_slice(signing_secret_key).unwrap();
1474 let public_key = PublicKey::from_secret_key(&secp, &secret_key);
1475
1476 let mut public_shares = HashMap::new();
1477 public_shares.insert(user_identifier.to_string(), public_key.serialize().to_vec());
1478
1479 spark_protos::frost::KeyPackage {
1480 identifier: user_identifier.to_string(),
1481 secret_share: signing_secret_key.to_vec(),
1482 public_shares,
1483 public_key: public_key.serialize().to_vec(),
1484 min_signers: FROST_USER_KEY_PACKAGE_MIN_SIGNERS,
1485 }
1486}
1487
1488use spark_protos::common::SigningCommitment as ProtoSigningCommitments;
1489pub(crate) fn marshal_frost_commitments(
1500 commitments: &FrostSigningCommitments,
1501) -> Result<ProtoSigningCommitments, SparkSdkError> {
1502 let hiding = commitments.hiding().serialize().unwrap();
1503 let binding = commitments.binding().serialize().unwrap();
1504
1505 Ok(ProtoSigningCommitments { hiding, binding })
1506}
1507
1508use spark_protos::frost::SigningNonce as ProtoSigningNonce;
1509pub(crate) fn marshal_frost_nonces(
1520 nonce: &FrostSigningNonces,
1521) -> Result<ProtoSigningNonce, SparkSdkError> {
1522 let hiding = nonce.hiding().serialize();
1523 let binding = nonce.binding().serialize();
1524
1525 Ok(ProtoSigningNonce { hiding, binding })
1526}
1527
1528pub(crate) fn _unmarshal_frost_nonces(
1539 nonce: &ProtoSigningNonce,
1540) -> Result<FrostSigningNonces, SparkSdkError> {
1541 let hiding_nonce = FrostNonce::deserialize(&nonce.hiding).unwrap();
1542 let binding_nonce = FrostNonce::deserialize(&nonce.binding).unwrap();
1543
1544 Ok(FrostSigningNonces::from_nonces(hiding_nonce, binding_nonce))
1545}
1546
1547fn serialize_marshalized_frost_commitments(
1558 commitments: &ProtoSigningCommitments,
1559) -> Result<Vec<u8>, SparkSdkError> {
1560 let hiding = commitments.hiding.clone();
1561 let binding = commitments.binding.clone();
1562
1563 let prefix_hex = "00230f8ab3";
1564 let hiding_hex = hex_encode(&hiding);
1565 let binding_hex = hex_encode(&binding);
1566
1567 let commitments_hex = format!("{}{}{}", prefix_hex, hiding_hex, binding_hex);
1568 Ok(hex_decode(&commitments_hex).unwrap())
1569}
1570
1571fn generate_signing_job_id() -> String {
1576 Uuid::now_v7().to_string()
1577}
1578
1579type SignatureSharesType = HashMap<String, Vec<u8>>;
1581type PublicSharesType = HashMap<String, Vec<u8>>;
1583type SigningKeyshareType = Option<SigningKeyshare>;
1585type SigningCommitmentsType = HashMap<String, SparkOperatorCommitment>;
1587
1588fn get_signature_data_from_signing_result(
1603 signing_result: &Option<SigningResult>,
1604) -> Result<
1605 (
1606 SignatureSharesType,
1607 PublicSharesType,
1608 SigningKeyshareType,
1609 SigningCommitmentsType,
1610 ),
1611 SparkSdkError,
1612> {
1613 let signing_result = signing_result.clone().unwrap();
1614 let signature_shares = signing_result.signature_shares;
1615 let public_shares = signing_result.public_keys;
1616 let signing_keyshare = signing_result.signing_keyshare;
1617 let commitments = signing_result.signing_nonce_commitments;
1618
1619 Ok((
1620 signature_shares,
1621 public_shares,
1622 signing_keyshare,
1623 commitments,
1624 ))
1625}
1626
1627fn create_refund_tx(
1645 sequence: u32,
1646 node_outpoint: bitcoin::OutPoint,
1647 amount_sats: bitcoin::Amount,
1648 receiving_pubkey: &bitcoin::secp256k1::PublicKey,
1649 network: bitcoin::Network,
1650 secp: &Secp256k1<All>,
1651) -> Result<bitcoin::Transaction, SparkSdkError> {
1652 let mut new_refund_tx = bitcoin::Transaction {
1653 version: bitcoin::transaction::Version::TWO,
1654 lock_time: bitcoin::absolute::LockTime::ZERO,
1655 input: vec![],
1656 output: vec![],
1657 };
1658
1659 new_refund_tx.input.push(bitcoin::TxIn {
1660 previous_output: node_outpoint,
1661 script_sig: bitcoin::ScriptBuf::default(),
1662 sequence: bitcoin::Sequence(sequence),
1663 witness: bitcoin::Witness::default(),
1664 });
1665
1666 let addr = bitcoin::Address::p2tr(secp, receiving_pubkey.x_only_public_key().0, None, network);
1667 let refund_pk_script = addr.script_pubkey();
1668
1669 new_refund_tx.output.push(bitcoin::TxOut {
1670 value: amount_sats,
1671 script_pubkey: refund_pk_script,
1672 });
1673
1674 new_refund_tx.output.push(ephemeral_anchor_output());
1675
1676 Ok(new_refund_tx)
1677}
1678
1679#[cfg(test)]
1680mod frost_to_proto_conversions_test {
1681 use super::*;
1682
1683 fn test_unmarshal_frost_commitments(
1684 commitment: &ProtoSigningCommitments,
1685 ) -> Result<FrostSigningCommitments, SparkSdkError> {
1686 let hiding = commitment.hiding.clone();
1687 let binding = commitment.binding.clone();
1688
1689 let hiding_nonce = FrostNonceCommitment::deserialize(&hiding).unwrap();
1690 let binding_nonce = FrostNonceCommitment::deserialize(&binding).unwrap();
1691
1692 Ok(FrostSigningCommitments::new(hiding_nonce, binding_nonce))
1693 }
1694
1695 #[test]
1696 fn test_frost_to_proto_conversions() {
1697 let hiding_sk = SecretKey::new(&mut rand::thread_rng());
1698 let binding_sk = SecretKey::new(&mut rand::thread_rng());
1699 let hiding_sk_bytes = hiding_sk.secret_bytes().to_vec();
1700 let binding_sk_bytes = binding_sk.secret_bytes().to_vec();
1701
1702 let hiding_nonce = FrostNonce::deserialize(&hiding_sk_bytes).unwrap();
1703 let binding_nonce = FrostNonce::deserialize(&binding_sk_bytes).unwrap();
1704
1705 let frost_nonces = FrostSigningNonces::from_nonces(hiding_nonce, binding_nonce);
1707 let frost_commitments = frost_nonces.commitments();
1708
1709 let marshalized_nonces = marshal_frost_nonces(&frost_nonces).unwrap();
1711 let unmarshalized_nonces = _unmarshal_frost_nonces(&marshalized_nonces).unwrap();
1712
1713 let marshalized_commitments = marshal_frost_commitments(frost_commitments).unwrap();
1715 let unmarshalized_commitments =
1716 test_unmarshal_frost_commitments(&marshalized_commitments).unwrap();
1717
1718 assert_eq!(frost_nonces, unmarshalized_nonces);
1720 assert_eq!(frost_commitments, &unmarshalized_commitments);
1721 }
1722}