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 a FROST round-1 nonce commitment.
232///
233/// The nonces themselves never cross this boundary: a signer keeps them until
234/// it signs, which is what makes them single-use.
235#[derive(Clone, Debug, Serialize, Deserialize)]
236#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
237pub struct ExternalFrostCommitments {
238    /// Serialized hiding nonce commitment (variable length, typically 33 bytes compressed point)
239    pub hiding_commitment: Vec<u8>,
240    /// Serialized binding nonce commitment (variable length, typically 33 bytes compressed point)
241    pub binding_commitment: Vec<u8>,
242}
243
244impl ExternalFrostCommitments {
245    pub fn from_frost_commitments(
246        commitments: &frost_secp256k1_tr::round1::SigningCommitments,
247    ) -> Result<Self, SdkError> {
248        let hiding_commitment = commitments.hiding().serialize().map_err(|e| {
249            SdkError::Generic(format!("Failed to serialize hiding commitment: {e}"))
250        })?;
251        let binding_commitment = commitments.binding().serialize().map_err(|e| {
252            SdkError::Generic(format!("Failed to serialize binding commitment: {e}"))
253        })?;
254
255        Ok(Self {
256            hiding_commitment,
257            binding_commitment,
258        })
259    }
260
261    pub fn to_frost_commitments(
262        &self,
263    ) -> Result<frost_secp256k1_tr::round1::SigningCommitments, 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        Ok(SigningCommitments::new(hiding, binding))
274    }
275}
276
277/// FFI-safe representation of `frost_secp256k1_tr::Identifier`
278#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
279#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
280pub struct ExternalIdentifier {
281    /// Serialized identifier bytes
282    pub bytes: Vec<u8>,
283}
284
285impl ExternalIdentifier {
286    pub fn from_identifier(id: &frost_secp256k1_tr::Identifier) -> Self {
287        Self {
288            bytes: id.serialize(),
289        }
290    }
291
292    pub fn to_identifier(&self) -> Result<frost_secp256k1_tr::Identifier, SdkError> {
293        frost_secp256k1_tr::Identifier::deserialize(&self.bytes)
294            .map_err(|e| SdkError::Generic(format!("Invalid identifier: {e}")))
295    }
296}
297
298/// FFI-safe representation of `frost_secp256k1_tr::round1::SigningCommitments`
299#[derive(Clone, Debug, Serialize, Deserialize)]
300#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
301pub struct ExternalSigningCommitments {
302    /// Serialized hiding nonce commitment
303    pub hiding: Vec<u8>,
304    /// Serialized binding nonce commitment
305    pub binding: Vec<u8>,
306}
307
308impl ExternalSigningCommitments {
309    pub fn from_signing_commitments(
310        commitments: &frost_secp256k1_tr::round1::SigningCommitments,
311    ) -> Result<Self, SdkError> {
312        let hiding = commitments.hiding().serialize().map_err(|e| {
313            SdkError::Generic(format!("Failed to serialize hiding commitment: {e}"))
314        })?;
315        let binding = commitments.binding().serialize().map_err(|e| {
316            SdkError::Generic(format!("Failed to serialize binding commitment: {e}"))
317        })?;
318        Ok(Self { hiding, binding })
319    }
320
321    pub fn to_signing_commitments(
322        &self,
323    ) -> Result<frost_secp256k1_tr::round1::SigningCommitments, SdkError> {
324        use frost_secp256k1_tr::round1::NonceCommitment;
325
326        let hiding = NonceCommitment::deserialize(&self.hiding)
327            .map_err(|e| SdkError::Generic(format!("Failed to deserialize hiding: {e}")))?;
328        let binding = NonceCommitment::deserialize(&self.binding)
329            .map_err(|e| SdkError::Generic(format!("Failed to deserialize binding: {e}")))?;
330
331        Ok(frost_secp256k1_tr::round1::SigningCommitments::new(
332            hiding, binding,
333        ))
334    }
335}
336
337/// FFI-safe wrapper for (Identifier, `SigningCommitments`) pair
338#[derive(Clone, Debug, Serialize, Deserialize)]
339#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
340pub struct IdentifierCommitmentPair {
341    pub identifier: ExternalIdentifier,
342    pub commitment: ExternalSigningCommitments,
343}
344
345/// FFI-safe wrapper for (Identifier, `SignatureShare`) pair
346#[derive(Clone, Debug, Serialize, Deserialize)]
347#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
348pub struct IdentifierSignaturePair {
349    pub identifier: ExternalIdentifier,
350    pub signature: ExternalFrostSignatureShare,
351}
352
353/// FFI-safe wrapper for (Identifier, `PublicKey`) pair
354#[derive(Clone, Debug, Serialize, Deserialize)]
355#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
356pub struct IdentifierPublicKeyPair {
357    pub identifier: ExternalIdentifier,
358    pub public_key: Vec<u8>,
359}