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';
3 11     11   2146 use v5.10;
  11         55  
4 11     11   68 use strict;
  11         32  
  11         269  
5 11     11   85 use warnings;
  11         29  
  11         357  
6 11     11   75 use Exporter qw(import);
  11         25  
  11         467  
7 11     11   69 use List::Util qw(first);
  11         29  
  11         850  
8 11     11   736 use Crypt::PK::ECC;
  11         15756  
  11         495  
9 11     11   6724 use Unicode::Normalize;
  11         25181  
  11         806  
10 11     11   5155 use Crypt::KeyDerivation qw(pbkdf2);
  11         3704  
  11         680  
11 11     11   5702 use Encode qw(encode);
  11         104753  
  11         781  
12              
13 11     11   503 use Bitcoin::Crypto::Config;
  11         38  
  11         282  
14 11     11   873 use Bitcoin::Crypto::Base58 qw(decode_base58check);
  11         28  
  11         7080  
15              
16             our @EXPORT_OK = qw(
17             validate_wif
18             get_key_type
19             mnemonic_to_seed
20             get_path_info
21             );
22              
23             our %EXPORT_TAGS = (all => [@EXPORT_OK]);
24              
25             sub validate_wif
26             {
27 11     11 1 1120 my ($wif) = @_;
28 11         39 my $byte_wif = decode_base58check($wif);
29 10         65 my $last_byte = substr $byte_wif, -1;
30 10 100       36 if (length $byte_wif == Bitcoin::Crypto::Config::key_max_length + 2) {
31 4         20 return $last_byte eq Bitcoin::Crypto::Config::wif_compressed_byte;
32             }
33             else {
34 6         27 return length $byte_wif == Bitcoin::Crypto::Config::key_max_length + 1;
35             }
36             }
37              
38             sub get_key_type
39             {
40 501     501 1 940 my ($entropy) = @_;
41              
42 501         798 my $curve_size = Bitcoin::Crypto::Config::key_max_length;
43 501         1096 my $octet = substr $entropy, 0, 1;
44              
45 501   100     2266 my $has_unc_oc = $octet eq "\x04" || $octet eq "\x06" || $octet eq "\x07";
46 501   100     1495 my $is_unc = $has_unc_oc && length $entropy == 2 * $curve_size + 1;
47              
48 501   100     1490 my $has_com_oc = $octet eq "\x02" || $octet eq "\x03";
49 501   66     1070 my $is_com = $has_com_oc && length $entropy == $curve_size + 1;
50              
51 501 100 100     1700 return 0
52             if $is_com || $is_unc;
53 355 100       1201 return 1
54             if length $entropy <= $curve_size;
55 1         3 return;
56             }
57              
58             sub mnemonic_to_seed
59             {
60 31     31 1 198 my ($mnemonic, $password) = @_;
61              
62 31         572 $mnemonic = encode("UTF-8", NFKD($mnemonic));
63 31   100     3338 $password = encode("UTF-8", NFKD("mnemonic" . ($password // "")));
64              
65 31         315204 return pbkdf2($mnemonic, $password, 2048, "SHA512", 64);
66             }
67              
68             sub get_path_info
69             {
70 97     97 1 3522 my ($path) = @_;
71 97 100       786 if ($path =~ m#^([mM])((?:/\d+'?)*)$#) {
72 93         201 my %info;
73 93         446 $info{private} = $1 eq "m";
74 93 100 66     608 if (defined $2 && length $2 > 0) {
75             $info{path} =
76 83         437 [map { s#(\d+)'#$1 + Bitcoin::Crypto::Config::max_child_keys#e; $_ } split "/", substr $2, 1];
  293         799  
  106         368  
  293         813  
77             }
78             else {
79 10         44 $info{path} = [];
80             }
81 292     292   683 return undef if first { $_ >= Bitcoin::Crypto::Config::max_child_keys * 2 }
82 93 100       583 @{$info{path}};
  93         465  
83 91         485 return \%info;
84             }
85             else {
86 4         18 return undef;
87             }
88             }
89              
90             1;
91              
92             __END__
93             =head1 NAME
94              
95             Bitcoin::Crypto::Util - Basic utilities for working with bitcoin
96              
97             =head1 SYNOPSIS
98              
99             use Bitcoin::Crypto::Util qw(
100             validate_wif
101             get_key_type
102             get_path_info
103             );
104              
105             =head1 DESCRIPTION
106              
107             These are basic utilities for working with bitcoin, used by other packages.
108              
109             =head1 FUNCTIONS
110              
111             =head2 validate_wif
112              
113             $bool = validate_wif($str);
114              
115             Ensures Base58 encoded string looks like encoded private key in WIF format.
116             Throws an exception if C<$str> is not valid base58.
117              
118             =head2 get_key_type
119              
120             $is_private = get_key_type($bytestr);
121              
122             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).
123             Returns boolean which can be used to determine if the key is private.
124             Returns undef if C<$bytestr> does not look like a valid key entropy.
125              
126             =head2 mnemonic_to_seed
127              
128             $seed = mnemonic_to_seed($mnemonic, $password);
129              
130             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>.
131              
132             C<$seed> is a 512 bit bytestring (64 characters). C<$mnemonic> should be a BIP39 mnemonic, but will not be checked against a dictionary.
133              
134             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.
135              
136             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.
137              
138             =head2 get_path_info
139              
140             $path_data = get_path_info($path);
141              
142             Tries to get derivation path data from C<$path>.
143             Returns undef if C<$path> is not a valid path.
144             Otherwise returns the structure:
145              
146             {
147             private => bool, # is path derivation private (lowercase m)
148             path => [
149             # derivation path with 2^31 added to every hardened child number
150             int, int, ..
151             ],
152             }
153              
154             Example:
155              
156             my $path = "m/1/3'";
157             my $path_data = get_path_info($path);
158              
159             =head1 SEE ALSO
160              
161             =over 2
162              
163             =item L<Bitcoin::Crypto::Key::ExtPrivate>
164              
165             =back
166              
167             =cut
168