Skip to content

Commit ec9567f

Browse files
committed
Use associated mnemonic generation
Mnemonic generation no longer needs a separate global entry point now that bindings expose a real mnemonic object. Use numeric word counts for both native and binding constructors so the APIs stay aligned without a binding-specific enum. Co-Authored-By: HAL 9000
1 parent 6d9828f commit ec9567f

10 files changed

Lines changed: 49 additions & 125 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
mnemonic phrases must be passed through its fallible constructor, which returns
66
`NodeError::InvalidMnemonic` for invalid input; generated mnemonics can be converted back to a
77
string through their language's standard string conversion.
8+
- `generate_entropy_mnemonic` has been removed. Use `bip39::Mnemonic::generate` in Rust and
9+
`Mnemonic::generate` in the language bindings instead.
810
- Migrating between storage backends does not preserve the relative creation order of
911
pre-existing payments, as the generic KV store migration copies entries in an unspecified
1012
order. Expect the order in which `Node::list_payments` returns pre-existing payments to

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ The primary abstraction of the library is the [`Node`][api_docs_node], which can
1717
```rust
1818
use ldk_node::bitcoin::secp256k1::PublicKey;
1919
use ldk_node::bitcoin::Network;
20-
use ldk_node::entropy::{generate_entropy_mnemonic, NodeEntropy};
20+
use ldk_node::bip39::Mnemonic;
21+
use ldk_node::entropy::NodeEntropy;
2122
use ldk_node::lightning::ln::msgs::SocketAddress;
2223
use ldk_node::lightning_invoice::Bolt11Invoice;
2324
use ldk_node::Builder;
@@ -32,7 +33,7 @@ fn main() {
3233
);
3334

3435

35-
let mnemonic = generate_entropy_mnemonic(None);
36+
let mnemonic = Mnemonic::generate(24).unwrap();
3637
let node_entropy = NodeEntropy::from_bip39_mnemonic(mnemonic, None);
3738
let node = builder.build(node_entropy).unwrap();
3839

bindings/kotlin/ldk-node-android/lib/src/androidTest/kotlin/org/lightningdevkit/ldknode/AndroidLibTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ class AndroidLibTest {
3434
val builder1 = Builder.fromConfig(config1)
3535
val builder2 = Builder.fromConfig(config2)
3636

37-
val mnemonic1 = generateEntropyMnemonic(null)
37+
val mnemonic1 = Mnemonic.generate(24u)
3838
val nodeEntropy1 = NodeEntropy.fromBip39Mnemonic(mnemonic1, null)
3939
val node1 = builder1.build(nodeEntropy1)
4040

41-
val mnemonic2 = generateEntropyMnemonic(null)
41+
val mnemonic2 = Mnemonic.generate(24u)
4242
val nodeEntropy2 = NodeEntropy.fromBip39Mnemonic(mnemonic2, null)
4343
val node2 = builder2.build(nodeEntropy2)
4444

bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,11 +207,11 @@ class LibraryTest {
207207
builder2.setChainSourceEsplora(esploraEndpoint, null)
208208
builder2.setCustomLogger(logWriter2)
209209

210-
val mnemonic1 = generateEntropyMnemonic(null)
210+
val mnemonic1 = Mnemonic.generate(24u)
211211
val nodeEntropy1 = NodeEntropy.fromBip39Mnemonic(mnemonic1, null)
212212
val node1 = builder1.build(nodeEntropy1)
213213

214-
val mnemonic2 = generateEntropyMnemonic(null)
214+
val mnemonic2 = Mnemonic.generate(24u)
215215
val nodeEntropy2 = NodeEntropy.fromBip39Mnemonic(mnemonic2, null)
216216
val node2 = builder2.build(nodeEntropy2)
217217

bindings/ldk_node.udl

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
namespace ldk_node {
2-
Mnemonic generate_entropy_mnemonic(WordCount? word_count);
32
Config default_config();
43
};
54

@@ -15,8 +14,6 @@ typedef interface NodeEntropy;
1514

1615
typedef interface ProbingConfig;
1716

18-
typedef enum WordCount;
19-
2017
[Remote]
2118
enum LogLevel {
2219
"Gossip",

bindings/python/src/ldk_node/test_ldk_node.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import socket
99

1010
from ldk_node import *
11-
from ldk_node import ldk_node as bindings
1211

1312
DEFAULT_ESPLORA_SERVER_URL = "http://127.0.0.1:3002"
1413
DEFAULT_TEST_NETWORK = Network.REGTEST
@@ -99,7 +98,7 @@ def send_to_address(address, amount_sats):
9998

10099

101100
def setup_node(tmp_dir, esplora_endpoint, listening_addresses):
102-
mnemonic = generate_entropy_mnemonic(None)
101+
mnemonic = Mnemonic.generate(24)
103102
node_entropy = NodeEntropy.from_bip39_mnemonic(mnemonic, None)
104103
config = default_config()
105104
builder = Builder.from_config(config)
@@ -209,24 +208,24 @@ def init_features_exposed(test_case, init_features):
209208
class TestMnemonic(unittest.TestCase):
210209
def test_invalid_mnemonic_returns_node_error(self):
211210
invalid_mnemonic = "abandon " * 11 + "abandon"
212-
mnemonic_constructor = getattr(bindings.Mnemonic, "from_str", bindings.Mnemonic)
211+
mnemonic_constructor = getattr(Mnemonic, "from_str", Mnemonic)
213212

214213
with self.assertRaises(NodeError) as error:
215214
mnemonic_constructor(invalid_mnemonic)
216215

217216
self.assertIsInstance(error.exception, NodeError.InvalidMnemonic)
218217

219218
def test_mnemonic_round_trip(self):
220-
mnemonic = generate_entropy_mnemonic(None)
221-
parsed_mnemonic = bindings.Mnemonic.from_str(str(mnemonic))
219+
mnemonic = Mnemonic.generate(24)
220+
parsed_mnemonic = Mnemonic.from_str(str(mnemonic))
222221

223-
self.assertIsInstance(mnemonic, bindings.Mnemonic)
222+
self.assertIsInstance(mnemonic, Mnemonic)
224223
self.assertEqual(parsed_mnemonic, mnemonic)
225224
self.assertIsInstance(NodeEntropy.from_bip39_mnemonic(parsed_mnemonic, None), NodeEntropy)
226225

227226
def test_mnemonic_functionality(self):
228227
entropy = bytes(16)
229-
mnemonic = bindings.Mnemonic.from_entropy(entropy)
228+
mnemonic = Mnemonic.from_entropy(entropy)
230229

231230
self.assertEqual(mnemonic.words(), ["abandon"] * 11 + ["about"])
232231
self.assertEqual(mnemonic.word_indices(), [0] * 11 + [3])
@@ -238,10 +237,15 @@ def test_mnemonic_functionality(self):
238237
"c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e5349553"
239238
"1f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04",
240239
)
241-
self.assertEqual(bindings.Mnemonic.generate(WordCount.WORDS12).word_count(), 12)
240+
self.assertEqual(Mnemonic.generate(12).word_count(), 12)
242241

243242
with self.assertRaises(NodeError) as error:
244-
bindings.Mnemonic.from_entropy(bytes(15))
243+
Mnemonic.generate(13)
244+
245+
self.assertIsInstance(error.exception, NodeError.InvalidMnemonic)
246+
247+
with self.assertRaises(NodeError) as error:
248+
Mnemonic.from_entropy(bytes(15))
245249

246250
self.assertIsInstance(error.exception, NodeError.InvalidMnemonic)
247251

src/entropy.rs

Lines changed: 2 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,12 @@
99
1010
use std::fmt;
1111

12-
use bip39::rand::rngs::OsRng;
13-
use bip39::{Language, Mnemonic as Bip39Mnemonic};
14-
1512
use crate::config::WALLET_KEYS_SEED_LEN;
16-
use crate::ffi::{maybe_deref, maybe_wrap};
13+
use crate::ffi::maybe_deref;
1714
use crate::io;
1815

1916
#[cfg(not(feature = "uniffi"))]
20-
type Mnemonic = Bip39Mnemonic;
17+
type Mnemonic = bip39::Mnemonic;
2118
#[cfg(feature = "uniffi")]
2219
type Mnemonic = std::sync::Arc<crate::ffi::Mnemonic>;
2320

@@ -124,89 +121,3 @@ impl fmt::Debug for NodeEntropy {
124121
write!(f, "NODE ENTROPY")
125122
}
126123
}
127-
128-
/// Generates a random [BIP 39] mnemonic with the specified word count.
129-
///
130-
/// If no word count is specified, defaults to 24 words (256-bit entropy).
131-
///
132-
/// The result may be used to initialize the [`NodeEntropy`], i.e., can be given to
133-
/// [`NodeEntropy::from_bip39_mnemonic`].
134-
///
135-
/// [BIP 39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
136-
/// [`Node`]: crate::Node
137-
pub fn generate_entropy_mnemonic(word_count: Option<WordCount>) -> Mnemonic {
138-
let word_count = word_count.unwrap_or(WordCount::Words24).word_count();
139-
let mnemonic = Bip39Mnemonic::generate_in_with(&mut OsRng, Language::English, word_count)
140-
.expect("Failed to generate mnemonic");
141-
maybe_wrap(mnemonic)
142-
}
143-
144-
/// Supported BIP39 mnemonic word counts for entropy generation.
145-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146-
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
147-
pub enum WordCount {
148-
/// 12-word mnemonic (128-bit entropy)
149-
Words12,
150-
/// 15-word mnemonic (160-bit entropy)
151-
Words15,
152-
/// 18-word mnemonic (192-bit entropy)
153-
Words18,
154-
/// 21-word mnemonic (224-bit entropy)
155-
Words21,
156-
/// 24-word mnemonic (256-bit entropy)
157-
Words24,
158-
}
159-
160-
impl WordCount {
161-
/// Returns the word count as a usize value.
162-
pub fn word_count(&self) -> usize {
163-
match self {
164-
WordCount::Words12 => 12,
165-
WordCount::Words15 => 15,
166-
WordCount::Words18 => 18,
167-
WordCount::Words21 => 21,
168-
WordCount::Words24 => 24,
169-
}
170-
}
171-
}
172-
173-
#[cfg(test)]
174-
mod tests {
175-
use super::*;
176-
177-
#[test]
178-
fn mnemonic_to_entropy_to_mnemonic() {
179-
// Test default (24 words)
180-
let mnemonic = generate_entropy_mnemonic(None);
181-
let mnemonic_inner = maybe_deref(&mnemonic);
182-
let entropy = mnemonic_inner.to_entropy();
183-
assert_eq!(mnemonic_inner, &Bip39Mnemonic::from_entropy(&entropy).unwrap());
184-
assert_eq!(mnemonic_inner.word_count(), 24);
185-
186-
// Test with different word counts
187-
let word_counts = [
188-
WordCount::Words12,
189-
WordCount::Words15,
190-
WordCount::Words18,
191-
WordCount::Words21,
192-
WordCount::Words24,
193-
];
194-
195-
for word_count in word_counts {
196-
let mnemonic = generate_entropy_mnemonic(Some(word_count));
197-
let mnemonic_inner = maybe_deref(&mnemonic);
198-
let entropy = mnemonic_inner.to_entropy();
199-
assert_eq!(mnemonic_inner, &Bip39Mnemonic::from_entropy(&entropy).unwrap());
200-
201-
// Verify expected word count
202-
let expected_words = match word_count {
203-
WordCount::Words12 => 12,
204-
WordCount::Words15 => 15,
205-
WordCount::Words18 => 18,
206-
WordCount::Words21 => 21,
207-
WordCount::Words24 => 24,
208-
};
209-
assert_eq!(mnemonic_inner.word_count(), expected_words);
210-
}
211-
}
212-
}

src/ffi/types.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ impl VssClientHeaderProvider for VssHeaderProviderAdapter {
151151

152152
use crate::builder::sanitize_alias;
153153
pub use crate::config::{default_config, ElectrumSyncConfig, EsploraSyncConfig, TorConfig};
154-
pub use crate::entropy::{generate_entropy_mnemonic, NodeEntropy, WordCount};
154+
pub use crate::entropy::NodeEntropy;
155155
use crate::error::Error;
156156
pub use crate::liquidity::LSPS1OrderStatus;
157157
pub use crate::logger::{LogLevel, LogRecord, LogWriter};
@@ -1175,14 +1175,11 @@ impl Mnemonic {
11751175
}
11761176

11771177
/// Generates a random English mnemonic with the specified word count.
1178-
///
1179-
/// Defaults to 24 words when no word count is specified.
11801178
#[uniffi::constructor]
1181-
pub fn generate(word_count: Option<WordCount>) -> Self {
1182-
let word_count = word_count.unwrap_or(WordCount::Words24).word_count();
1183-
let inner = Bip39Mnemonic::generate(word_count)
1184-
.expect("WordCount always maps to a valid BIP 39 word count");
1185-
Self { inner }
1179+
pub fn generate(word_count: u8) -> Result<Self, Error> {
1180+
Bip39Mnemonic::generate(word_count.into())
1181+
.map(Self::from)
1182+
.map_err(|_| Error::InvalidMnemonic)
11861183
}
11871184

11881185
/// Returns the words in the mnemonic.
@@ -3009,7 +3006,8 @@ mod tests {
30093006
assert_eq!(mnemonic.to_entropy(), entropy);
30103007
assert_eq!(mnemonic.checksum(), 3);
30113008
assert_eq!(mnemonic.to_seed("TREZOR").len(), 64);
3012-
assert_eq!(Mnemonic::generate(Some(WordCount::Words12)).word_count(), 12);
3009+
assert_eq!(Mnemonic::generate(12).unwrap().word_count(), 12);
3010+
assert_eq!(Mnemonic::generate(13), Err(Error::InvalidMnemonic));
30133011
assert_eq!(Mnemonic::from_entropy(&[0; 15]), Err(Error::InvalidMnemonic));
30143012
}
30153013
}

src/lib.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
//!
3030
//! use ldk_node::bitcoin::secp256k1::PublicKey;
3131
//! use ldk_node::bitcoin::Network;
32-
//! use ldk_node::entropy::{generate_entropy_mnemonic, NodeEntropy};
32+
//! use ldk_node::bip39::Mnemonic;
33+
//! use ldk_node::entropy::NodeEntropy;
3334
//! use ldk_node::lightning::ln::msgs::SocketAddress;
3435
//! use ldk_node::lightning_invoice::Bolt11Invoice;
3536
//! use ldk_node::Builder;
@@ -42,7 +43,7 @@
4243
//! "https://rapidsync.lightningdevkit.org/testnet/v2/snapshot".to_string(),
4344
//! );
4445
//!
45-
//! let mnemonic = generate_entropy_mnemonic(None);
46+
//! let mnemonic = Mnemonic::generate(24).unwrap();
4647
//! let node_entropy = NodeEntropy::from_bip39_mnemonic(mnemonic, None);
4748
//! let node = builder.build(node_entropy).unwrap();
4849
//!
@@ -2212,11 +2213,14 @@ impl Node {
22122213
///
22132214
/// For example, you could retrieve all stored outbound payments as follows:
22142215
/// ```
2216+
/// # #[cfg(not(feature = "uniffi"))]
2217+
/// # fn main() -> Result<(), ldk_node::NodeError> {
22152218
/// # use ldk_node::Builder;
22162219
/// # use ldk_node::config::Config;
22172220
/// # use ldk_node::payment::{PaymentDetails, PaymentDirection};
22182221
/// # use ldk_node::bitcoin::Network;
2219-
/// # use ldk_node::entropy::{generate_entropy_mnemonic, NodeEntropy};
2222+
/// # use ldk_node::bip39::Mnemonic;
2223+
/// # use ldk_node::entropy::NodeEntropy;
22202224
/// # use rand::distr::Alphanumeric;
22212225
/// # use rand::{rng, Rng};
22222226
/// # let mut config = Config::default();
@@ -2226,7 +2230,7 @@ impl Node {
22262230
/// # temp_path.push(rand_dir);
22272231
/// # config.storage_dir_path = temp_path.display().to_string();
22282232
/// # let builder = Builder::from_config(config);
2229-
/// # let mnemonic = generate_entropy_mnemonic(None);
2233+
/// # let mnemonic = Mnemonic::generate(24).unwrap();
22302234
/// # let node_entropy = NodeEntropy::from_bip39_mnemonic(mnemonic, None);
22312235
/// # let node = builder.build(node_entropy.into()).unwrap();
22322236
/// let mut outbound = Vec::new();
@@ -2241,7 +2245,10 @@ impl Node {
22412245
/// None => break,
22422246
/// }
22432247
/// }
2244-
/// # Ok::<(), ldk_node::NodeError>(())
2248+
/// # Ok(())
2249+
/// # }
2250+
/// # #[cfg(feature = "uniffi")]
2251+
/// # fn main() {}
22452252
/// ```
22462253
pub fn list_payments(
22472254
&self, page_token: Option<payment::PageToken>,

tests/common/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ use bitcoin::{
3737
use electrsd::corepc_node::{Client as BitcoindClient, Node as BitcoinD};
3838
use electrsd::electrum_client::ElectrumApi;
3939
use electrsd::{corepc_node, ElectrsD};
40+
use ldk_node::bip39::Mnemonic;
4041
use ldk_node::config::{
4142
AsyncPaymentsRole, Config, ElectrumSyncConfig, EsploraSyncConfig, HRNResolverConfig,
4243
HumanReadableNamesConfig,
4344
};
44-
use ldk_node::entropy::{generate_entropy_mnemonic, NodeEntropy};
45+
use ldk_node::entropy::NodeEntropy;
4546
use ldk_node::io::sqlite_store::SqliteStore;
4647
use ldk_node::payment::{
4748
PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, TransactionType,
@@ -644,8 +645,11 @@ impl Default for TestConfig {
644645
let log_writer = Default::default();
645646
let store_type = Default::default();
646647

647-
let mnemonic = generate_entropy_mnemonic(None);
648+
let mnemonic = Mnemonic::generate(24).unwrap();
649+
#[cfg(not(feature = "uniffi"))]
648650
let node_entropy = NodeEntropy::from_bip39_mnemonic(mnemonic, None);
651+
#[cfg(feature = "uniffi")]
652+
let node_entropy = NodeEntropy::from_seed_bytes(mnemonic.to_seed("").to_vec()).unwrap();
649653
let async_payments_role = None;
650654
let wallet_rescan_from_height = None;
651655
let force_wallet_full_scan = false;

0 commit comments

Comments
 (0)