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