spark_rust/signer/
default_signer.rs

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/// A default, in-memory signer for Spark v1.
70///
71/// - This signer keeps a `master_seed` and derives keypairs to carry out signing operations.
72/// - Keys are held in memory and not persisted unless a future update is made
73///   to store them to disk.
74/// - Nonce commitments for each signing round remain ephemeral in memory,
75///   which is typically sufficient for short-lived signing sessions.
76#[derive(Clone)]
77pub struct DefaultSigner {
78    /// The master seed for the signer.
79    master_seed: Vec<u8>,
80
81    /// A map of references to nonce commitments, stored as strings. These are used
82    /// per signing round and can remain in memory until the round completes.
83    pub nonce_commitments: Arc<RwLock<HashbrownMap<String, String>>>,
84
85    /// A map from public keys to secret keys, stored in memory only. Keys can always be reconstructed
86    /// from the master seed and the derivation path.
87    pub public_keys_to_secret_keys: Arc<RwLock<HashbrownMap<PublicKey, SecretKey>>>,
88
89    /// The network that the signer is operating on.
90    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    // Helper function to create a DefaultSigner for testing secp256k1 operations.
244    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        // generate a random secret key to insert
287        let secret_key = SecretKey::new(&mut SparkRange);
288        let pubkey = signer.insert_secp256k1_keypair_from_secret_key(&secret_key)?;
289
290        // verify that the secret key exists in the signer space
291        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        // create two new keypairs
310        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        // do subtraction
317        let new_pubkey = signer.subtract_secret_keys_given_pubkeys(&pk1, &pk2, true)?;
318
319        // try exposing the new key's secret key
320        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        // Get the shares
336        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    // Helper function to create a DefaultSigner for testing Shamir.
380    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        // For demonstration: 2-of-3 threshold scheme.
395        let threshold = 2;
396        let num_shares = 3;
397
398        // Attempt to split the secret.
399        let shares = signer.split_with_verifiable_secret_sharing(message, threshold, num_shares)?;
400
401        // We expect exactly 'num_shares' shares.
402        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        // Create a SHA256 hash of the payload
416        // Using SHA256::digest gives us the hash directly, similar to Go's sha256.Sum256
417        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        // Get the identity secret key
427        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    // Helper function to create a DefaultSigner for testing ECDSA operations.
465    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        // prepare and sign message
478        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        // verify the signature externally
483        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        // perform verification
492        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        // Generate a new keypair
508        let secp = Secp256k1::new();
509        let mut rng = thread_rng();
510        let keypair = bitcoin::secp256k1::Keypair::new(&secp, &mut rng);
511
512        // Create the signer
513        let signer = create_ecdsa_test_signer().await?;
514
515        // Add the keypair to the signer
516        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    // Helper function to create a DefaultSigner for testing ECIES operations.
570    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        // prepare keys
582        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        // encrypt then decrypt the ephemeral secret key
590        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        // verify the decrypted key matches the original ephemeral secret key
595        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(); // This is a safe unwrap
647        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            // prepare sign data
709            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                    &current.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                &current.parent_tx.clone(),
729                vec![],
730                internal_node.verification_key.clone().unwrap(),
731            )?;
732
733            // sign
734            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            // Handle refund transaction if present
760            if let Some(refund_signing_job) = current.creation_node.refund_tx_signing_job {
761                // these are constants for tree creation
762                let refund_prevout_index = 0;
763                let refund_signing_input_index = 0_usize;
764
765                // prepare signing data
766                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                    &current.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            // Push children to queue maintaining BFS order
816            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            // TODO: this is an internal error if not found -- change the error to a client-side internal error
847            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            // prepare signing data
854            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            // Calculate refund transaction sighash
873            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            // TODO: structure
890            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        // Get user signatures
907        let user_signatures = self.sign_frost(user_signing_jobs)?;
908
909        // Process signatures and create node signatures
910        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(), // Empty byte array
935            });
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        // sign
1013        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>,   // TODO: revisit the need here
1023        _refund_tx_bytes: Vec<u8>, // TODO: revisit the need here
1024        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        // Parse signatures, commitments, and public keys
1031        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        // Prepare FROST signing jobs
1059        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        // Parse signatures
1086        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        // get the signing key and the nonce
1132        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        // create key package
1144        let key_package = create_user_key_package(&signing_private_key.secret_bytes());
1145
1146        // unwrap or use empty vector for the adaptor public key
1147        let adaptor_public_key = adaptor_public_key.unwrap_or_default();
1148
1149        // construct the signing job
1150        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        // sign the message
1168        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        // expect the signature with the job id
1178        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    /// Creates a new wallet from a mnemonic (BIP-39).
1194    ///  - Converts the mnemonic to a 64-byte seed
1195    ///  - Calls `from_seed` to do the rest
1196    async fn from_mnemonic(
1197        mnemonic: &str,
1198        network: SparkNetwork,
1199    ) -> Result<Self::WrappedSigner, SparkSdkError> {
1200        // Generate the BIP-39 seed
1201        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        // let seed_bytes = seed.as_bytes().to_vec();
1209
1210        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    /// Loads the master seed from the signer instance.
1237    ///
1238    /// # Returns
1239    /// A clone of the master seed as a byte vector
1240    ///
1241    /// # Errors
1242    /// Returns a `SparkSdkError` if the master seed cannot be loaded
1243    pub(crate) fn load_master_seed(&self) -> Result<Vec<u8>, SparkSdkError> {
1244        Ok(self.master_seed.clone())
1245    }
1246
1247    /// Retrieves the identity secret key for this signer.
1248    ///
1249    /// Derives the identity key from the master seed using the predefined
1250    /// IDENTITY_KEYPAIR_INDEX.
1251    ///
1252    /// # Returns
1253    /// The identity secret key as a byte vector
1254    ///
1255    /// # Errors
1256    /// Returns a `SparkSdkError` if key derivation fails
1257    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    /// Retrieves a secret key from the in-memory key store by its public key.
1280    ///
1281    /// # Arguments
1282    /// * `public_key` - The public key whose corresponding secret key should be retrieved
1283    ///
1284    /// # Returns
1285    /// The corresponding secret key as a byte vector
1286    ///
1287    /// # Errors
1288    /// Returns a `SparkSdkError` if the public key is not found in the key store
1289    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    /// Inserts a keypair into the in-memory key store.
1305    ///
1306    /// # Arguments
1307    /// * `public_key` - The public key to store
1308    /// * `secret_key` - The corresponding secret key to store
1309    ///
1310    /// # Returns
1311    /// `Ok(())` if the insertion was successful
1312    ///
1313    /// # Errors
1314    /// Returns a `SparkSdkError` if the insertion fails
1315    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    /// Removes a keypair from the in-memory key store by its public key.
1328    ///
1329    /// # Arguments
1330    /// * `public_key` - The public key of the keypair to remove
1331    ///
1332    /// # Returns
1333    /// `Ok(())` if the removal was successful or if the key wasn't present
1334    ///
1335    /// # Errors
1336    /// Returns a `SparkSdkError` if the removal fails due to a lock issue
1337    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    /// Inserts a nonce pair into the in-memory nonce commitment store.
1347    ///
1348    /// # Arguments
1349    /// * `nonce_commitment` - The nonce commitment to store
1350    /// * `nonce` - The corresponding nonce to store
1351    ///
1352    /// # Returns
1353    /// `Ok(())` if the insertion was successful
1354    ///
1355    /// # Errors
1356    /// Returns a `SparkSdkError` if the insertion fails due to a lock issue
1357    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    /// Prepares a FROST signing job for a node transaction by assembling all required components.
1373    ///
1374    /// # Arguments
1375    /// * `signing_public_key` - The public key corresponding to the secret key that will be used for signing
1376    /// * `user_frost_commitments` - The user's FROST nonce commitments in serialized form. If user has no commitments at the time, pass in None, and the function will generate them.
1377    /// * `spark_frost_commitments` - Map of operator IDs to their FROST commitments
1378    /// * `serialized_bitcoin_tx` - The Bitcoin transaction to be signed in serialized form
1379    /// * `prevout_to_use` - Index of the previous output being spent
1380    /// * `signing_input_index` - Index of the input being signed
1381    /// * `parent_tx` - The parent transaction containing the output being spent
1382    /// * `adaptor_public_key` - Public key used for adaptor signatures (if any)
1383    /// * `verifying_key` - The verification key for the FROST signing session
1384    ///
1385    /// # Returns
1386    /// A [`FrostSigningJob`] containing:
1387    /// - A unique job ID
1388    /// - The message to be signed (transaction sighash)
1389    /// - The key package containing signing material
1390    /// - The verification key
1391    /// - Marshaled nonces and commitments from both user and operators
1392    /// - The adaptor public key
1393    ///
1394    /// # Errors
1395    /// Returns [`SparkSdkError`] if:
1396    /// - Secret key exposure fails
1397    /// - Nonce exposure fails
1398    /// - Marshaling of nonces/commitments fails
1399    /// - Transaction decoding fails
1400    /// - Sighash calculation fails
1401    #[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        // expose the secret key, expose and marshal signing nonces
1417        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        // get user frost commitments, if none, generate new ones
1423        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        // marshal the nonces and commitments
1432        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
1458/// Creates a FROST key package for the user.
1459///
1460/// This function constructs a protocol buffer representation of a user's key package
1461/// using the provided signing secret key.
1462///
1463/// # Arguments
1464/// * `signing_secret_key` - The secret key bytes to use for the key package
1465///
1466/// # Returns
1467/// A protocol buffer KeyPackage containing the user's identity and key information
1468pub(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;
1489/// Converts FROST signing commitments to the protocol buffer format.
1490///
1491/// # Arguments
1492/// * `commitments` - The FROST signing commitments to convert
1493///
1494/// # Returns
1495/// A `ProtoSigningCommitments` containing the serialized hiding and binding commitments
1496///
1497/// # Errors
1498/// Returns a `SparkSdkError` if marshalling fails
1499pub(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;
1509/// Converts FROST signing nonces to the protocol buffer format.
1510///
1511/// # Arguments
1512/// * `nonce` - The FROST signing nonces to convert
1513///
1514/// # Returns
1515/// A `ProtoSigningNonce` containing the serialized hiding and binding nonces
1516///
1517/// # Errors
1518/// Returns a `SparkSdkError` if marshalling fails
1519pub(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
1528/// Converts protocol buffer signing nonces back to FROST signing nonces.
1529///
1530/// # Arguments
1531/// * `nonce` - The protocol buffer nonces to convert
1532///
1533/// # Returns
1534/// The deserialized `FrostSigningNonces`
1535///
1536/// # Errors
1537/// Returns a `SparkSdkError` if unmarshalling fails
1538pub(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
1547/// Serializes protocol buffer signing commitments to a byte vector.
1548///
1549/// # Arguments
1550/// * `commitments` - The protocol buffer commitments to serialize
1551///
1552/// # Returns
1553/// A byte vector containing the serialized commitments with a specific prefix format
1554///
1555/// # Errors
1556/// Returns a `SparkSdkError` if serialization fails
1557fn 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
1571/// Generates a unique identifier for a signing job using UUID v7.
1572///
1573/// # Returns
1574/// A string containing a time-ordered UUID v7 for tracking signing jobs
1575fn generate_signing_job_id() -> String {
1576    Uuid::now_v7().to_string()
1577}
1578
1579/// Map type for storing signature shares keyed by participant identifier.
1580type SignatureSharesType = HashMap<String, Vec<u8>>;
1581/// Map type for storing public key shares keyed by participant identifier.
1582type PublicSharesType = HashMap<String, Vec<u8>>;
1583/// Optional type for storing signing keyshare data.
1584type SigningKeyshareType = Option<SigningKeyshare>;
1585/// Map type for storing operator signing commitments keyed by participant identifier.
1586type SigningCommitmentsType = HashMap<String, SparkOperatorCommitment>;
1587
1588/// Extracts signature data components from a FROST signing result.
1589///
1590/// # Arguments
1591/// * `signing_result` - The optional signing result containing signature shares, public keys,
1592///
1593/// # Returns
1594/// A tuple containing:
1595/// * `SignatureSharesType` - Map of participant IDs to their signature shares
1596/// * `PublicSharesType` - Map of participant IDs to their public key shares
1597/// * `SigningKeyshareType` - Optional signing keyshare data
1598/// * `SigningCommitmentsType` - Map of participant IDs to their signing commitments
1599///
1600/// # Errors
1601/// Returns a `SparkSdkError` if the signing result is `None` or invalid
1602fn 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
1627/// Creates a Bitcoin refund transaction.
1628///
1629/// This function builds a refund transaction using the specified input parameters
1630/// and creates appropriate outputs to spend from the specified outpoint.
1631///
1632/// # Arguments
1633/// * `sequence` - The sequence number to use for the transaction input
1634/// * `node_outpoint` - The outpoint to spend from
1635/// * `amount_sats` - The amount in satoshis to send to the receiving address
1636/// * `receiving_pubkey` - The public key that will receive the funds
1637/// * `network` - The Bitcoin network to use for address encoding
1638///
1639/// # Returns
1640/// A Bitcoin transaction ready to be signed
1641///
1642/// # Errors
1643/// Returns a `SparkSdkError` if transaction creation fails
1644fn 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        // Create nonces and commitments
1706        let frost_nonces = FrostSigningNonces::from_nonces(hiding_nonce, binding_nonce);
1707        let frost_commitments = frost_nonces.commitments();
1708
1709        // Marshal and unmarshal nonces
1710        let marshalized_nonces = marshal_frost_nonces(&frost_nonces).unwrap();
1711        let unmarshalized_nonces = _unmarshal_frost_nonces(&marshalized_nonces).unwrap();
1712
1713        // Marshal and unmarshal commitments
1714        let marshalized_commitments = marshal_frost_commitments(frost_commitments).unwrap();
1715        let unmarshalized_commitments =
1716            test_unmarshal_frost_commitments(&marshalized_commitments).unwrap();
1717
1718        // Assert that the nonces and commitments are the same
1719        assert_eq!(frost_nonces, unmarshalized_nonces);
1720        assert_eq!(frost_commitments, &unmarshalized_commitments);
1721    }
1722}