breez_sdk_spark/signer/
external_types.rs

1//! FFI-compatible types for the `ExternalSigner` trait
2//!
3//! These types are designed to be simpler and FFI-safe, using basic types like
4//! Vec<u8> and String instead of complex Rust types.
5use bitcoin::bip32::DerivationPath;
6use bitcoin::secp256k1;
7use serde::{Deserialize, Serialize};
8use std::str::FromStr;
9
10use crate::SdkError;
11
12/// FFI-safe representation of a secp256k1 public key (33 bytes compressed)
13#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
14#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
15pub struct PublicKeyBytes {
16    pub bytes: Vec<u8>,
17}
18
19impl PublicKeyBytes {
20    pub fn from_public_key(pk: &secp256k1::PublicKey) -> Self {
21        Self {
22            bytes: pk.serialize().to_vec(),
23        }
24    }
25
26    pub fn to_public_key(&self) -> Result<secp256k1::PublicKey, SdkError> {
27        secp256k1::PublicKey::from_slice(&self.bytes)
28            .map_err(|e| SdkError::Generic(format!("Invalid public key bytes: {e}")))
29    }
30}
31
32/// FFI-safe representation of an ECDSA signature (64 bytes)
33#[derive(Clone, Debug, Serialize, Deserialize)]
34#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
35pub struct EcdsaSignatureBytes {
36    pub bytes: Vec<u8>,
37}
38
39impl EcdsaSignatureBytes {
40    pub fn from_signature(sig: &secp256k1::ecdsa::Signature) -> Self {
41        Self {
42            bytes: sig.serialize_compact().to_vec(),
43        }
44    }
45
46    pub fn to_signature(&self) -> Result<secp256k1::ecdsa::Signature, SdkError> {
47        secp256k1::ecdsa::Signature::from_compact(&self.bytes)
48            .map_err(|e| SdkError::Generic(format!("Invalid ECDSA signature bytes: {e}")))
49    }
50}
51
52/// FFI-safe representation of a Schnorr signature (64 bytes)
53#[derive(Clone, Debug, Serialize, Deserialize)]
54#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
55pub struct SchnorrSignatureBytes {
56    pub bytes: Vec<u8>,
57}
58
59impl SchnorrSignatureBytes {
60    pub fn from_signature(sig: &secp256k1::schnorr::Signature) -> Self {
61        Self {
62            bytes: sig.as_ref().to_vec(),
63        }
64    }
65
66    pub fn to_signature(&self) -> Result<secp256k1::schnorr::Signature, SdkError> {
67        secp256k1::schnorr::Signature::from_slice(&self.bytes)
68            .map_err(|e| SdkError::Generic(format!("Invalid Schnorr signature bytes: {e}")))
69    }
70}
71
72/// FFI-safe representation of a recoverable ECDSA signature (65 bytes: 1 recovery byte + 64 signature bytes)
73#[derive(Clone, Debug, Serialize, Deserialize)]
74#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
75pub struct RecoverableEcdsaSignatureBytes {
76    pub bytes: Vec<u8>,
77}
78
79impl RecoverableEcdsaSignatureBytes {
80    pub fn new(bytes: Vec<u8>) -> Self {
81        Self { bytes }
82    }
83}
84
85/// FFI-safe representation of a private key (32 bytes)
86#[derive(Clone, Debug, Serialize, Deserialize)]
87#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
88pub struct PrivateKeyBytes {
89    pub bytes: Vec<u8>,
90}
91
92impl PrivateKeyBytes {
93    pub fn from_secret_key(sk: &secp256k1::SecretKey) -> Self {
94        Self {
95            bytes: sk.secret_bytes().to_vec(),
96        }
97    }
98
99    pub fn to_secret_key(&self) -> Result<secp256k1::SecretKey, SdkError> {
100        secp256k1::SecretKey::from_slice(&self.bytes)
101            .map_err(|e| SdkError::Generic(format!("Invalid private key bytes: {e}")))
102    }
103}
104
105/// Helper functions for `DerivationPath` string conversion
106pub fn derivation_path_to_string(path: &DerivationPath) -> String {
107    path.to_string()
108}
109
110pub fn string_to_derivation_path(s: &str) -> Result<DerivationPath, SdkError> {
111    DerivationPath::from_str(s)
112        .map_err(|e| SdkError::Generic(format!("Invalid derivation path '{s}': {e}")))
113}
114
115/// FFI-safe representation of `spark_wallet::TreeNodeId`
116#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
117#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
118pub struct ExternalTreeNodeId {
119    /// The tree node identifier as a string
120    pub id: String,
121}
122
123impl ExternalTreeNodeId {
124    pub fn from_tree_node_id(id: &spark_wallet::TreeNodeId) -> Result<Self, SdkError> {
125        Ok(Self { id: id.to_string() })
126    }
127
128    pub fn to_tree_node_id(&self) -> Result<spark_wallet::TreeNodeId, SdkError> {
129        spark_wallet::TreeNodeId::from_str(&self.id)
130            .map_err(|e| SdkError::Generic(format!("Invalid TreeNodeId: {e}")))
131    }
132}
133
134/// FFI-safe representation of `spark_wallet::EncryptedPrivateKey`
135#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
136#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
137pub struct ExternalEncryptedPrivateKey {
138    /// The encrypted ciphertext
139    pub ciphertext: Vec<u8>,
140}
141
142impl ExternalEncryptedPrivateKey {
143    pub fn from_encrypted_private_key(
144        key: &spark_wallet::EncryptedPrivateKey,
145    ) -> Result<Self, SdkError> {
146        Ok(Self {
147            ciphertext: key.as_slice().to_vec(),
148        })
149    }
150
151    pub fn to_encrypted_private_key(&self) -> Result<spark_wallet::EncryptedPrivateKey, SdkError> {
152        Ok(spark_wallet::EncryptedPrivateKey::new(
153            self.ciphertext.clone(),
154        ))
155    }
156}
157
158/// FFI-safe representation of `spark_wallet::PrivateKeySource`
159#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
160#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
161pub enum ExternalPrivateKeySource {
162    /// Private key derived from a tree node
163    Derived { node_id: ExternalTreeNodeId },
164    /// Encrypted private key
165    Encrypted { key: ExternalEncryptedPrivateKey },
166}
167
168impl ExternalPrivateKeySource {
169    pub fn from_private_key_source(
170        source: &spark_wallet::PrivateKeySource,
171    ) -> Result<Self, SdkError> {
172        match source {
173            spark_wallet::PrivateKeySource::Derived(node_id) => Ok(Self::Derived {
174                node_id: ExternalTreeNodeId::from_tree_node_id(node_id)?,
175            }),
176            spark_wallet::PrivateKeySource::Encrypted(key) => Ok(Self::Encrypted {
177                key: ExternalEncryptedPrivateKey::from_encrypted_private_key(key)?,
178            }),
179        }
180    }
181
182    pub fn to_private_key_source(&self) -> Result<spark_wallet::PrivateKeySource, SdkError> {
183        match self {
184            Self::Derived { node_id } => Ok(spark_wallet::PrivateKeySource::Derived(
185                node_id.to_tree_node_id()?,
186            )),
187            Self::Encrypted { key } => Ok(spark_wallet::PrivateKeySource::Encrypted(
188                key.to_encrypted_private_key()?,
189            )),
190        }
191    }
192}
193
194/// FFI-safe representation of `spark_wallet::SecretToSplit`
195#[derive(Clone, Debug, Serialize, Deserialize)]
196#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
197pub enum ExternalSecretToSplit {
198    /// A private key to split
199    PrivateKey { source: ExternalPrivateKeySource },
200    /// A preimage to split (32 bytes)
201    Preimage { data: Vec<u8> },
202}
203
204impl ExternalSecretToSplit {
205    pub fn from_secret_to_split(secret: &spark_wallet::SecretToSplit) -> Result<Self, SdkError> {
206        match secret {
207            spark_wallet::SecretToSplit::PrivateKey(source) => Ok(Self::PrivateKey {
208                source: ExternalPrivateKeySource::from_private_key_source(source)?,
209            }),
210            spark_wallet::SecretToSplit::Preimage(data) => {
211                Ok(Self::Preimage { data: data.clone() })
212            }
213        }
214    }
215
216    pub fn to_secret_to_split(&self) -> Result<spark_wallet::SecretToSplit, SdkError> {
217        match self {
218            Self::PrivateKey { source } => Ok(spark_wallet::SecretToSplit::PrivateKey(
219                source.to_private_key_source()?,
220            )),
221            Self::Preimage { data } => Ok(spark_wallet::SecretToSplit::Preimage(data.clone())),
222        }
223    }
224}
225
226/// FFI-safe representation of `k256::Scalar` (32 bytes)
227#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
228#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
229pub struct ExternalScalar {
230    /// The 32-byte scalar value
231    pub bytes: Vec<u8>,
232}
233
234/// FFI-safe representation of `spark_wallet::SecretShare`
235#[derive(Clone, Debug, Serialize, Deserialize)]
236#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
237pub struct ExternalSecretShare {
238    /// Number of shares required to recover the secret
239    pub threshold: u32,
240    /// Index (x-coordinate) of the share as 32 bytes
241    pub index: ExternalScalar,
242    /// Share value (y-coordinate) as 32 bytes
243    pub share: ExternalScalar,
244}
245
246/// FFI-safe representation of `spark_wallet::VerifiableSecretShare`
247#[derive(Clone, Debug, Serialize, Deserialize)]
248#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
249pub struct ExternalVerifiableSecretShare {
250    /// Base secret share containing threshold, index, and share value
251    pub secret_share: ExternalSecretShare,
252    /// Cryptographic proofs for share verification (each proof is 33 bytes compressed public key)
253    pub proofs: Vec<Vec<u8>>,
254}
255
256impl ExternalVerifiableSecretShare {
257    pub fn from_verifiable_secret_share(
258        share: &spark_wallet::VerifiableSecretShare,
259    ) -> Result<Self, SdkError> {
260        use k256::elliptic_curve::sec1::ToEncodedPoint;
261
262        let secret_share = ExternalSecretShare {
263            threshold: share
264                .secret_share
265                .threshold
266                .try_into()
267                .map_err(|_| SdkError::Generic("Threshold value too large".to_string()))?,
268            index: ExternalScalar {
269                bytes: share.secret_share.index.to_bytes().to_vec(),
270            },
271            share: ExternalScalar {
272                bytes: share.secret_share.share.to_bytes().to_vec(),
273            },
274        };
275
276        let proofs = share
277            .proofs
278            .iter()
279            .map(|pk| pk.to_encoded_point(true).as_bytes().to_vec())
280            .collect();
281
282        Ok(Self {
283            secret_share,
284            proofs,
285        })
286    }
287
288    pub fn to_verifiable_secret_share(
289        &self,
290    ) -> Result<spark_wallet::VerifiableSecretShare, SdkError> {
291        use k256::elliptic_curve::PrimeField;
292        use k256::{FieldBytes, PublicKey as k256PublicKey, Scalar};
293
294        let index_bytes: [u8; 32] = self.secret_share.index.bytes[..]
295            .try_into()
296            .map_err(|_| SdkError::Generic("Invalid index scalar length".into()))?;
297        let index = Scalar::from_repr(FieldBytes::clone_from_slice(&index_bytes))
298            .into_option()
299            .ok_or_else(|| SdkError::Generic("Invalid index scalar".into()))?;
300
301        let share_bytes: [u8; 32] = self.secret_share.share.bytes[..]
302            .try_into()
303            .map_err(|_| SdkError::Generic("Invalid share scalar length".into()))?;
304        let share = Scalar::from_repr(FieldBytes::clone_from_slice(&share_bytes))
305            .into_option()
306            .ok_or_else(|| SdkError::Generic("Invalid share scalar".into()))?;
307
308        let proofs: Vec<k256PublicKey> = self
309            .proofs
310            .iter()
311            .map(|bytes| {
312                k256PublicKey::from_sec1_bytes(bytes)
313                    .map_err(|e| SdkError::Generic(format!("Invalid proof public key: {e}")))
314            })
315            .collect::<Result<Vec<_>, _>>()?;
316
317        Ok(spark_wallet::VerifiableSecretShare {
318            secret_share: spark_wallet::SecretShare {
319                threshold: self.secret_share.threshold as usize,
320                index,
321                share,
322            },
323            proofs,
324        })
325    }
326}
327
328/// FFI-safe representation of `frost_secp256k1_tr::round2::SignatureShare`
329#[derive(Clone, Debug, Serialize, Deserialize)]
330#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
331pub struct ExternalFrostSignatureShare {
332    /// Serialized signature share bytes (variable length, typically 32 bytes)
333    pub bytes: Vec<u8>,
334}
335
336impl ExternalFrostSignatureShare {
337    pub fn from_signature_share(
338        share: &frost_secp256k1_tr::round2::SignatureShare,
339    ) -> Result<Self, SdkError> {
340        let bytes = share.serialize();
341        Ok(Self { bytes })
342    }
343
344    pub fn to_signature_share(
345        &self,
346    ) -> Result<frost_secp256k1_tr::round2::SignatureShare, SdkError> {
347        frost_secp256k1_tr::round2::SignatureShare::deserialize(&self.bytes)
348            .map_err(|e| SdkError::Generic(format!("Failed to deserialize SignatureShare: {e}")))
349    }
350}
351
352/// FFI-safe representation of `frost_secp256k1_tr::Signature`
353#[derive(Clone, Debug, Serialize, Deserialize)]
354#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
355pub struct ExternalFrostSignature {
356    /// Serialized Frost signature bytes (64 bytes)
357    pub bytes: Vec<u8>,
358}
359
360impl ExternalFrostSignature {
361    pub fn from_frost_signature(sig: &frost_secp256k1_tr::Signature) -> Result<Self, SdkError> {
362        let bytes = sig
363            .serialize()
364            .map_err(|e| SdkError::Generic(format!("Failed to serialize Frost signature: {e}")))?;
365        let bytes = bytes.clone();
366        Ok(Self { bytes })
367    }
368
369    pub fn to_frost_signature(&self) -> Result<frost_secp256k1_tr::Signature, SdkError> {
370        frost_secp256k1_tr::Signature::deserialize(&self.bytes)
371            .map_err(|e| SdkError::Generic(format!("Failed to deserialize Frost signature: {e}")))
372    }
373}
374
375/// FFI-safe representation of `spark_wallet::FrostSigningCommitmentsWithNonces`
376#[derive(Clone, Debug, Serialize, Deserialize)]
377#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
378pub struct ExternalFrostCommitments {
379    /// Serialized hiding nonce commitment (variable length, typically 33 bytes compressed point)
380    pub hiding_commitment: Vec<u8>,
381    /// Serialized binding nonce commitment (variable length, typically 33 bytes compressed point)
382    pub binding_commitment: Vec<u8>,
383    /// Encrypted nonces ciphertext
384    pub nonces_ciphertext: Vec<u8>,
385}
386
387impl ExternalFrostCommitments {
388    pub fn from_frost_commitments(
389        commitments: &spark_wallet::FrostSigningCommitmentsWithNonces,
390    ) -> Result<Self, SdkError> {
391        let hiding_commitment = commitments.commitments.hiding().serialize().map_err(|e| {
392            SdkError::Generic(format!("Failed to serialize hiding commitment: {e}"))
393        })?;
394        let binding_commitment = commitments.commitments.binding().serialize().map_err(|e| {
395            SdkError::Generic(format!("Failed to serialize binding commitment: {e}"))
396        })?;
397
398        Ok(Self {
399            hiding_commitment,
400            binding_commitment,
401            nonces_ciphertext: commitments.nonces_ciphertext.clone(),
402        })
403    }
404
405    pub fn to_frost_commitments(
406        &self,
407    ) -> Result<spark_wallet::FrostSigningCommitmentsWithNonces, SdkError> {
408        use frost_secp256k1_tr::round1::{NonceCommitment, SigningCommitments};
409
410        let hiding = NonceCommitment::deserialize(&self.hiding_commitment).map_err(|e| {
411            SdkError::Generic(format!("Failed to deserialize hiding commitment: {e}"))
412        })?;
413        let binding = NonceCommitment::deserialize(&self.binding_commitment).map_err(|e| {
414            SdkError::Generic(format!("Failed to deserialize binding commitment: {e}"))
415        })?;
416
417        let commitments = SigningCommitments::new(hiding, binding);
418
419        Ok(spark_wallet::FrostSigningCommitmentsWithNonces {
420            commitments,
421            nonces_ciphertext: self.nonces_ciphertext.clone(),
422        })
423    }
424}
425
426/// FFI-safe representation of `frost_secp256k1_tr::Identifier`
427#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
428#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
429pub struct ExternalIdentifier {
430    /// Serialized identifier bytes
431    pub bytes: Vec<u8>,
432}
433
434impl ExternalIdentifier {
435    pub fn from_identifier(id: &frost_secp256k1_tr::Identifier) -> Self {
436        Self {
437            bytes: id.serialize(),
438        }
439    }
440
441    pub fn to_identifier(&self) -> Result<frost_secp256k1_tr::Identifier, SdkError> {
442        frost_secp256k1_tr::Identifier::deserialize(&self.bytes)
443            .map_err(|e| SdkError::Generic(format!("Invalid identifier: {e}")))
444    }
445}
446
447/// FFI-safe representation of `frost_secp256k1_tr::round1::SigningCommitments`
448#[derive(Clone, Debug, Serialize, Deserialize)]
449#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
450pub struct ExternalSigningCommitments {
451    /// Serialized hiding nonce commitment
452    pub hiding: Vec<u8>,
453    /// Serialized binding nonce commitment
454    pub binding: Vec<u8>,
455}
456
457impl ExternalSigningCommitments {
458    pub fn from_signing_commitments(
459        commitments: &frost_secp256k1_tr::round1::SigningCommitments,
460    ) -> Result<Self, SdkError> {
461        let hiding = commitments.hiding().serialize().map_err(|e| {
462            SdkError::Generic(format!("Failed to serialize hiding commitment: {e}"))
463        })?;
464        let binding = commitments.binding().serialize().map_err(|e| {
465            SdkError::Generic(format!("Failed to serialize binding commitment: {e}"))
466        })?;
467        Ok(Self { hiding, binding })
468    }
469
470    pub fn to_signing_commitments(
471        &self,
472    ) -> Result<frost_secp256k1_tr::round1::SigningCommitments, SdkError> {
473        use frost_secp256k1_tr::round1::NonceCommitment;
474
475        let hiding = NonceCommitment::deserialize(&self.hiding)
476            .map_err(|e| SdkError::Generic(format!("Failed to deserialize hiding: {e}")))?;
477        let binding = NonceCommitment::deserialize(&self.binding)
478            .map_err(|e| SdkError::Generic(format!("Failed to deserialize binding: {e}")))?;
479
480        Ok(frost_secp256k1_tr::round1::SigningCommitments::new(
481            hiding, binding,
482        ))
483    }
484}
485
486/// FFI-safe wrapper for (Identifier, `SigningCommitments`) pair
487#[derive(Clone, Debug, Serialize, Deserialize)]
488#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
489pub struct IdentifierCommitmentPair {
490    pub identifier: ExternalIdentifier,
491    pub commitment: ExternalSigningCommitments,
492}
493
494/// FFI-safe wrapper for (Identifier, `SignatureShare`) pair
495#[derive(Clone, Debug, Serialize, Deserialize)]
496#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
497pub struct IdentifierSignaturePair {
498    pub identifier: ExternalIdentifier,
499    pub signature: ExternalFrostSignatureShare,
500}
501
502/// FFI-safe wrapper for (Identifier, `PublicKey`) pair
503#[derive(Clone, Debug, Serialize, Deserialize)]
504#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
505pub struct IdentifierPublicKeyPair {
506    pub identifier: ExternalIdentifier,
507    pub public_key: Vec<u8>,
508}
509
510/// FFI-safe representation of `spark_wallet::SignFrostRequest`
511#[derive(Clone, Debug, Serialize, Deserialize)]
512#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
513pub struct ExternalSignFrostRequest {
514    /// The message to sign
515    pub message: Vec<u8>,
516    /// The public key (33 bytes compressed)
517    pub public_key: Vec<u8>,
518    /// The private key source
519    pub private_key: ExternalPrivateKeySource,
520    /// The verifying key (33 bytes compressed)
521    pub verifying_key: Vec<u8>,
522    /// The self nonce commitment
523    pub self_nonce_commitment: ExternalFrostCommitments,
524    /// Statechain commitments as a list of identifier-commitment pairs
525    pub statechain_commitments: Vec<IdentifierCommitmentPair>,
526    /// Optional adaptor public key (33 bytes compressed)
527    pub adaptor_public_key: Option<Vec<u8>>,
528}
529
530impl ExternalSignFrostRequest {
531    pub fn from_sign_frost_request(
532        request: &spark_wallet::SignFrostRequest,
533    ) -> Result<Self, SdkError> {
534        let statechain_commitments = request
535            .statechain_commitments
536            .iter()
537            .map(|(id, comm)| {
538                Ok(IdentifierCommitmentPair {
539                    identifier: ExternalIdentifier::from_identifier(id),
540                    commitment: ExternalSigningCommitments::from_signing_commitments(comm)?,
541                })
542            })
543            .collect::<Result<Vec<_>, SdkError>>()?;
544
545        Ok(Self {
546            message: request.message.to_vec(),
547            public_key: request.public_key.serialize().to_vec(),
548            private_key: ExternalPrivateKeySource::from_private_key_source(request.private_key)?,
549            verifying_key: request.verifying_key.serialize().to_vec(),
550            self_nonce_commitment: ExternalFrostCommitments::from_frost_commitments(
551                request.self_nonce_commitment,
552            )?,
553            statechain_commitments,
554            adaptor_public_key: request.adaptor_public_key.map(|pk| pk.serialize().to_vec()),
555        })
556    }
557
558    pub fn to_sign_frost_request(
559        &self,
560    ) -> Result<spark_wallet::SignFrostRequest<'static>, SdkError> {
561        use std::collections::BTreeMap;
562
563        let public_key = secp256k1::PublicKey::from_slice(&self.public_key)
564            .map_err(|e| SdkError::Generic(format!("Invalid public key: {e}")))?;
565        let verifying_key = secp256k1::PublicKey::from_slice(&self.verifying_key)
566            .map_err(|e| SdkError::Generic(format!("Invalid verifying key: {e}")))?;
567
568        let statechain_commitments: BTreeMap<_, _> = self
569            .statechain_commitments
570            .iter()
571            .map(|pair| {
572                Ok((
573                    pair.identifier.to_identifier()?,
574                    pair.commitment.to_signing_commitments()?,
575                ))
576            })
577            .collect::<Result<_, SdkError>>()?;
578
579        let adaptor_public_key = self
580            .adaptor_public_key
581            .as_ref()
582            .map(|bytes| {
583                secp256k1::PublicKey::from_slice(bytes)
584                    .map_err(|e| SdkError::Generic(format!("Invalid adaptor public key: {e}")))
585            })
586            .transpose()?;
587
588        // Note: This creates a static lifetime version with owned data
589        // The actual usage will need to handle the conversion appropriately
590        Ok(spark_wallet::SignFrostRequest {
591            message: Box::leak(self.message.clone().into_boxed_slice()),
592            public_key: Box::leak(Box::new(public_key)),
593            private_key: Box::leak(Box::new(self.private_key.to_private_key_source()?)),
594            verifying_key: Box::leak(Box::new(verifying_key)),
595            self_nonce_commitment: Box::leak(Box::new(
596                self.self_nonce_commitment.to_frost_commitments()?,
597            )),
598            statechain_commitments,
599            adaptor_public_key: adaptor_public_key.map(|pk| Box::leak(Box::new(pk)) as &_),
600        })
601    }
602}
603
604/// FFI-safe representation of `spark_wallet::AggregateFrostRequest`
605#[derive(Clone, Debug, Serialize, Deserialize)]
606#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
607pub struct ExternalAggregateFrostRequest {
608    /// The message that was signed
609    pub message: Vec<u8>,
610    /// Statechain signatures as a list of identifier-signature pairs
611    pub statechain_signatures: Vec<IdentifierSignaturePair>,
612    /// Statechain public keys as a list of identifier-publickey pairs
613    pub statechain_public_keys: Vec<IdentifierPublicKeyPair>,
614    /// The verifying key (33 bytes compressed)
615    pub verifying_key: Vec<u8>,
616    /// Statechain commitments as a list of identifier-commitment pairs
617    pub statechain_commitments: Vec<IdentifierCommitmentPair>,
618    /// The self commitment
619    pub self_commitment: ExternalSigningCommitments,
620    /// The public key (33 bytes compressed)
621    pub public_key: Vec<u8>,
622    /// The self signature share
623    pub self_signature: ExternalFrostSignatureShare,
624    /// Optional adaptor public key (33 bytes compressed)
625    pub adaptor_public_key: Option<Vec<u8>>,
626}
627
628impl ExternalAggregateFrostRequest {
629    pub fn from_aggregate_frost_request(
630        request: &spark_wallet::AggregateFrostRequest,
631    ) -> Result<Self, SdkError> {
632        let statechain_signatures = request
633            .statechain_signatures
634            .iter()
635            .map(|(id, share)| {
636                Ok(IdentifierSignaturePair {
637                    identifier: ExternalIdentifier::from_identifier(id),
638                    signature: ExternalFrostSignatureShare::from_signature_share(share)?,
639                })
640            })
641            .collect::<Result<Vec<_>, SdkError>>()?;
642
643        let statechain_public_keys = request
644            .statechain_public_keys
645            .iter()
646            .map(|(id, pk)| IdentifierPublicKeyPair {
647                identifier: ExternalIdentifier::from_identifier(id),
648                public_key: pk.serialize().to_vec(),
649            })
650            .collect();
651
652        let statechain_commitments = request
653            .statechain_commitments
654            .iter()
655            .map(|(id, comm)| {
656                Ok(IdentifierCommitmentPair {
657                    identifier: ExternalIdentifier::from_identifier(id),
658                    commitment: ExternalSigningCommitments::from_signing_commitments(comm)?,
659                })
660            })
661            .collect::<Result<Vec<_>, SdkError>>()?;
662
663        Ok(Self {
664            message: request.message.to_vec(),
665            statechain_signatures,
666            statechain_public_keys,
667            verifying_key: request.verifying_key.serialize().to_vec(),
668            statechain_commitments,
669            self_commitment: ExternalSigningCommitments::from_signing_commitments(
670                request.self_commitment,
671            )?,
672            public_key: request.public_key.serialize().to_vec(),
673            self_signature: ExternalFrostSignatureShare::from_signature_share(
674                request.self_signature,
675            )?,
676            adaptor_public_key: request.adaptor_public_key.map(|pk| pk.serialize().to_vec()),
677        })
678    }
679
680    pub fn to_aggregate_frost_request(
681        &self,
682    ) -> Result<spark_wallet::AggregateFrostRequest<'static>, SdkError> {
683        use std::collections::BTreeMap;
684
685        let statechain_signatures: BTreeMap<_, _> = self
686            .statechain_signatures
687            .iter()
688            .map(|pair| {
689                Ok((
690                    pair.identifier.to_identifier()?,
691                    pair.signature.to_signature_share()?,
692                ))
693            })
694            .collect::<Result<_, SdkError>>()?;
695
696        let statechain_public_keys: BTreeMap<_, _> = self
697            .statechain_public_keys
698            .iter()
699            .map(|pair| {
700                Ok((
701                    pair.identifier.to_identifier()?,
702                    secp256k1::PublicKey::from_slice(&pair.public_key)
703                        .map_err(|e| SdkError::Generic(format!("Invalid public key: {e}")))?,
704                ))
705            })
706            .collect::<Result<_, SdkError>>()?;
707
708        let verifying_key = secp256k1::PublicKey::from_slice(&self.verifying_key)
709            .map_err(|e| SdkError::Generic(format!("Invalid verifying key: {e}")))?;
710
711        let statechain_commitments: BTreeMap<_, _> = self
712            .statechain_commitments
713            .iter()
714            .map(|pair| {
715                Ok((
716                    pair.identifier.to_identifier()?,
717                    pair.commitment.to_signing_commitments()?,
718                ))
719            })
720            .collect::<Result<_, SdkError>>()?;
721
722        let public_key = secp256k1::PublicKey::from_slice(&self.public_key)
723            .map_err(|e| SdkError::Generic(format!("Invalid public key: {e}")))?;
724
725        let adaptor_public_key = self
726            .adaptor_public_key
727            .as_ref()
728            .map(|bytes| {
729                secp256k1::PublicKey::from_slice(bytes)
730                    .map_err(|e| SdkError::Generic(format!("Invalid adaptor public key: {e}")))
731            })
732            .transpose()?;
733
734        Ok(spark_wallet::AggregateFrostRequest {
735            message: Box::leak(self.message.clone().into_boxed_slice()),
736            statechain_signatures,
737            statechain_public_keys,
738            verifying_key: Box::leak(Box::new(verifying_key)),
739            statechain_commitments,
740            self_commitment: Box::leak(Box::new(self.self_commitment.to_signing_commitments()?)),
741            public_key: Box::leak(Box::new(public_key)),
742            self_signature: Box::leak(Box::new(self.self_signature.to_signature_share()?)),
743            adaptor_public_key: adaptor_public_key.map(|pk| Box::leak(Box::new(pk)) as &_),
744        })
745    }
746}