File Coverage

blib/lib/Blockchain/Ethereum/Keystore/Keyfile.pm
Criterion Covered Total %
statement 111 134 82.8
branch 2 6 33.3
condition n/a
subroutine 27 34 79.4
pod 4 20 20.0
total 144 194 74.2


line stmt bran cond sub pod time code
1 2     2   108062 use v5.26;
  2         21  
2 2     2   698 use Object::Pad ':experimental(init_expr)';
  2         11687  
  2         9  
3              
4             package Blockchain::Ethereum::Keystore::Keyfile 0.005;
5             class Blockchain::Ethereum::Keystore::Keyfile;
6              
7             =encoding utf8
8              
9             =head1 NAME
10              
11             Blockchain::Ethereum::Keystore::Keyfile - Ethereum Keyfile abstraction
12              
13             =head1 SYNOPSIS
14              
15             Ethereum keyfile abstraction
16              
17             Currently only supports read and write for keyfile v3
18              
19             =cut
20              
21 2     2   812 use Carp;
  2         4  
  2         163  
22 2     2   1239 use File::Slurp;
  2         58414  
  2         161  
23 2     2   1064 use JSON::MaybeXS qw(decode_json encode_json);
  2         12716  
  2         137  
24 2     2   536 use Crypt::PRNG;
  2         3634  
  2         95  
25 2     2   1078 use Net::SSH::Perl::Cipher;
  2         6886  
  2         82  
26              
27 2     2   529 use Blockchain::Ethereum::Keystore::Key;
  2         10  
  2         86  
28 2     2   981 use Blockchain::Ethereum::Keystore::Keyfile::KDF;
  2         6  
  2         5750  
29              
30 2     2 0 4 field $cipher :reader :writer;
  2     2 0 8  
  2         5  
  2         6  
31 2     2 0 9 field $ciphertext :reader :writer;
  2     4 0 23  
  4         20  
  4         13  
32 0     0 0 0 field $mac :reader :writer;
  0     2 0 0  
  2         5  
  2         7  
33 2     2 0 5 field $version :reader :writer;
  2     0 0 7  
  0         0  
  0         0  
34 2     2 0 24 field $iv :reader :writer;
  2     4 0 69  
  4         10  
  4         11  
35 14     14 0 44 field $kdf :reader :writer;
  14     2 0 122  
  2         5  
  2         8  
36 0     0 0 0 field $id :reader :writer;
  0     0 0 0  
  0         0  
  0         0  
37 6     6 0 37 field $private_key :reader :writer;
  6     4 0 39  
  4         14  
  4         138  
38              
39 2     2   11 field $_json :reader(_json) = JSON::MaybeXS->new(utf8 => 1);
40              
41 2         54 =head2 import_file
42              
43             Import a v3 keyfile
44              
45             Usage:
46              
47             import_file($file_path) -> $self
48              
49             =over 4
50              
51             =item * C<file_path> - string path for the keyfile
52              
53             =back
54              
55             self
56              
57             =cut
58              
59 2     2 1 61 method import_file ($file_path, $password) {
  2         6  
  2         5  
  2         4  
  2         3  
60              
61 2         18 my $content = read_file($file_path);
62 2         2113 my $decoded = $self->_json->decode(lc $content);
63              
64 2         44 return $self->_from_object($decoded, $password);
65             }
66              
67 2     2   15 method _from_object ($object, $password) {
  2         4  
  2         4  
  2         5  
  2         5  
68              
69 2         4 my $version = $object->{version};
70              
71 2 50       11 croak 'Could not determine the version' unless $version >= 3;
72              
73 2         9 return $self->_from_v3($object, $password);
74             }
75              
76 2     2   6 method _from_v3 ($object, $password) {
  2         5  
  2         5  
  2         5  
  2         5  
77              
78 2         6 my $crypto = $object->{crypto};
79              
80 2         10 $self->set_cipher('AES128_CTR');
81 2         9 $self->set_ciphertext($crypto->{ciphertext});
82 2         10 $self->set_mac($crypto->{mac});
83 2         9 $self->set_version(3);
84 2         8 $self->set_iv($crypto->{cipherparams}->{iv});
85              
86 2         6 my $header = $crypto->{kdfparams};
87              
88             $self->set_kdf(
89             Blockchain::Ethereum::Keystore::Keyfile::KDF->new(
90             algorithm => $crypto->{kdf}, #
91             dklen => $header->{dklen},
92             n => $header->{n},
93             p => $header->{p},
94             r => $header->{r},
95             c => $header->{c},
96             prf => $header->{prf},
97 2         41 salt => $header->{salt}));
98              
99 2         14 $self->set_private_key($self->_private_key($password));
100              
101 2         36 return $self;
102             }
103              
104             =head2 change_password
105              
106             Change the imported keyfile password
107              
108             Usage:
109              
110             change_password($old_password, $new_password) -> $self
111              
112             =over 4
113              
114             =item * C<old_password> - Current password for the keyfile
115              
116             =item * C<new_password> - New password to be set
117              
118             =back
119              
120             self
121              
122             =cut
123              
124 0     0 1 0 method change_password ($old_password, $new_password) {
  0         0  
  0         0  
  0         0  
  0         0  
125              
126 0         0 return $self->import_key($self->_private_key($old_password), $new_password);
127             }
128              
129 2     2   4 method _private_key ($password) {
  2         5  
  2         5  
  2         3  
130              
131 2 50       7 return $self->private_key if $self->private_key;
132              
133 2         11 my $cipher = Net::SSH::Perl::Cipher->new(
134             $self->cipher, #
135             $self->kdf->decode($password),
136             pack("H*", $self->iv));
137              
138 2         6095 my $key = $cipher->decrypt(pack("H*", $self->ciphertext));
139              
140 2         250 return Blockchain::Ethereum::Keystore::Key->new(private_key => $key);
141             }
142              
143             =head2 import_key
144              
145             Import a L<Blockchain::Ethereum::keystore::Key>
146              
147             Usage:
148              
149             import_key($keyfile) -> $self
150              
151             =over 4
152              
153             =item * C<keyfile> - L<Blockchain::Ethereum::Keystore::Key>
154              
155             =back
156              
157             self
158              
159             =cut
160              
161 2     2 1 8 method import_key ($key, $password) {
  2         6  
  2         6  
  2         5  
  2         6  
162              
163             # use the internal method here otherwise would not be availble to get the kdf params
164             # salt if give will be the same as the response, if not will be auto generated by the library
165 2         7 my ($derived_key, $salt, $N, $r, $p);
166 2         15 ($derived_key, $salt, $N, $r, $p) = Crypt::ScryptKDF::_scrypt_extra($password);
167 2         229537 $self->kdf->set_algorithm("scrypt");
168 2         10 $self->kdf->set_dklen(length $derived_key);
169 2         9 $self->kdf->set_n($N);
170 2         8 $self->kdf->set_p($p);
171 2         7 $self->kdf->set_r($r);
172 2         9 $self->kdf->set_salt(unpack "H*", $salt);
173              
174 2         20 my $iv = Crypt::PRNG::random_bytes(16);
175 2         76 $self->set_iv(unpack "H*", $iv);
176              
177 2         22 my $cipher = Net::SSH::Perl::Cipher->new(
178             "AES128_CTR", #
179             $derived_key,
180             $iv
181             );
182              
183 2         248 my $encrypted = $cipher->encrypt($key->export);
184 2         165 $self->set_ciphertext(unpack "H*", $encrypted);
185              
186 2         17 $self->set_private_key($key);
187              
188 2         22 return $self;
189             }
190              
191 0     0     method _write_to_object {
192              
193 0 0         croak "KDF algorithm and parameters are not set" unless $self->kdf;
194              
195 0           my $file = {
196             "crypto" => {
197             "cipher" => 'aes-128-ctr',
198             "cipherparams" => {"iv" => $self->iv},
199             "ciphertext" => $self->ciphertext,
200             "kdf" => $self->kdf->algorithm,
201             "kdfparams" => {
202             "dklen" => $self->kdf->dklen,
203             "n" => $self->kdf->n,
204             "p" => $self->kdf->p,
205             "r" => $self->kdf->r,
206             "salt" => $self->kdf->salt
207             },
208             "mac" => $self->mac
209             },
210             "id" => $self->id,
211             "version" => 3
212             };
213              
214 0           return $file;
215             }
216              
217             =head2 write_to_file
218              
219             Write the imported keyfile/private_key to a keyfile in the file system
220              
221             Usage:
222              
223             write_to_file($file_path) -> $self
224              
225             =over 4
226              
227             =item * C<file_path> - file path to save the data
228              
229             =back
230              
231             returns 1 upon successfully writing the file or undef if it encountered an error
232              
233             =cut
234              
235 0     0 1   method write_to_file ($file_path) {
  0            
  0            
  0            
236              
237 0           return write_file($file_path, $self->_json->canonical(1)->pretty->encode($self->_write_to_object));
238             }
239              
240             1;
241              
242             __END__
243              
244             =head1 AUTHOR
245              
246             Reginaldo Costa, C<< <refeco at cpan.org> >>
247              
248             =head1 BUGS
249              
250             Please report any bugs or feature requests to L<https://github.com/refeco/perl-ethereum-keystore>
251              
252             =head1 LICENSE AND COPYRIGHT
253              
254             This software is Copyright (c) 2023 by REFECO.
255              
256             This is free software, licensed under:
257              
258             The MIT License
259              
260             =cut