lwk/
signer.rs

1use crate::{LwkError, Mnemonic, Network, Pset, WolletDescriptor};
2use std::sync::Arc;
3
4#[derive(uniffi::Enum)]
5pub enum Singlesig {
6    Wpkh,
7    ShWpkh,
8}
9
10impl From<Singlesig> for lwk_common::Singlesig {
11    fn from(singlesig: Singlesig) -> Self {
12        match singlesig {
13            Singlesig::Wpkh => lwk_common::Singlesig::Wpkh,
14            Singlesig::ShWpkh => lwk_common::Singlesig::ShWpkh,
15        }
16    }
17}
18
19#[derive(uniffi::Enum)]
20pub enum DescriptorBlindingKey {
21    Slip77,
22    Slip77Rand,
23    Elip151,
24}
25
26impl From<DescriptorBlindingKey> for lwk_common::DescriptorBlindingKey {
27    fn from(blinding_key: DescriptorBlindingKey) -> Self {
28        match blinding_key {
29            DescriptorBlindingKey::Slip77 => lwk_common::DescriptorBlindingKey::Slip77,
30            DescriptorBlindingKey::Slip77Rand => lwk_common::DescriptorBlindingKey::Slip77Rand,
31            DescriptorBlindingKey::Elip151 => lwk_common::DescriptorBlindingKey::Elip151,
32        }
33    }
34}
35
36/// wrapper over [`lwk_common::Bip`]
37#[derive(uniffi::Object)]
38pub struct Bip {
39    inner: lwk_common::Bip,
40}
41
42#[uniffi::export]
43impl Bip {
44    /// For P2SH-P2WPKH wallets
45    #[uniffi::constructor]
46    pub fn new_bip49() -> Self {
47        Self {
48            inner: lwk_common::Bip::Bip49,
49        }
50    }
51
52    /// For P2WPKH wallets
53    #[uniffi::constructor]
54    pub fn new_bip84() -> Self {
55        Self {
56            inner: lwk_common::Bip::Bip84,
57        }
58    }
59
60    /// For multisig wallets
61    #[uniffi::constructor]
62    pub fn new_bip87() -> Self {
63        Self {
64            inner: lwk_common::Bip::Bip87,
65        }
66    }
67}
68
69/// A Software signer, wrapper over [`lwk_signer::SwSigner`]
70#[derive(uniffi::Object)]
71pub struct Signer {
72    pub(crate) inner: lwk_signer::SwSigner,
73}
74
75#[uniffi::export]
76impl Signer {
77    /// Construct a software signer
78    #[uniffi::constructor]
79    pub fn new(mnemonic: &Mnemonic, network: &Network) -> Result<Arc<Self>, LwkError> {
80        let inner = lwk_signer::SwSigner::new(&mnemonic.to_string(), network.is_mainnet())?;
81        Ok(Arc::new(Self { inner }))
82    }
83
84    /// Generate a new random software signer
85    #[uniffi::constructor]
86    pub fn random(network: &Network) -> Result<Arc<Self>, LwkError> {
87        let (inner, _mnemonic) = lwk_signer::SwSigner::random(network.is_mainnet())?;
88        Ok(Arc::new(Self { inner }))
89    }
90
91    /// Sign the given `pset`
92    ///
93    /// Note from an API perspective it would be better to consume the `pset` parameter so it would
94    /// be clear the signed PSET is the returned one, but it's not possible with uniffi bindings
95    pub fn sign(&self, pset: &Pset) -> Result<Arc<Pset>, LwkError> {
96        let mut pset = pset.inner();
97        lwk_common::Signer::sign(&self.inner, &mut pset)?;
98        Ok(Arc::new(pset.into()))
99    }
100
101    /// Return the witness public key hash, slip77 descriptor of this signer
102    pub fn wpkh_slip77_descriptor(&self) -> Result<Arc<WolletDescriptor>, LwkError> {
103        self.singlesig_desc(Singlesig::Wpkh, DescriptorBlindingKey::Slip77)
104    }
105
106    /// Generate a singlesig descriptor with the given parameters
107    pub fn singlesig_desc(
108        &self,
109        script_variant: Singlesig,
110        blinding_variant: DescriptorBlindingKey,
111    ) -> Result<Arc<WolletDescriptor>, LwkError> {
112        let desc_str = lwk_common::singlesig_desc(
113            &self.inner,
114            script_variant.into(),
115            blinding_variant.into(),
116        )?;
117        WolletDescriptor::new(&desc_str)
118    }
119
120    /// Return keyorigin and xpub, like "[73c5da0a/84h/1h/0h]tpub..."
121    pub fn keyorigin_xpub(&self, bip: &Bip) -> Result<String, LwkError> {
122        let is_mainnet = lwk_common::Signer::is_mainnet(&self.inner)?;
123        Ok(lwk_common::Signer::keyorigin_xpub(
124            &self.inner,
125            bip.inner,
126            is_mainnet,
127        )?)
128    }
129
130    /// Return the signer fingerprint
131    pub fn fingerprint(&self) -> Result<String, LwkError> {
132        Ok(self.inner.fingerprint().to_string())
133    }
134
135    /// Get the mnemonic of the signer
136    pub fn mnemonic(&self) -> Result<Arc<Mnemonic>, LwkError> {
137        Ok(Arc::new(self.inner.mnemonic().map(Into::into).ok_or_else(
138            || LwkError::Generic {
139                msg: "Mnemonic not available".to_string(),
140            },
141        )?))
142    }
143
144    /// Derive a BIP85 mnemonic from this signer
145    ///
146    /// # Arguments
147    /// * `index` - The index for the derived mnemonic (0-based)
148    /// * `word_count` - The number of words in the derived mnemonic (12 or 24)
149    ///
150    /// # Returns
151    /// * `Ok(Mnemonic)` - The derived BIP85 mnemonic
152    /// * `Err(LwkError)` - If BIP85 derivation fails
153    ///
154    /// # Example
155    /// ```python
156    /// signer = Signer.new(mnemonic, network)
157    /// derived_mnemonic = signer.derive_bip85_mnemonic(0, 12)
158    /// ```
159    pub fn derive_bip85_mnemonic(
160        &self,
161        index: u32,
162        word_count: u32,
163    ) -> Result<Arc<Mnemonic>, LwkError> {
164        let derived_mnemonic = self.inner.derive_bip85_mnemonic(index, word_count)?;
165        Ok(Arc::new(derived_mnemonic.into()))
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use lwk_wollet::ElementsNetwork;
172
173    use crate::{Bip, Mnemonic, Pset, Signer};
174
175    #[test]
176    fn signer() {
177        let mnemonic_str = lwk_test_util::TEST_MNEMONIC;
178        let mnemonic = Mnemonic::new(mnemonic_str).unwrap();
179        let network: crate::Network = ElementsNetwork::default_regtest().into();
180
181        let signer = Signer::new(&mnemonic, &network).unwrap();
182
183        let pset_string =
184            include_str!("../../lwk_jade/test_data/pset_to_be_signed.base64").to_string();
185        let pset = Pset::new(&pset_string).unwrap();
186
187        let signed_pset = signer.sign(&pset).unwrap();
188
189        assert_ne!(pset, signed_pset);
190
191        let xpub = signer.keyorigin_xpub(&Bip::new_bip49()).unwrap();
192        let expected = "[73c5da0a/49h/1h/0h]tpubDD7tXK8KeQ3YY83yWq755fHY2JW8Ha8Q765tknUM5rSvjPcGWfUppDFMpQ1ScziKfW3ZNtZvAD7M3u7bSs7HofjTD3KP3YxPK7X6hwV8Rk2";
193        assert_eq!(xpub, expected);
194
195        let xpub = signer.keyorigin_xpub(&Bip::new_bip84()).unwrap();
196        let expected = "[73c5da0a/84h/1h/0h]tpubDC8msFGeGuwnKG9Upg7DM2b4DaRqg3CUZa5g8v2SRQ6K4NSkxUgd7HsL2XVWbVm39yBA4LAxysQAm397zwQSQoQgewGiYZqrA9DsP4zbQ1M";
197        assert_eq!(xpub, expected);
198
199        let xpub = signer.keyorigin_xpub(&Bip::new_bip87()).unwrap();
200        let expected = "[73c5da0a/87h/1h/0h]tpubDCChhoz5Qdrkn7Z7KXawq6Ad6r3A4MUkCoVTqeWxfTkA6bHNJ3CHUEtALQdkNeixNz4446PcAmw4WKcj3mV2vb29H7sg9EPzbyCU1y2merw";
201        assert_eq!(xpub, expected);
202
203        assert_eq!(signer.mnemonic().unwrap(), mnemonic);
204
205        assert_eq!(signer.fingerprint().unwrap(), xpub[1..9])
206    }
207
208    #[test]
209    fn test_bip85_derivation() {
210        let mnemonic_str = lwk_test_util::TEST_MNEMONIC;
211        let mnemonic = Mnemonic::new(mnemonic_str).unwrap();
212        let network: crate::Network = ElementsNetwork::default_regtest().into();
213
214        let signer = Signer::new(&mnemonic, &network).unwrap();
215
216        // Test BIP85 derivation with 12 words
217        let derived_mnemonic_12 = signer.derive_bip85_mnemonic(0, 12).unwrap();
218        assert_eq!(derived_mnemonic_12.word_count(), 12);
219        let expected_mnemonic_12 =
220            "prosper short ramp prepare exchange stove life snack client enough purpose fold";
221        assert_eq!(derived_mnemonic_12.to_string(), expected_mnemonic_12);
222
223        // Test BIP85 derivation with 24 words
224        let derived_mnemonic_24 = signer.derive_bip85_mnemonic(0, 24).unwrap();
225        assert_eq!(derived_mnemonic_24.word_count(), 24);
226        let expected_mnemonic_24 = "stick exact spice sock filter ginger museum horse kit multiply manual wear grief demand derive alert quiz fault december lava picture immune decade jaguar";
227        assert_eq!(derived_mnemonic_24.to_string(), expected_mnemonic_24);
228
229        // Test that different indices produce different mnemonics
230        let derived_mnemonic_1 = signer.derive_bip85_mnemonic(1, 12).unwrap();
231        assert_ne!(
232            derived_mnemonic_12.to_string(),
233            derived_mnemonic_1.to_string()
234        );
235
236        // Test that the same index produces the same mnemonic
237        let derived_mnemonic_0_again = signer.derive_bip85_mnemonic(0, 12).unwrap();
238        assert_eq!(
239            derived_mnemonic_12.to_string(),
240            derived_mnemonic_0_again.to_string()
241        );
242    }
243}