Skip to content

Commit b32c29f

Browse files
committed
Return an error from NodeEntropy::from_bip39_mnemonic in bindings
The uniffi-exposed `from_bip39_mnemonic` constructor was infallible and took the `Mnemonic` custom type, which parses the string during argument lifting. An invalid mnemonic would fail the lift and surface as an unexpected-error call status. The generated Swift wrapper for an infallible function wraps the call in `try!`, so passing an invalid mnemonic (e.g., a user typo during wallet restore) aborts the process with an uncatchable EXC_BREAKPOINT. Kotlin and Python raise their generic internal exceptions instead. Following the `from_seed_bytes` precedent, give the constructor a uniffi-specific signature that takes the mnemonic as a plain string and returns `Result<NodeEntropy, EntropyError>`, parsing inside the function and reporting failures via a new `EntropyError::InvalidMnemonic` variant. The non-uniffi Rust API is unchanged.
1 parent aa6d051 commit b32c29f

2 files changed

Lines changed: 43 additions & 4 deletions

File tree

src/entropy.rs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ use crate::io;
1919
#[derive(Debug, Clone, PartialEq)]
2020
#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
2121
pub enum EntropyError {
22+
/// The given BIP 39 mnemonic is invalid.
23+
InvalidMnemonic,
2224
/// The given seed bytes are invalid, e.g., have invalid length.
2325
InvalidSeedBytes,
2426
/// The given seed file is invalid, e.g., has invalid length, or could not be read.
@@ -28,6 +30,7 @@ pub enum EntropyError {
2830
impl fmt::Display for EntropyError {
2931
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3032
match *self {
33+
Self::InvalidMnemonic => write!(f, "Given BIP 39 mnemonic is invalid."),
3134
Self::InvalidSeedBytes => write!(f, "Given seed bytes are invalid."),
3235
Self::InvalidSeedFile => write!(f, "Given seed file is invalid or could not be read."),
3336
}
@@ -45,6 +48,18 @@ impl std::error::Error for EntropyError {}
4548
pub struct NodeEntropy([u8; WALLET_KEYS_SEED_LEN]);
4649

4750
impl NodeEntropy {
51+
/// Configures the [`Node`] instance to source its wallet entropy from a [BIP 39] mnemonic.
52+
///
53+
/// [BIP 39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
54+
/// [`Node`]: crate::Node
55+
#[cfg(not(feature = "uniffi"))]
56+
pub fn from_bip39_mnemonic(mnemonic: Mnemonic, passphrase: Option<String>) -> Self {
57+
match passphrase {
58+
Some(passphrase) => Self(mnemonic.to_seed(passphrase)),
59+
None => Self(mnemonic.to_seed("")),
60+
}
61+
}
62+
4863
/// Configures the [`Node`] instance to source its wallet entropy from the given
4964
/// [`WALLET_KEYS_SEED_LEN`] seed bytes.
5065
///
@@ -63,13 +78,19 @@ impl NodeEntropy {
6378
impl NodeEntropy {
6479
/// Configures the [`Node`] instance to source its wallet entropy from a [BIP 39] mnemonic.
6580
///
81+
/// Will return an error if the given mnemonic is invalid.
82+
///
6683
/// [BIP 39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
6784
/// [`Node`]: crate::Node
68-
#[cfg_attr(feature = "uniffi", uniffi::constructor)]
69-
pub fn from_bip39_mnemonic(mnemonic: Mnemonic, passphrase: Option<String>) -> Self {
85+
#[cfg(feature = "uniffi")]
86+
#[uniffi::constructor]
87+
pub fn from_bip39_mnemonic(
88+
mnemonic: String, passphrase: Option<String>,
89+
) -> Result<NodeEntropy, EntropyError> {
90+
let mnemonic = Mnemonic::parse(&mnemonic).map_err(|_| EntropyError::InvalidMnemonic)?;
7091
match passphrase {
71-
Some(passphrase) => Self(mnemonic.to_seed(passphrase)),
72-
None => Self(mnemonic.to_seed("")),
92+
Some(passphrase) => Ok(Self(mnemonic.to_seed(passphrase))),
93+
None => Ok(Self(mnemonic.to_seed(""))),
7394
}
7495
}
7596

@@ -166,6 +187,21 @@ impl WordCount {
166187
mod tests {
167188
use super::*;
168189

190+
#[cfg(feature = "uniffi")]
191+
#[test]
192+
fn invalid_mnemonic_returns_error() {
193+
// A bad checksum must yield an error rather than an argument-lift panic that
194+
// aborts the process in the Swift bindings.
195+
let invalid = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon";
196+
assert_eq!(
197+
NodeEntropy::from_bip39_mnemonic(invalid.to_string(), None).err(),
198+
Some(EntropyError::InvalidMnemonic)
199+
);
200+
201+
let valid = generate_entropy_mnemonic(None);
202+
assert!(NodeEntropy::from_bip39_mnemonic(valid.to_string(), None).is_ok());
203+
}
204+
169205
#[test]
170206
fn mnemonic_to_entropy_to_mnemonic() {
171207
// Test default (24 words)

tests/common/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,9 @@ impl Default for TestConfig {
591591
let store_type = Default::default();
592592

593593
let mnemonic = generate_entropy_mnemonic(None);
594+
#[cfg(feature = "uniffi")]
595+
let node_entropy = NodeEntropy::from_bip39_mnemonic(mnemonic.to_string(), None).unwrap();
596+
#[cfg(not(feature = "uniffi"))]
594597
let node_entropy = NodeEntropy::from_bip39_mnemonic(mnemonic, None);
595598
let async_payments_role = None;
596599
let wallet_rescan_from_height = None;

0 commit comments

Comments
 (0)