Skip to content

Commit 4ac24f0

Browse files
committed
Implement BIP39 (mnemonic) application from BIP85
1 parent 0aa16e9 commit 4ac24f0

File tree

2 files changed

+103
-2
lines changed

2 files changed

+103
-2
lines changed

lib/Bitcoin/Crypto/BIP85.pm

+78-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use List::Util qw(all);
1010
use Crypt::Mac::HMAC qw(hmac);
1111
use Crypt::Digest::SHAKE;
1212

13-
use Bitcoin::Crypto::Util qw(get_path_info);
13+
use Bitcoin::Crypto::Util qw(get_path_info mnemonic_from_entropy);
1414
use Bitcoin::Crypto::Exception;
1515

1616
use namespace::clean;
@@ -60,6 +60,49 @@ sub derive_entropy
6060
return $seed;
6161
}
6262

63+
signature_for derive_mnemonic => (
64+
method => Object,
65+
named => [
66+
words => Enum [12, 18, 24],
67+
{default => 24},
68+
language => Str,
69+
{default => 'en'},
70+
index => PositiveOrZeroInt,
71+
{default => 0},
72+
],
73+
bless => !!0,
74+
);
75+
76+
sub derive_mnemonic
77+
{
78+
my ($self, $args) = @_;
79+
80+
my %language_map = (
81+
'en' => 0,
82+
'ja' => 1,
83+
'ko' => 2,
84+
'es' => 3,
85+
'zh-simplified' => 4,
86+
'zh-traditional' => 5,
87+
'fr' => 6,
88+
'it' => 7,
89+
90+
# NOTE: czech has no BIP39 wordlist module, but add it for sake of completeness
91+
'cz' => 8,
92+
);
93+
94+
my $language_index = $language_map{$args->{language}};
95+
Bitcoin::Crypto::Exception::MnemonicGenerate->raise(
96+
"unknown mnemonic language code $args->{language}"
97+
) unless defined $language_index;
98+
99+
my $spec_path = "m/83696968'/39'/$language_index'/$args->{words}'/$args->{index}'";
100+
my $length = $args->{words} / 3 * 4;
101+
102+
my $entropy = $self->derive_entropy($spec_path, $length);
103+
return mnemonic_from_entropy($entropy, $args->{language});
104+
}
105+
63106
1;
64107

65108
__END__
@@ -78,11 +121,22 @@ Bitcoin::Crypto::BIP85 - BIP85 (deterministic entropy) implementation
78121
# get raw bytestring seed
79122
my $seed = $bip85->derive_entropy("m/0'/0'");
80123
124+
# get a mnemonic
125+
my $mnemonic = $bip85->derive_mnemonic(index => 0);
126+
81127
=head1 DESCRIPTION
82128
83129
This module implements
84130
L<BIP85|https://github.com/bitcoin/bips/blob/master/bip-0085.mediawiki>,
85-
enabling deterministic seed generation from a master key.
131+
enabling deterministic entropy generation from a master key.
132+
133+
It currently implements the following applications from the BIP85 spec:
134+
135+
=over
136+
137+
=item * C<BIP39>: L</derive_mnemonic>
138+
139+
=back
86140
87141
=head1 INTERFACE
88142
@@ -117,3 +171,25 @@ than C<64>, the entropy will be truncated to the derired length. If greater
117171
than C<64>, the C<DRNG> algorithm defined in BIP85 will be used to stretch the
118172
entropy to this size.
119173
174+
=head3 derive_mnemonic
175+
176+
$mnemonic = $object->derive_mnemonic(%args)
177+
178+
Derives mnemonic from the master key. C<%args> can be any combination of:
179+
180+
=over
181+
182+
=item * C<words>
183+
184+
The number of words to generate. Can be either C<12>, C<18> or C<24>. Default: C<24>.
185+
186+
=item * C<language>
187+
188+
The language to use. See L<Bitcoin::BIP39> for more info about this argument. Default: C<en>.
189+
190+
=item * C<index>
191+
192+
The generation index. Must be a non-negative integer. Default: C<0>
193+
194+
=back
195+

t/BIP85.t

+25
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,30 @@ subtest 'should derive_entropy' => sub {
3030
'entropy index 1 derived ok';
3131
};
3232

33+
subtest 'should derive a mnemonic according to BIP39 application of BIP85' => sub {
34+
my $bip85 = Bitcoin::Crypto::BIP85->new(
35+
key => btc_extprv->from_serialized(
36+
[
37+
base58 =>
38+
'xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb'
39+
]
40+
),
41+
);
42+
43+
# check words
44+
is $bip85->derive_mnemonic(words => 12),
45+
'girl mad pet galaxy egg matter matrix prison refuse sense ordinary nose', '12 words ok';
46+
is $bip85->derive_mnemonic(words => 18),
47+
'near account window bike charge season chef number sketch tomorrow excuse sniff circle vital hockey outdoor supply token',
48+
'18 words ok';
49+
is $bip85->derive_mnemonic(words => 24),
50+
'puppy ocean match cereal symbol another shed magic wrap hammer bulb intact gadget divorce twin tonight reason outdoor destroy simple truth cigar social volcano',
51+
'24 words ok';
52+
53+
# check index
54+
is $bip85->derive_mnemonic(words => 12, index => 1),
55+
'mystery car occur shallow stable order number feature else best trigger curious', '12 words index 1 ok';
56+
};
57+
3358
done_testing;
3459

0 commit comments

Comments
 (0)