Skip to main content

breez_sdk_spark/signer/
external_types.rs

1//! FFI-compatible types for the `ExternalBreezSigner` 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, Serialize, Deserialize)]
129#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
130pub struct SecretBytes {
131    pub bytes: Vec<u8>,
132}
133
134/// Redacted `Debug`: never print the raw key, so it can't leak into logs even
135/// when wrapped in a `Debug`-deriving container. Mirrors `secp256k1::SecretKey`.
136impl std::fmt::Debug for SecretBytes {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        f.debug_tuple("SecretBytes").field(&"<redacted>").finish()
139    }
140}
141
142impl SecretBytes {
143    pub fn from_secret_key(sk: &secp256k1::SecretKey) -> Self {
144        Self {
145            bytes: sk.secret_bytes().to_vec(),
146        }
147    }
148
149    pub fn to_secret_key(&self) -> Result<secp256k1::SecretKey, SdkError> {
150        secp256k1::SecretKey::from_slice(&self.bytes)
151            .map_err(|e| SdkError::Generic(format!("Invalid private key bytes: {e}")))
152    }
153}
154
155/// Helper functions for `DerivationPath` string conversion
156pub fn derivation_path_to_string(path: &DerivationPath) -> String {
157    path.to_string()
158}
159
160pub fn string_to_derivation_path(s: &str) -> Result<DerivationPath, SdkError> {
161    DerivationPath::from_str(s)
162        .map_err(|e| SdkError::Generic(format!("Invalid derivation path '{s}': {e}")))
163}
164
165/// FFI-safe representation of `spark_wallet::TreeNodeId`
166#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
167#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
168pub struct ExternalTreeNodeId {
169    /// The tree node identifier as a string
170    pub id: String,
171}
172
173impl ExternalTreeNodeId {
174    pub fn from_tree_node_id(id: &spark_wallet::TreeNodeId) -> Result<Self, SdkError> {
175        Ok(Self { id: id.to_string() })
176    }
177
178    pub fn to_tree_node_id(&self) -> Result<spark_wallet::TreeNodeId, SdkError> {
179        spark_wallet::TreeNodeId::from_str(&self.id)
180            .map_err(|e| SdkError::Generic(format!("Invalid TreeNodeId: {e}")))
181    }
182}
183
184/// FFI-safe representation of `frost_secp256k1_tr::round2::SignatureShare`
185#[derive(Clone, Debug, Serialize, Deserialize)]
186#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
187pub struct ExternalFrostSignatureShare {
188    /// Serialized signature share bytes (variable length, typically 32 bytes)
189    pub bytes: Vec<u8>,
190}
191
192impl ExternalFrostSignatureShare {
193    pub fn from_signature_share(
194        share: &frost_secp256k1_tr::round2::SignatureShare,
195    ) -> Result<Self, SdkError> {
196        let bytes = share.serialize();
197        Ok(Self { bytes })
198    }
199
200    pub fn to_signature_share(
201        &self,
202    ) -> Result<frost_secp256k1_tr::round2::SignatureShare, SdkError> {
203        frost_secp256k1_tr::round2::SignatureShare::deserialize(&self.bytes)
204            .map_err(|e| SdkError::Generic(format!("Failed to deserialize SignatureShare: {e}")))
205    }
206}
207
208/// FFI-safe representation of `frost_secp256k1_tr::Signature`
209#[derive(Clone, Debug, Serialize, Deserialize)]
210#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
211pub struct ExternalFrostSignature {
212    /// Serialized Frost signature bytes (64 bytes)
213    pub bytes: Vec<u8>,
214}
215
216impl ExternalFrostSignature {
217    pub fn from_frost_signature(sig: &frost_secp256k1_tr::Signature) -> Result<Self, SdkError> {
218        let bytes = sig
219            .serialize()
220            .map_err(|e| SdkError::Generic(format!("Failed to serialize Frost signature: {e}")))?;
221        let bytes = bytes.clone();
222        Ok(Self { bytes })
223    }
224
225    pub fn to_frost_signature(&self) -> Result<frost_secp256k1_tr::Signature, SdkError> {
226        frost_secp256k1_tr::Signature::deserialize(&self.bytes)
227            .map_err(|e| SdkError::Generic(format!("Failed to deserialize Frost signature: {e}")))
228    }
229}
230
231/// FFI-safe representation of `spark_wallet::FrostSigningCommitmentsWithNonces`
232#[derive(Clone, Debug, Serialize, Deserialize)]
233#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
234pub struct ExternalFrostCommitments {
235    /// Serialized hiding nonce commitment (variable length, typically 33 bytes compressed point)
236    pub hiding_commitment: Vec<u8>,
237    /// Serialized binding nonce commitment (variable length, typically 33 bytes compressed point)
238    pub binding_commitment: Vec<u8>,
239    /// Encrypted nonces ciphertext
240    pub nonces_ciphertext: Vec<u8>,
241}
242
243impl ExternalFrostCommitments {
244    pub fn from_frost_commitments(
245        commitments: &spark_wallet::FrostSigningCommitmentsWithNonces,
246    ) -> Result<Self, SdkError> {
247        let hiding_commitment = commitments.commitments.hiding().serialize().map_err(|e| {
248            SdkError::Generic(format!("Failed to serialize hiding commitment: {e}"))
249        })?;
250        let binding_commitment = commitments.commitments.binding().serialize().map_err(|e| {
251            SdkError::Generic(format!("Failed to serialize binding commitment: {e}"))
252        })?;
253
254        Ok(Self {
255            hiding_commitment,
256            binding_commitment,
257            nonces_ciphertext: commitments.nonces_ciphertext.clone(),
258        })
259    }
260
261    pub fn to_frost_commitments(
262        &self,
263    ) -> Result<spark_wallet::FrostSigningCommitmentsWithNonces, SdkError> {
264        use frost_secp256k1_tr::round1::{NonceCommitment, SigningCommitments};
265
266        let hiding = NonceCommitment::deserialize(&self.hiding_commitment).map_err(|e| {
267            SdkError::Generic(format!("Failed to deserialize hiding commitment: {e}"))
268        })?;
269        let binding = NonceCommitment::deserialize(&self.binding_commitment).map_err(|e| {
270            SdkError::Generic(format!("Failed to deserialize binding commitment: {e}"))
271        })?;
272
273        let commitments = SigningCommitments::new(hiding, binding);
274
275        Ok(spark_wallet::FrostSigningCommitmentsWithNonces {
276            commitments,
277            nonces_ciphertext: self.nonces_ciphertext.clone(),
278        })
279    }
280}
281
282/// FFI-safe representation of `frost_secp256k1_tr::Identifier`
283#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
284#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
285pub struct ExternalIdentifier {
286    /// Serialized identifier bytes
287    pub bytes: Vec<u8>,
288}
289
290impl ExternalIdentifier {
291    pub fn from_identifier(id: &frost_secp256k1_tr::Identifier) -> Self {
292        Self {
293            bytes: id.serialize(),
294        }
295    }
296
297    pub fn to_identifier(&self) -> Result<frost_secp256k1_tr::Identifier, SdkError> {
298        frost_secp256k1_tr::Identifier::deserialize(&self.bytes)
299            .map_err(|e| SdkError::Generic(format!("Invalid identifier: {e}")))
300    }
301}
302
303/// FFI-safe representation of `frost_secp256k1_tr::round1::SigningCommitments`
304#[derive(Clone, Debug, Serialize, Deserialize)]
305#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
306pub struct ExternalSigningCommitments {
307    /// Serialized hiding nonce commitment
308    pub hiding: Vec<u8>,
309    /// Serialized binding nonce commitment
310    pub binding: Vec<u8>,
311}
312
313impl ExternalSigningCommitments {
314    pub fn from_signing_commitments(
315        commitments: &frost_secp256k1_tr::round1::SigningCommitments,
316    ) -> Result<Self, SdkError> {
317        let hiding = commitments.hiding().serialize().map_err(|e| {
318            SdkError::Generic(format!("Failed to serialize hiding commitment: {e}"))
319        })?;
320        let binding = commitments.binding().serialize().map_err(|e| {
321            SdkError::Generic(format!("Failed to serialize binding commitment: {e}"))
322        })?;
323        Ok(Self { hiding, binding })
324    }
325
326    pub fn to_signing_commitments(
327        &self,
328    ) -> Result<frost_secp256k1_tr::round1::SigningCommitments, SdkError> {
329        use frost_secp256k1_tr::round1::NonceCommitment;
330
331        let hiding = NonceCommitment::deserialize(&self.hiding)
332            .map_err(|e| SdkError::Generic(format!("Failed to deserialize hiding: {e}")))?;
333        let binding = NonceCommitment::deserialize(&self.binding)
334            .map_err(|e| SdkError::Generic(format!("Failed to deserialize binding: {e}")))?;
335
336        Ok(frost_secp256k1_tr::round1::SigningCommitments::new(
337            hiding, binding,
338        ))
339    }
340}
341
342/// FFI-safe wrapper for (Identifier, `SigningCommitments`) pair
343#[derive(Clone, Debug, Serialize, Deserialize)]
344#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
345pub struct IdentifierCommitmentPair {
346    pub identifier: ExternalIdentifier,
347    pub commitment: ExternalSigningCommitments,
348}
349
350/// FFI-safe wrapper for (Identifier, `SignatureShare`) pair
351#[derive(Clone, Debug, Serialize, Deserialize)]
352#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
353pub struct IdentifierSignaturePair {
354    pub identifier: ExternalIdentifier,
355    pub signature: ExternalFrostSignatureShare,
356}
357
358/// FFI-safe wrapper for (Identifier, `PublicKey`) pair
359#[derive(Clone, Debug, Serialize, Deserialize)]
360#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
361pub struct IdentifierPublicKeyPair {
362    pub identifier: ExternalIdentifier,
363    pub public_key: Vec<u8>,
364}