File Coverage

blib/lib/Bitcoin/Crypto/Util.pm
Criterion Covered Total %
statement 67 67 100.0
branch 12 12 100.0
condition 18 20 90.0
subroutine 16 16 100.0
pod 4 4 100.0
total 117 119 98.3


line stmt bran cond sub pod time code
1             package Bitcoin::Crypto::Util;
2             $Bitcoin::Crypto::Util::VERSION = '1.008_01'; # TRIAL
3             $Bitcoin::Crypto::Util::VERSION = '1.00801';
4 11     11   1526 use v5.10;
  11         41  
5 11     11   63 use strict;
  11         29  
  11         256  
6 11     11   59 use warnings;
  11         22  
  11         378  
7 11     11   81 use Exporter qw(import);
  11         32  
  11         478  
8 11     11   84 use List::Util qw(first);
  11         22  
  11         849  
9 11     11   75 use Crypt::PK::ECC;
  11         27  
  11         516  
10 11     11   8801 use Unicode::Normalize;
  11         30731  
  11         872  
11 11     11   5157 use Crypt::KeyDerivation qw(pbkdf2);
  11         3846  
  11         706  
12 11     11   5744 use Encode qw(encode);
  11         108000  
  11         781  
13              
14 11     11   94 use Bitcoin::Crypto::Config;
  11         31  
  11         295  
15 11     11   1004 use Bitcoin::Crypto::Base58 qw(decode_base58check);
  11         31  
  11         7174  
16              
17             our @EXPORT_OK = qw(
18             validate_wif
19             get_key_type
20             mnemonic_to_seed
21             get_path_info
22             );
23              
24             our %EXPORT_TAGS = (all => [@EXPORT_OK]);
25              
26             sub validate_wif
27             {
28 11     11 1 1615 my ($wif) = @_;
29 11         48 my $byte_wif = decode_base58check($wif);
30 10         35 my $last_byte = substr $byte_wif, -1;
31 10 100       39 if (length $byte_wif == Bitcoin::Crypto::Config::key_max_length + 2) {
32 4         21 return $last_byte eq Bitcoin::Crypto::Config::wif_compressed_byte;
33             }
34             else {
35 6         36 return length $byte_wif == Bitcoin::Crypto::Config::key_max_length + 1;
36             }
37             }
38              
39             sub get_key_type
40             {
41 502     502 1 961 my ($entropy) = @_;
42              
43 502         843 my $curve_size = Bitcoin::Crypto::Config::key_max_length;
44 502         1004 my $octet = substr $entropy, 0, 1;
45              
46 502   100     2501 my $has_unc_oc = $octet eq "\x04" || $octet eq "\x06" || $octet eq "\x07";
47 502   100     1666 my $is_unc = $has_unc_oc && length $entropy == 2 * $curve_size + 1;
48              
49 502   100     1617 my $has_com_oc = $octet eq "\x02" || $octet eq "\x03";
50 502   66     1100 my $is_com = $has_com_oc && length $entropy == $curve_size + 1;
51              
52 502 100 100     1806 return 0
53             if $is_com || $is_unc;
54 355 100       1164 return 1
55             if length $entropy <= $curve_size;
56 1         5 return;
57             }
58              
59             sub mnemonic_to_seed
60             {
61 31     31 1 190 my ($mnemonic, $password) = @_;
62              
63 31         555 $mnemonic = encode('UTF-8', NFKD($mnemonic));
64 31   100     3405 $password = encode('UTF-8', NFKD('mnemonic' . ($password // '')));
65              
66 31         314404 return pbkdf2($mnemonic, $password, 2048, 'SHA512', 64);
67             }
68              
69             sub get_path_info
70             {
71 97     97 1 4184 my ($path) = @_;
72 97 100       878 if ($path =~ m#^([mM])((?:/\d+'?)*)$#) {
73 93         203 my %info;
74 93         422 $info{private} = $1 eq 'm';
75 93 100 66     650 if (defined $2 && length $2 > 0) {
76             $info{path} =
77 83         464 [map { s#(\d+)'#$1 + Bitcoin::Crypto::Config::max_child_keys#e; $_ } split /\//, substr $2, 1];
  293         831  
  106         381  
  293         819  
78             }
79             else {
80 10         27 $info{path} = [];
81             }
82 292     292   657 return undef if first { $_ >= Bitcoin::Crypto::Config::max_child_keys * 2 }
83 93 100       555 @{$info{path}};
  93         488  
84 91         554 return \%info;
85             }
86             else {
87 4         22 return undef;
88             }
89             }
90              
91             1;
92              
93             __END__
94             =head1 NAME
95              
96             Bitcoin::Crypto::Util - Basic utilities for working with bitcoin
97              
98             =head1 SYNOPSIS
99              
100             use Bitcoin::Crypto::Util qw(
101             validate_wif
102             get_key_type
103             get_path_info
104             );
105              
106             =head1 DESCRIPTION
107              
108             These are basic utilities for working with bitcoin, used by other packages.
109              
110             =head1 FUNCTIONS
111              
112             =head2 validate_wif
113              
114             $bool = validate_wif($str);
115              
116             Ensures Base58 encoded string looks like encoded private key in WIF format.
117             Throws an exception if C<$str> is not valid base58.
118              
119             =head2 get_key_type
120              
121             $is_private = get_key_type($bytestr);
122              
123             Checks if the C<$bytestr> looks like a valid ASN X9.62 format (compressed / uncompressed / hybrid public key or private key entropy up to curve size bits).
124             Returns boolean which can be used to determine if the key is private.
125             Returns undef if C<$bytestr> does not look like a valid key entropy.
126              
127             =head2 mnemonic_to_seed
128              
129             $seed = mnemonic_to_seed($mnemonic, $password);
130              
131             Transforms the given BIP39 C<$mnemonic> and C<$password> into a valid BIP32 C<$seed>, which can be fed into L<Bitcoin::Crypto::Key::ExtPrivate/from_seed>.
132              
133             C<$seed> is a 512 bit bytestring (64 characters). C<$mnemonic> should be a BIP39 mnemonic, but will not be checked against a dictionary.
134              
135             This function is only useful if you need a seed instead of mnemonic (for example, you use a wallet implementation which does not implement BIP39). If you only want to create a private key from mnemonic, you should consider using L<Bitcoin::Crypto::Key::ExtPrivate/from_mnemonic> instead.
136              
137             B<Important note about unicode:> this function only accepts UTF8-decoded strings (both C<$mnemonic> and C<$password>), but can't detect whether it got it or not. This will only become a problem if you use non-ascii mnemonic and/or password. If there's a possibility of non-ascii, always use utf8 and set binmodes to get decoded (wide) characters to avoid problems recovering your wallet.
138              
139             =head2 get_path_info
140              
141             $path_data = get_path_info($path);
142              
143             Tries to get derivation path data from C<$path>.
144             Returns undef if C<$path> is not a valid path.
145             Otherwise returns the structure:
146              
147             {
148             private => bool, # is path derivation private (lowercase m)
149             path => [
150             # derivation path with 2^31 added to every hardened child number
151             int, int, ..
152             ],
153             }
154              
155             Example:
156              
157             my $path = "m/1/3'";
158             my $path_data = get_path_info($path);
159              
160             =head1 SEE ALSO
161              
162             =over 2
163              
164             =item L<Bitcoin::Crypto::Key::ExtPrivate>
165              
166             =back
167              
168             =cut
169