• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

libbitcoin / libbitcoin-system / 22832990031

09 Mar 2026 12:06AM UTC coverage: 81.289% (+0.07%) from 81.216%
22832990031

Pull #1791

github

web-flow
Merge 3697e6efc into 032c34e64
Pull Request #1791: [RFC] Replace ICU dependency with embedded Unicode tables

139 of 150 new or added lines in 1 file covered. (92.67%)

30 existing lines in 3 files now uncovered.

10987 of 13516 relevant lines covered (81.29%)

3503968.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.89
/src/wallet/mnemonics/electrum.cpp
1
/**
2
 * Copyright (c) 2011-2026 libbitcoin developers (see AUTHORS)
3
 *
4
 * This file is part of libbitcoin.
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Affero General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU Affero General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Affero General Public License
17
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
 */
19
#include <bitcoin/system/wallet/mnemonics/electrum.hpp>
20

21
#include <bitcoin/system/crypto/crypto.hpp>
22
#include <bitcoin/system/data/data.hpp>
23
#include <bitcoin/system/hash/hash.hpp>
24
#include <bitcoin/system/math/math.hpp>
25
#include <bitcoin/system/radix/radix.hpp>
26
#include <bitcoin/system/unicode/unicode.hpp>
27
#include <bitcoin/system/wallet/context.hpp>
28
#include <bitcoin/system/wallet/keys/ec_private.hpp>
29
#include <bitcoin/system/wallet/keys/hd_private.hpp>
30
#include <bitcoin/system/wallet/mnemonics/electrum_v1.hpp>
31
#include <bitcoin/system/words/words.hpp>
32

33
namespace libbitcoin {
34
namespace system {
35
namespace wallet {
36

37
// local constants
38
// ----------------------------------------------------------------------------
39

40
// Seed prefixes.
41
// The values for old, bip39 and none are not actual prefixes but included
42
// here for consistency when handling electrum exceptional conditions.
43
// The values are selected such that they contain invalid hexidecimal
44
// characters and therefore cannot match an actual seed prefix.
45
static const auto version_old = "old";
46
static const auto version_bip39 = "bip39";
47
static const auto version_standard = "01";
48
static const auto version_witness = "100";
49
static const auto version_two_factor_authentication = "101";
50
static const auto version_two_factor_authentication_witness = "102";
51
static const auto version_none = "none";
52

53
// 2^11 = 2048 implies 11 bits exactly indexes every possible dictionary word.
54
static const auto index_bits = narrow_cast<uint8_t>(floored_log2(
55
    electrum::dictionary::size()));
56

57
// private static
58
// ----------------------------------------------------------------------------
59

60
const electrum::dictionaries electrum::dictionaries_
61
{
62
    {
63
        electrum::dictionary{ language::en, words::electrum::en },
64
        electrum::dictionary{ language::es, words::electrum::es },
65
        electrum::dictionary{ language::it, words::electrum::it },
66
        electrum::dictionary{ language::fr, words::electrum::fr },
67
        electrum::dictionary{ language::cs, words::electrum::cs },
68
        electrum::dictionary{ language::pt, words::electrum::pt },
69
        electrum::dictionary{ language::ja, words::electrum::ja },
70
        electrum::dictionary{ language::ko, words::electrum::ko },
71
        electrum::dictionary{ language::zh_Hans, words::electrum::zh_Hans },
72
        electrum::dictionary{ language::zh_Hant, words::electrum::zh_Hant }
73
    }
74
};
75

76
// protected static (coders)
77
// ----------------------------------------------------------------------------
78

79
// Electrum entropy is an entirely private (internal) format.
80
string_list electrum::encoder(const data_chunk& entropy,
11,187✔
81
    language identifier) NOEXCEPT
82
{
83
    // Bytes are the base2048 encoding, so this is byte decoding.
84
    return decode_base2048_list(entropy, identifier);
2✔
85
}
86

87
// Electrum entropy is an entirely private (internal) format.
88
data_chunk electrum::decoder(const string_list& words,
69✔
89
    language identifier) NOEXCEPT
90
{
91
    // Words are the base2048 decoding, so this is word encoding.
92
    data_chunk out;
69✔
93
    return encode_base2048_list(out, words, identifier) ? out : data_chunk{};
69✔
94
}
95

96
// Electrum also grinds randoms on a sequential nonce until the entropy is high
97
// enough to ensure population of words that represent the intended bit
98
// strength of the seed. But we do not use an internal PRNG and instead
99
// only accept base entropy from the caller. We want determinism from the
100
// entropy, and we cannot truly increase randomness. So we maintain entropy
101
// strength by using full provided entropy and produce exact word count.
102
// This is done by iterating a hash function so an arbitrary limit is required.
103
// The Electrum prng minimum value technique sacrifices 11 bits of entropy by
104
// discarding any prng value that is below 2^(strength-11).
105
// github.com/spesmilo/electrum/blob/master/electrum/mnemonic.py#L190-L205
106
electrum::grinding electrum::grinder(const data_chunk& entropy,
26✔
107
    seed_prefix prefix, language identifier, size_t limit) NOEXCEPT
108
{
109
    string_list words;
26✔
110
    data_chunk hash(entropy);
26✔
111
    const auto start = limit;
26✔
112

113
    // Remove unusable entropy bytes.
114
    const auto entropy_size = usable_size(hash);
26✔
115
    hash.resize(entropy_size);
26✔
116

117
    // Create a byte mask for zeroizing entropy pad bits.
118
    const auto padding_mask = 0xff << unused_bits(hash);
26✔
119

120
    // This just grinds away until exhausted or prefix found.
121
    // Previously-discovered entropy round-trips, matching on the first pass.
122
    do
11,185✔
123
    {
124
        // Normalize entropy to the wordlist by managing its pad bits.
125
        // Electrum pads to the left, but entropy is a private format for
126
        // electrum and public for bip39, so we use the bip39/mnemonic format.
127
        // This results in any electrum entropy/prefix value producing the same
128
        // words as that same entropy/checksum value in bip39/mnemonic.
129
        hash[sub1(entropy_size)] &= padding_mask;
11,185✔
130
        words = encoder(hash, identifier);
11,185✔
131

132
        // Avoid collisions with Electrum v1 (en) and BIP39 mnemonics.
133
        // Run validator first because conflict checks can be costly.
134
        if (validator(words, prefix) && !is_conflict(words))
11,185✔
135
            return { hash, words, start - limit };
44✔
136

137
        // This replaces Electrum's prng with determinism.
138
        hash = to_chunk(sha512_hash(hash));
11,163✔
139
        hash.resize(entropy_size);
11,163✔
140
    }
141
    while (!is_zero(limit--));
11,163✔
142

143
    return { {}, {}, start };
8✔
144
}
52✔
145

146
// This cannot match electrum_v1 or mnemonic.
147
bool electrum::validator(const string_list& words, seed_prefix prefix) NOEXCEPT
11,359✔
148
{
149
    // Words are in normal (lower, nfkd) form, even without ICU.
150
    auto sentence = system::join(words);
11,359✔
151
    sentence = to_non_combining_form(sentence);
11,359✔
152
    sentence = to_compressed_form(sentence);
11,359✔
153

154
    const auto seed = hmac<sha512>::code(sentence, "Seed version");
11,359✔
155
    return starts_with(encode_base16(seed), to_version(prefix));
22,718✔
156
}
157

158
// Electrum uses the same normalization function for words and passphrases.
159
// Passpharse entropy loss from lowering (and normalizing) should be considered.
160
// github.com/spesmilo/electrum/blob/master/electrum/mnemonic.py#L77
161
long_hash electrum::seeder(const string_list& words,
15✔
162
    const std::string& passphrase) NOEXCEPT
163
{
164
    constexpr size_t hmac_iterations = 2048;
15✔
165
    constexpr auto passphrase_prefix = "electrum";
15✔
166

167
    // Passphrase is limited to ascii (normal) if HAVE_ICU undefined.
168
    std::string phrase{ passphrase };
15✔
169

170
    LCOV_EXCL_START("Always succeeds unless HAVE_ICU undefined.")
171

172
    // TODO: replace the ICU lib dependency with Python's unicodedata. 
173
    // Conforms to the Unicode Standard for nfkd and case lowering (ICU lib).
174
    // ------------------------------------------------------------------------
175
    // seed = unicodedata.normalize('NFKD', seed)
176
    // seed = seed.lower()
177
    // ------------------------------------------------------------------------
178
    // These can only return false if non-ascii phrase and HAVE_ICU undefined.
179
    if (!to_compatibility_decomposition(phrase) || !to_lower(phrase))
180
        return {};
181

182
    LCOV_EXCL_STOP()
183

184
    // Python's unicodedata.combining returns true if the character is of the
185
    // canonical combining class (which may or may not be a diacritic).
186
    // Libbitcoin uses Python's unicodedata implementation for consistency.
187
    // ------------------------------------------------------------------------
188
    // seed = u''.join([c for c in seed if not unicodedata.combining(c)])
189
    // ------------------------------------------------------------------------
190
    // Remove combining class characters (from the nfkd form converted above).
191
    phrase = to_non_combining_form(phrase);
15✔
192

193
    // Python string.whitespace represents the 6 ascii/C whitespace chars:
194
    // print(string.whitespace) => 09 0d 0a 0b 0c 0d 0a 20 (two duplicates).
195
    // Libbitcoin uses CJK intervals from Electrum sources for consistency.
196
    // ------------------------------------------------------------------------
197
    // seed = u''.join([seed[i] for i in range(len(seed))
198
    //    if not (seed[i] in string.whitespace and is_CJK(seed[i-1]) and
199
    //       is_CJK(seed[i+1]))])
200
    // ------------------------------------------------------------------------
201
    // Compress ascii whitespace and remove ascii spaces between cjk characters.
202
    phrase = to_compressed_form(phrase);
15✔
203

204
    // Python unicode splits on all unicode separators:
205
    // print(u'x\u3000y z'.split()) => [u'x', u'y', u'z']
206
    // Electrum joins with an ascii space (0x20) as confirmed by test results.
207
    // ------------------------------------------------------------------------
208
    // seed = u' '.join(seed.split())
209
    // ------------------------------------------------------------------------
210
    // Words are normal (lower, nfkd) form even without ICU (dictionary-match).
211
    auto sentence = system::join(words);
15✔
212
    sentence = to_non_combining_form(sentence);
15✔
213
    sentence = to_compressed_form(sentence);
15✔
214

215
    const auto data = to_chunk(sentence);
15✔
216
    const auto salt = to_chunk(passphrase_prefix + phrase);
15✔
217
    return pbkd<sha512>::key<long_hash_size>(data, salt, hmac_iterations);
15✔
218
}
219

220
// protected static (sizers)
221
// ----------------------------------------------------------------------------
222

223
size_t electrum::entropy_bits(const data_slice& entropy) NOEXCEPT
84✔
224
{
225
    // The number of bits for the given number of bytes.
226
    return to_bits(entropy.size());
84✔
227
}
228

229
size_t electrum::entropy_bits(const string_list& words) NOEXCEPT
8✔
230
{
231
    // The number of bits for the given number of words.
232
    return words.size() * index_bits;
8✔
233
}
234

235
size_t electrum::entropy_size(size_t bit_strength) NOEXCEPT
69✔
236
{
237
    // The required number of bytes to achieve the given bit strength.
238
    return ceilinged_divide(bit_strength, byte_bits);
69✔
239
}
240

241
size_t electrum::entropy_size(const string_list& words) NOEXCEPT
6✔
242
{
243
    // The required number of bytes for the given number of words.
244
    return ceilinged_divide(entropy_bits(words), byte_bits);
6✔
245
}
246

247
size_t electrum::word_count(const data_slice& entropy) NOEXCEPT
24✔
248
{
249
    // The number of words that can be derived from the given entropy size.
250
    return entropy_bits(entropy) / index_bits;
22✔
251
}
252

253
size_t electrum::word_count(size_t bit_strength) NOEXCEPT
215✔
254
{
255
    // The required number of words to achieve the given bit strength.
256
    return ceilinged_divide(bit_strength, index_bits);
215✔
257
}
258

259
uint8_t electrum::unused_bits(const data_slice& entropy) NOEXCEPT
58✔
260
{
261
    // 0..10 unused bits are possible given arbitrary entropy size.
262
    return entropy_bits(entropy) % index_bits;
26✔
263
}
264

265
uint8_t electrum::unused_bytes(const data_slice& entropy) NOEXCEPT
30✔
266
{
267
    // 0..10 unused bits implies we can discard up to one byte.
268
    return to_floored_bytes(unused_bits(entropy));
30✔
269
}
270

271
size_t electrum::usable_size(const data_slice& entropy) NOEXCEPT
28✔
272
{
273
    return entropy.size() - unused_bytes(entropy);
28✔
274
}
275

276
// protected static (checkers)
277
// ----------------------------------------------------------------------------
278

279
bool electrum::is_ambiguous(size_t count, seed_prefix prefix) NOEXCEPT
213✔
280
{
281
    constexpr size_t minimum_two_factor_authentication_words = 20;
213✔
282

283
    // HACK: github.com/spesmilo/electrum/blob/master/electrum/mnemonic.py#L258
284
    // In Electrum 2.7, there was a breaking change in key derivation for
285
    // two_factor_authentication. Unfortunately the seed prefix was reused,
286
    // and now we can only distinguish them based on number of words. :(
287
    if (prefix != seed_prefix::two_factor_authentication)
213✔
288
        return false;
289

290
    return
44✔
291
        count != word_count(strength_minimum) &&
44✔
292
        count < minimum_two_factor_authentication_words;
293
}
294

295
bool electrum::is_ambiguous(const string_list& words, language requested,
68✔
296
    language derived) NOEXCEPT
297
{
298
    // HACK: There are 100 same words in en/fr, all with distinct indexes.
299
    // If matches en and unspecified then check fr, since en is searched first.
300
    return
68✔
301
        derived == language::en &&
68✔
302
        requested == language::none &&
68✔
303
        contained_by(words, language::fr) == language::fr;
6✔
304
}
305

306
bool electrum::is_conflict(const string_list& words) NOEXCEPT
22✔
307
{
308
    return to_conflict(words) != seed_prefix::none;
22✔
309
}
310

311
electrum::seed_prefix electrum::to_conflict(const string_list& words) NOEXCEPT
89✔
312
{
313
    // Electrum only considers english for electrum_v1 conflicts.
314
    // Five electrum languages contain electrum_v1 english words.
315
    if (electrum_v1(words, language::en))
89✔
316
        return seed_prefix::old;
317

318
    // Generating a mnemonic checksum is unlikely but possible. But a generated
319
    // mnemonic (with valid checksum) can more easily match an electrum prefix.
320
    if (mnemonic(words))
89✔
321
        return seed_prefix::bip39;
1✔
322

323
    return seed_prefix::none;
324
}
325

326
bool electrum::normalized_is_prefix(const string_list& words,
181✔
327
    seed_prefix prefix) NOEXCEPT
328
{
329
    if (prefix == seed_prefix::old)
181✔
330
        return electrum_v1(words, language::en);
1✔
331

332
    if (prefix == seed_prefix::bip39)
180✔
333
        return mnemonic(words);
1✔
334

335
    // HACK: 2fa prefix ambiguity.
336
    if (is_ambiguous(words.size(), prefix))
179✔
337
        return false;
338

339
    if (validator(words, prefix))
169✔
340
        return true;
341

342
    return prefix == seed_prefix::none;
99✔
343
}
344

345
electrum::seed_prefix electrum::normalized_to_prefix(
67✔
346
    const string_list& words) NOEXCEPT
347
{
348
    const auto conflict = to_conflict(words);
67✔
349

350
    // Prioritize conflicts since electrum doesn't generate them.
351
    if (conflict != seed_prefix::none)
67✔
352
        return conflict;
353

354
    // Words are in normal form, even without ICU.
355
    if (normalized_is_prefix(words, seed_prefix::standard))
66✔
356
        return seed_prefix::standard;
357
    if (normalized_is_prefix(words, seed_prefix::witness))
49✔
358
        return seed_prefix::witness;
359
    if (normalized_is_prefix(words, seed_prefix::two_factor_authentication))
24✔
360
        return seed_prefix::two_factor_authentication;
361
    if (normalized_is_prefix(words, seed_prefix::two_factor_authentication_witness))
18✔
362
        return seed_prefix::two_factor_authentication_witness;
4✔
363

364
    return seed_prefix::none;
365
}
366

367
// public static
368
// ----------------------------------------------------------------------------
369

370
language electrum::contained_by(const string_list& words,
115✔
371
    language identifier) NOEXCEPT
372
{
373
    return dictionaries_.contains(words, identifier);
115✔
374
}
375

376
bool electrum::is_valid_dictionary(language identifier) NOEXCEPT
11✔
377
{
378
    return dictionaries_.exists(identifier);
11✔
379
}
380

381
bool electrum::is_valid_entropy_size(size_t size) NOEXCEPT
36✔
382
{
383
    // Electrum has no explicit minimum or maximum strengths.
384
    // Upper bounds here are based on use of a 512 bit hash function
385
    // in grind() and transportation of entropy in byte vectors.
386
    // A 64 byte seed (512 / 11) is 46 words (6 unused bits).
387
    // This limits strength to 506 bits (BIP39 is 256).
388
    // Lower bounds here are based on seed strength.
389
    // A 132 bit (exactly 12 word) seed is the Electrum default.
390
    // A 128 bit seed is the BIP39 minium, but this 11 Electrum words.
391
    // So our limits are 132 to 506 bits (12 to 46) words.
392

393
    return size >= entropy_size(strength_minimum) &&
36✔
394
        size <= entropy_size(strength_maximum);
31✔
395
}
396

397
bool electrum::is_valid_word_count(size_t count) NOEXCEPT
86✔
398
{
399
    return count >= word_count(strength_minimum) &&
86✔
400
        count <= word_count(strength_maximum);
81✔
401
}
402

403
bool electrum::is_seedable(seed_prefix prefix) NOEXCEPT
37✔
404
{
405
    // Only seed from native entropy.
406
    switch (prefix)
7✔
407
    {
408
        case seed_prefix::standard:
409
        case seed_prefix::witness:
410
        case seed_prefix::two_factor_authentication:
411
        case seed_prefix::two_factor_authentication_witness:
412
            return true;
413
        default:
5✔
414
            return false;
3✔
415
    }
416
}
417

418
bool electrum::is_prefix(const string_list& words, seed_prefix prefix) NOEXCEPT
25✔
419
{
420
    // Normalize to improve chance of dictionary matching.
421
    const auto tokens = try_normalize(words);
25✔
422

423
    // Tokens are accepted if they are contained by any single dictionary.
424
    if (contained_by(tokens) == language::none)
25✔
425
        return prefix == seed_prefix::none;
1✔
426

427
    return normalized_is_prefix(tokens, prefix);
24✔
428
}
25✔
429

430
bool electrum::is_prefix(const std::string& sentence,
4✔
431
    seed_prefix prefix) NOEXCEPT
432
{
433
    return is_prefix(split(sentence, language::none), prefix);
4✔
434
}
435

UNCOV
436
electrum::seed_prefix electrum::to_prefix(const string_list& words) NOEXCEPT
×
437
{
438
    // Normalize to improve chance of dictionary matching.
UNCOV
439
    const auto tokens = try_normalize(words);
×
440

441
    // Tokens are accepted if they are contained by any single dictionary.
UNCOV
442
    if (contained_by(tokens) == language::none)
×
443
        return seed_prefix::none;
444

445
    return normalized_to_prefix(words);
×
UNCOV
446
}
×
447

UNCOV
448
electrum::seed_prefix electrum::to_prefix(const std::string& sentence) NOEXCEPT
×
449
{
UNCOV
450
    return to_prefix(split(sentence, language::none));
×
451
}
452

453
std::string electrum::to_version(seed_prefix prefix) NOEXCEPT
11,366✔
454
{
455
    switch (prefix)
11,366✔
456
    {
457
        case seed_prefix::old:
1✔
458
            return version_old;
1✔
459
        case seed_prefix::bip39:
1✔
460
            return version_bip39;
1✔
461
        case seed_prefix::standard:
1,276✔
462
            return version_standard;
1,276✔
463
        case seed_prefix::witness:
63✔
464
            return version_witness;
63✔
465
        case seed_prefix::two_factor_authentication:
9,959✔
466
            return version_two_factor_authentication;
9,959✔
467
        case seed_prefix::two_factor_authentication_witness:
23✔
468
            return version_two_factor_authentication_witness;
23✔
469
        case seed_prefix::none:
43✔
470
        default:
43✔
471
            return version_none;
43✔
472
    }
473
}
474

475
// construction
476
// ----------------------------------------------------------------------------
477

478
electrum::electrum() NOEXCEPT
37✔
479
  : electrum_v1(), prefix_(seed_prefix::none)
37✔
480
{
481
}
37✔
482

483
electrum::electrum(const electrum_v1& old) NOEXCEPT
15✔
484
  : electrum_v1(old), prefix_(*this ? seed_prefix::old : seed_prefix::none)
15✔
485
{
486
}
15✔
487

488
electrum::electrum(const std::string& sentence, language identifier) NOEXCEPT
46✔
489
  : electrum(split(sentence, identifier), identifier)
46✔
490
{
491
}
46✔
492

493
electrum::electrum(const string_list& words, language identifier) NOEXCEPT
57✔
494
  : electrum(from_words(words, identifier))
57✔
495
{
496
}
57✔
497

498
electrum::electrum(const data_chunk& entropy, seed_prefix prefix,
26✔
499
    language identifier, size_t grind_limit) NOEXCEPT
26✔
500
  : electrum(from_entropy(entropy, prefix, identifier, grind_limit))
26✔
501
{
502
}
26✔
503

504
// protected
505
electrum::electrum(const data_chunk& entropy, const string_list& words,
87✔
506
    language identifier, seed_prefix prefix) NOEXCEPT
2✔
507
  : electrum_v1(entropy, words, identifier), prefix_(prefix)
69✔
508
{
509
}
18✔
510

511
// protected static (factories)
512
// ----------------------------------------------------------------------------
513

514
// To test existing entropy a caller should set grind_limit to zero (default).
515
electrum electrum::from_entropy(const data_chunk& entropy, seed_prefix prefix,
46✔
516
    language identifier, size_t grind_limit) NOEXCEPT
517
{
518
    // If allowed this would fail after grinding to limit.
519
    if (prefix == seed_prefix::none)
46✔
520
        return {};
2✔
521

522
    // Use mnemonic class instead.
523
    if (prefix == seed_prefix::bip39)
44✔
524
        return {};
2✔
525

526
    // Generates entropy from electrum_v1 entropy sizes.
527
    if (prefix == seed_prefix::old)
42✔
528
        return { entropy };
10✔
529

530
    if (!is_valid_entropy_size(entropy.size()))
32✔
531
        return {};
6✔
532

533
    if (!dictionaries_.exists(identifier))
26✔
534
        return {};
4✔
535

536
    // HACK: 2fa ambiguity.
537
    if (is_ambiguous(word_count(entropy), prefix))
22✔
538
        return {};
2✔
539

540
    // If prefix is 'none' this will return the first non-prefixed result.
541
    const auto grinding = grinder(entropy, prefix, identifier, grind_limit);
20✔
542

543
    // Not your lucky day.
544
    if (grinding.words.empty())
20✔
545
        return {};
2✔
546

547
    // Save derived words and ground entropy, original is discarded.
548
    return { grinding.entropy, grinding.words, identifier, prefix };
18✔
549
}
20✔
550

551
electrum electrum::from_words(const string_list& words,
82✔
552
    language identifier) NOEXCEPT
553
{
554
    if (!is_valid_word_count(words.size()))
82✔
555
        return {};
5✔
556

557
    // Prioritizes electrum_v1 (en) as electrum cannot generate the conflict.
558
    electrum_v1 old{ words, identifier };
77✔
559
    if (old)
77✔
560
        return old;
3✔
561

562
    // Normalize to improve chance of dictionary matching.
563
    const auto tokens = try_normalize(words);
74✔
564
    const auto lexicon = contained_by(tokens, identifier);
74✔
565

566
    if (lexicon == language::none)
74✔
567
        return {};
6✔
568

569
    if (identifier != language::none && lexicon != identifier)
68✔
UNCOV
570
        return {};
×
571

572
    // HACK: en-fr dictionary ambiguity.
573
    if (is_ambiguous(tokens, identifier, lexicon))
68✔
574
        return {};
1✔
575

576
    // Above prioritizes electrum matching, but if that fails then this will
577
    // be identified as a valid bip39 if the checksum validates. This is likely
578
    // in the case where a valid bip39 mnemonic is passed via the constructor.
579
    const auto prefix = normalized_to_prefix(tokens);
67✔
580

581
    // Save derived entropy and dictionary words, originals are discarded.
582
    return { decoder(tokens, lexicon), tokens, lexicon, prefix };
134✔
583
}
77✔
584

585
// public
586
// ----------------------------------------------------------------------------
587

588
electrum::seed_prefix electrum::prefix() const NOEXCEPT
55✔
589
{
590
    return prefix_;
55✔
591
}
592

593
long_hash electrum::to_seed(const std::string& passphrase) const NOEXCEPT
14✔
594
{
595
    if (!(*this))
14✔
UNCOV
596
        return {};
×
597

598
    if (!is_seedable(prefix_))
14✔
UNCOV
599
        return {};
×
600

601
    return seeder(words(), passphrase);
14✔
602
}
603

604
hd_private electrum::to_key(const std::string& passphrase,
17✔
605
    const context& context) const NOEXCEPT
606
{
607
    if (!(*this))
17✔
608
        return {};
1✔
609

610
    if (!is_seedable(prefix_))
16✔
611
        return {};
2✔
612

613
    // Bypass a BIP32 step, splitting directly to secret/chaincode.
614
    const auto halves = system::split(to_seed(passphrase));
14✔
615

616
    // The key will be invalid if the secret (part.first) does not ec verify.
617
    return { halves.first, halves.second, context.hd_prefixes() };
14✔
618
}
619

620
// static public (conversions)
621
// ----------------------------------------------------------------------------
622

UNCOV
623
hd_private electrum::to_key(const long_hash& seed,
×
624
    const context& context) NOEXCEPT
625
{
UNCOV
626
    const auto halves = system::split(seed);
×
627
    return { halves.first, halves.second, context.hd_prefixes() };
×
628
}
629

UNCOV
630
long_hash electrum::to_seed(const hd_private& key) NOEXCEPT
×
631
{
UNCOV
632
    return splice(key.secret(), key.chain_code());
×
633
}
634

635
} // namespace wallet
636
} // namespace system
637
} // namespace libbitcoin
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc