Skip to content

Commit 70f4dd3

Browse files
committed
Initial commit - first version
0 parents  commit 70f4dd3

File tree

8 files changed

+319
-0
lines changed

8 files changed

+319
-0
lines changed

.gitignore

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Makefile*
2+
!Makefile.PL
3+
MYMETA*
4+
blib/*
5+
MANIFEST*
6+
pm_to_blib
7+
*.tar.gz

Changes

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Revision history for Perl extension App::Bitcoin::PaperWallet.
2+
3+
1.00 Fri Sep 10 13:00:00 2021
4+
- first version
5+

Makefile.PL

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use 5.012000;
2+
use strict;
3+
use warnings;
4+
use ExtUtils::MakeMaker;
5+
6+
WriteMakefile(
7+
NAME => 'App::Bitcoin::PaperWallet',
8+
VERSION_FROM => 'lib/App/Bitcoin/PaperWallet.pm',
9+
MIN_PERL_VERSION => '5.012000',
10+
11+
EXE_FILES => [
12+
'bin/paper-wallet',
13+
],
14+
15+
PREREQ_PM => {
16+
'Bitcoin::Crypto' => 0,
17+
'Text::QRCode' => 0,
18+
},
19+
20+
META_MERGE => {
21+
'meta-spec' => { version => 2 },
22+
resources => {
23+
license => 'https://dev.perl.org/licenses',
24+
bugtracker => {
25+
web => 'https://github.com/brtastic/perl-app-bitcoin-paperwallet/issues',
26+
},
27+
repository => {
28+
type => 'git',
29+
url => 'https://github.com/brtastic/perl-app-bitcoin-paperwallet.git',
30+
web => 'https://github.com/brtastic/perl-app-bitcoin-paperwallet',
31+
},
32+
},
33+
},
34+
35+
ABSTRACT_FROM => 'lib/App/Bitcoin/PaperWallet.pm',
36+
AUTHOR => 'Bartosz Jarzyna <[email protected]>',
37+
LICENSE => 'perl',
38+
);

README.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# App::Bitcoin::PaperWallet
2+
A Perl module for generating Bitcoin paper wallets.
3+
4+
## Code and documentation
5+
[App::Bitcoin::PaperWallet on CPAN](https://metacpan.org/release/App-Bitcoin-PaperWallet)
6+
7+
## Bugs and feature requests
8+
Please use the Github's issue tracker to file both bugs and feature requests.
9+
10+
## Contributions
11+
Contributions to the project in form of Github's pull requests are
12+
welcome. Please make sure your code is in line with the general
13+
coding style of the module. Let me know if you plan something
14+
bigger so we can talk it through.
15+
16+
### Author
17+
Bartosz Jarzyna <[email protected]>

bin/paper-wallet

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#!/usr/bin/env perl
2+
3+
use v5.12;
4+
use warnings;
5+
use utf8;
6+
7+
use Text::QRCode;
8+
use List::Util qw(pairs);
9+
use App::Bitcoin::PaperWallet;
10+
11+
sub get_qr
12+
{
13+
my ($data) = @_;
14+
my $arrayref = Text::QRCode->new->plot($data);
15+
return map { $_ =~ s/\*//gr } map { join '', map { $_, $_ } @$_ } @$arrayref;
16+
}
17+
18+
sub align_qrs
19+
{
20+
my @qr1 = @{shift()};
21+
my @qr2 = @{shift()};
22+
my $offset = ' ' x (length $qr1[0]);
23+
24+
my @output;
25+
while (@qr1 || @qr2) {
26+
my $line = shift(@qr1) // $offset;
27+
$line .= ' ' x 4;
28+
$line .= shift(@qr2) // '';
29+
push @output, $line;
30+
}
31+
32+
return @output;
33+
}
34+
35+
sub prompt
36+
{
37+
my ($info) = @_;
38+
say $info;
39+
40+
my $data = <STDIN>;
41+
chomp $data;
42+
43+
return $data;
44+
}
45+
46+
sub get_entropy
47+
{
48+
say 'Waiting for user defined entropy. Use --auto for automatic generation';
49+
return prompt "Enter any random entropy: by rolling dice, drawing cards or other means available";
50+
}
51+
52+
sub get_passphrase
53+
{
54+
return prompt 'Enter passphrase for your private key. Warning: plaintext!';
55+
}
56+
57+
my $wallet_file = 'wallet.txt';
58+
die "wallet file $wallet_file already exists" if -f $wallet_file;
59+
60+
my $flag = shift;
61+
my $entropy = $flag eq '--auto' ? undef : get_entropy;
62+
my $pass = get_passphrase;
63+
64+
my $bitcoin_data = App::Bitcoin::PaperWallet->generate($entropy, $pass);
65+
66+
open my $fh, '>:utf8', $wallet_file
67+
or die 'cannot open ' . $wallet_file;
68+
69+
my @data;
70+
71+
push @data,
72+
'-- PASSWORD PROTECTED PRIVATE KEY --',
73+
$bitcoin_data->{mnemonic},
74+
'',
75+
'-- ADDRESSES --',
76+
''
77+
;
78+
79+
my @qrs;
80+
for my $addr (@{$bitcoin_data->{addresses}}) {
81+
push @data, $addr;
82+
push @qrs, [get_qr $addr];
83+
}
84+
85+
push @data, '';
86+
87+
for my $qr (pairs @qrs) {
88+
push @data,
89+
align_qrs(@$qr),
90+
'',
91+
''
92+
;
93+
}
94+
95+
say {$fh} join "\n", @data;
96+
97+
close $fh
98+
or die 'could not close the file';
99+
100+
say "done - see $wallet_file";
101+
102+
__END__
103+
104+
=head1 NAME
105+
106+
paper-wallet - Script to generate a paper wallet file
107+
108+
=head1 SYNOPSIS
109+
110+
paper-wallet [--auto]
111+
112+
=head1 DESCRIPTION
113+
114+
This script will generate a C<wallet.txt> file in your current working directory that contains Bitcoin wallet details ready to be printed or stored on a flash drive. This file must not exist already or the script will fail (to ensure that you don't override your previously generated wallet that you might have already used).
115+
116+
This is intended to be used as cold storage (Bitcoin wallet which does not have active connection to the Internet). The generation should best take place while being offline.
117+
118+
The script will interactively ask for wallet password and entropy (random data that secures your funds). If you don't have any means to generate random data, like rolling dice, or you don't know how to properly do so (to ensure large enough entropy), it is recommended to use the C<--auto> flag, which will use secure random generators to do it for you. Password can be skipped by hitting enter, but it is not recommended to do so. Remember that there is no way to restore your password, so make sure you won't lose it.
119+
120+
After printing, you can cut off the top section (mnemonic seed) and store it apart from the addresses for safety.
121+
122+
=head1 SEE ALSO
123+
124+
L<App::Bitcoin::PaperWallet>

lib/App/Bitcoin/PaperWallet.pm

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package App::Bitcoin::PaperWallet;
2+
3+
our $VERSION = '1.00';
4+
5+
use v5.12;
6+
use warnings;
7+
8+
use Bitcoin::Crypto qw(btc_extprv);
9+
use Digest::SHA qw(sha256);
10+
11+
sub get_addresses
12+
{
13+
my ($key, $count) = @_;
14+
$count //= 4;
15+
16+
my @addrs;
17+
my $priv = $key->derive_key_bip44(index => 0)->get_basic_key;
18+
my $addr = $priv->get_public_key->get_compat_address;
19+
push @addrs, $addr;
20+
21+
for my $ind (1 .. $count - 1) {
22+
my $priv = $key->derive_key_bip44(index => $ind)->get_basic_key;
23+
my $addr = $priv->get_public_key->get_segwit_address;
24+
push @addrs, $addr;
25+
}
26+
27+
return @addrs;
28+
}
29+
30+
sub generate
31+
{
32+
my ($class, $entropy, $pass, $address_count) = @_;
33+
34+
my $mnemonic = defined $entropy
35+
? btc_extprv->mnemonic_from_entropy(sha256($entropy))
36+
: btc_extprv->generate_mnemonic(256)
37+
;
38+
39+
my $key = btc_extprv->from_mnemonic($mnemonic, $pass);
40+
41+
return {
42+
mnemonic => $mnemonic,
43+
addresses => [get_addresses($key, $address_count)],
44+
};
45+
}
46+
47+
1;
48+
49+
__END__
50+
51+
=head1 NAME
52+
53+
App::Bitcoin::PaperWallet - Generate printable cold storage of bitcoins
54+
55+
=head1 SYNOPSIS
56+
57+
use App::Bitcoin::PaperWallet;
58+
59+
my $hash = App::Bitcoin::PaperWallet->generate($entropy, $password, $address_count // 4);
60+
61+
my $mnemonic = $hash->{mnemonic};
62+
my $addresses = $hash->{addresses};
63+
64+
=head1 DESCRIPTION
65+
66+
This module allows you to generate a Hierarchical Deterministic BIP44 compilant Bitcoin wallet.
67+
68+
This package contains high level cryptographic operations for doing that. See L<paper-wallet> for the main script of this distribution.
69+
70+
=head1 FUNCTIONS
71+
72+
=head2 generate
73+
74+
my $hash = App::Bitcoin::PaperWallet->generate($entropy, $password, $address_count // 4);
75+
76+
Not exported, should be used as a class method. Returns a hash containing two keys: C<mnemonic> (string) and C<addresses> (array reference of strings).
77+
78+
C<$entropy> is meant to be user-defined entropy (string) that will be passed through sha256 to obtain wallet seed. Can be passed C<undef> explicitly to use cryptographically secure random number generator instead.
79+
80+
C<$password> is a password that will be used to secure the generated mnemonic. Passing empty string will disable the password protection. Note that password does not have to be strong, since it will only secure the mnemonic in case someone obtained physical access to your mnemonic. Using a hard, long password increases the possibility you will not be able to claim your bitcoins in the future.
81+
82+
Optional C<$address_count> is the number of addresses that will be generated (default 4). The first address is always SegWit compat address, while the rest are SegWit native addresses.
83+
84+
=head1 SEE ALSO
85+
86+
L<Bitcoin::Crypto>
87+
88+
=head1 AUTHOR
89+
90+
Bartosz Jarzyna, E<lt>[email protected]E<gt>
91+
92+
=head1 COPYRIGHT AND LICENSE
93+
94+
Copyright (C) 2021 by Bartosz Jarzyna
95+
96+
This library is free software; you can redistribute it and/or modify
97+
it under the same terms as Perl itself, either Perl version 5.34.0 or,
98+
at your option, any later version of Perl 5 you may have available.
99+
100+
101+
=cut

t/01-compile.t

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use v5.12;
2+
use warnings;
3+
4+
use Test::More;
5+
BEGIN { use_ok('App::Bitcoin::PaperWallet') };
6+
7+
done_testing;
8+

t/02-generate.t

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use v5.12;
2+
use warnings;
3+
4+
use Test::More;
5+
use App::Bitcoin::PaperWallet;
6+
7+
my $hash = App::Bitcoin::PaperWallet->generate('silly entropy that should never be used in a real wallet', 'sillypass');
8+
9+
# seed should be 76f30b114cb9165116a9b0a9e214e3ea4cfa9923adc8154e5d72b12e54b5a20a
10+
is $hash->{mnemonic}, 'ivory obscure session offer multiply chuckle follow current prepare awful decline stand soul erode modify ribbon best indicate frequent girl torch food market evidence', 'mnemonic ok';
11+
12+
# those addresses take password into account
13+
is $hash->{addresses}[0], '3DG5rqGa7xFhYRxJYQnU1GPqLBmYnxpwDu', 'compat address ok';
14+
is $hash->{addresses}[1], 'bc1q2v0rc9vasw49dmnlttn5evj5t7yajt9hf85gfn', 'native address ok';
15+
16+
# test data generated using https://iancoleman.io/bip39/
17+
18+
done_testing;
19+

0 commit comments

Comments
 (0)