#!/usr/bin/env perl

use v5.12;
use warnings;
use utf8;

use Text::QRCode;
use List::Util qw(pairs);
use App::Bitcoin::PaperWallet;
use Time::Piece;
use Getopt::Long;
use Pod::Usage;

sub get_qr
{
	my ($data) = @_;
	my $arrayref = Text::QRCode->new->plot($data);
	return map { $_ =~ s/\*/█/g; $_ } map { join '', map { $_, $_ } @$_ } @$arrayref;
}

sub align_qrs
{
	my ($qr1, $qr2) = @_;
	return @$qr1 unless $qr2;

	my $offset = ' ' x (length $qr1->[0]);

	my @output;
	while (@$qr1 || @$qr2) {
		my $line = shift(@$qr1) // $offset;
		$line .= ' ' x 4;
		$line .= shift(@$qr2) // '';
		push @output, $line;
	}

	return @output;
}

binmode STDIN, ':encoding(UTF-8)';

my $stdout = !!0;
my $filename = 'wallet.txt';
my $auto_entropy = !!0;
my $help = !!0;
my $words = 24;
my $compat_addresses = 1;
my $segwit_addresses = 3;

GetOptions(
	'auto|a' => \$auto_entropy,
	'file|f=s' => \$filename,
	'stdout|o' => \$stdout,
	'words|w=n' => \$words,
	'compat_addrs|c=n' => \$compat_addresses,
	'segwit_addrs|s=n' => \$segwit_addresses,
	'help|h' => \$help,
);

my $interactive = !$stdout;

sub echo
{
	my ($message) = @_;

	say $message if $interactive;
	return;
}

sub prompt
{
	my ($info) = @_;
	echo $info;

	my $data = <STDIN>;
	chomp $data;

	return $data;
}

sub get_entropy
{
	return prompt "Enter any random entropy: by rolling dice, drawing cards or other means available";
}

sub get_passphrase
{
	return prompt 'Enter passphrase for your private key. Warning: plaintext!';
}

if ($help) {
	pod2usage(1);
}

if (!$stdout && -e $filename) {
	die "wallet file $filename already exists";
}

die 'words must be between 12 and 24'
	unless $words >= 12 && $words <= 24;
die 'words must be divisible by 3'
	unless $words % 3 == 0;
my $entropy_length = 128 + ($words - 12) * 32 / 3;

my $entropy = $auto_entropy ? undef : get_entropy;
my $pass = get_passphrase;

my $bitcoin_data = App::Bitcoin::PaperWallet->generate($entropy, $pass, {
	compat_addresses => $compat_addresses,
	segwit_addresses => $segwit_addresses,
	entropy_length => $entropy_length,
});

my @data;
my $id = substr $bitcoin_data->{addresses}[0], -4;

push @data,
	"-- PASSWORD PROTECTED PRIVATE KEY FOR ID $id --",
	$bitcoin_data->{mnemonic},
	'',
	"-- ADDRESSES FOR ID $id --",
	''
;

my @qrs;
for my $addr (@{$bitcoin_data->{addresses}}) {
	push @data, $addr;
	push @qrs, [get_qr $addr];
}

push @data, '';

push @qrs, undef unless @qrs % 2 == 0;
for my $qr (pairs @qrs) {
	push @data,
		align_qrs(@$qr),
		'',
		''
	;
}

push @data, '[Generated ' . localtime->cdate . ']';

if ($stdout) {
	binmode STDOUT, ':encoding(UTF-8)';
	print join "\n", @data;
}
else {
	open my $fh, '>:utf8', $filename
		or die "cannot open $filename";

	print {$fh} join "\n", @data;

	close $fh
		or die "could not close $filename";
}

echo "done - see $filename";

__END__

=head1 NAME

paper-wallet - Script to generate a paper wallet file

=head1 SYNOPSIS

	paper-wallet [OPTIONS]

=head1 OPTIONS

=over

=item -a, --auto

Generate entropy automatically using cryptographically-secure pseudorandom
number generator.

=item -o, --stdout

Do not print to file, use standard output instead. The script will not behave
interactively and instead will wait for data on standard input without
prompting anything. Best used with C<--auto> and a single standard input line
for password, or two lines: one for entropy, one for password

=item -f [FILE], --file [FILE]

Specify filename to print to - default is C<wallet.txt>. Has no effect if
C<--stdout> is passed.

=item -w [NUMBER], --words [NUMBER]

A number of words to be generated. Must be 12, 15, 18, 21 or 24. Default 24.

=item -c [NUMBER], --compat_addrs [NUMBER]

A number of compat addresses to generate, by default 1.

=item -s [NUMBER], --segwit_addrs [NUMBER]

A number of segwit addresses to generate, by default 3.

=item -h, --help

Show this help message.

=back

=head1 DESCRIPTION

This script will generate a file in your current working directory that
contains Bitcoin wallet details ready to be printed, written down 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).

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.

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.

After printing, you can cut off the top section (mnemonic seed) and store it
apart from the addresses for safety. You can also write the mnemonic seed down
by hand and only print the addresses part.

=head2 Security

With Bitcoin, information is money. You should be wary (or even paranoid) of
possible dangers during generation of your wallet.

If you want maximum safety, just buy a hardware wallet instead. Using a custom
solution like this one requires you to have enough expertise to be a hardware
wallet yourself. It can be a cool experience, but can also cost you money.

You should not depend on your passphrase and take securing your keys seriously.
Keep your passphrase rather simple, as its only purpose is to buy you time in
case of a leak. The danger of forgetting your passphrase is very real and with
a long passphrase, it can be very hard to recover.

For increased security, follow these guidelines:

=over

=item * Make sure your environment is as safe as possible

Scan your machine for malware. Check if you have any suspicious processes
running. Best, use security-focused system like OpenBSD.

=item * Disable Internet connection during generation

If your seed is going to leak, it will do so through the Web. As long as you
have the seed on your computer, avoid being online.

=item * Only store the seed physically

Paper or metal are unhackable. If they are stolen, you can move the funds
elsewhere before they crack your password - as long as you keep the second copy
in a different location.

=item * Don't print the seed, write it down

It is especially true if you have a printer which is connected to your local
network. Regular USB-only printers should be fine, but you never know if they
keep copies of what they printed somewhere. Never ever use printers you have at
work.

=item * Remove the seed from your computer when you're done

=back

=head3 Common pitfalls

Even if you safely generated the seed, you might still put it at risk without even
knowing:

=over

=item * If you opened a file with a program, it might have saved a draft copy somewhere

=item * Even if you deleted the file, it may still stick around in Trash

=item * Some systems may synchronize contents of a directory with the cloud

=item * If you forget your password, there's no way to recover it

=back

