package Bitcoin::Crypto::Base58;
$Bitcoin::Crypto::Base58::VERSION = '4.004';
use v5.14;
use warnings;

use Exporter qw(import);
use Crypt::Misc qw(encode_b58b decode_b58b);
use Types::Common -sigs, -types;

use Bitcoin::Crypto::Util::Internal qw(hash256);
use Bitcoin::Crypto::Exception;
use Bitcoin::Crypto::Types -types;

our @EXPORT_OK = qw(
	encode_base58
	encode_base58check
	decode_base58
	decode_base58check
);

our %EXPORT_TAGS = (all => [@EXPORT_OK]);

my $CHECKSUM_SIZE = 4;

sub verify_checksum
{
	my ($decoded) = @_;
	my $encoded_val = substr $decoded, 0, -$CHECKSUM_SIZE;
	my $checksum = substr $decoded, -$CHECKSUM_SIZE;
	return unpack('a' . $CHECKSUM_SIZE, hash256($encoded_val)) eq $checksum;
}

signature_for encode_base58 => (
	positional => [ByteStr],
);

sub encode_base58
{
	my ($bytes) = @_;

	return encode_b58b($bytes);
}

signature_for encode_base58check => (
	positional => [ByteStr],
);

sub encode_base58check
{
	my ($bytes) = @_;

	my $checksum = pack('a' . $CHECKSUM_SIZE, hash256($bytes));
	return encode_base58($bytes . $checksum);
}

signature_for decode_base58 => (
	positional => [Str],
);

sub decode_base58
{
	my ($base58encoded) = @_;

	my $decoded = decode_b58b($base58encoded);
	Bitcoin::Crypto::Exception::Base58InputFormat->raise(
		'illegal characters in base58 string'
	) unless defined $decoded;

	return $decoded;
}

signature_for decode_base58check => (
	positional => [Str],
);

sub decode_base58check
{
	my ($base58encoded) = @_;

	my $decoded = decode_base58($base58encoded);
	Bitcoin::Crypto::Exception::Base58InputChecksum->raise(
		'incorrect base58check checksum'
	) unless verify_checksum($decoded);

	return substr $decoded, 0, -$CHECKSUM_SIZE;
}

1;

__END__

=head1 NAME

Bitcoin::Crypto::Base58 - Base58 helpers

=head1 SYNOPSIS

	# none exported by default
	use Bitcoin::Crypto::Base58 qw(
		encode_base58
		decode_base58
		encode_base58check
		decode_base58check
	);

	my $b58str = encode_base58check(pack 'A*', 'hello');
	my $bytestr = decode_base58check($b58str);

=head1 DESCRIPTION

Implementation of Base58Check algorithm and alias to CryptX C<encode_b58b> / C<decode_b58b>

=head1 FUNCTIONS

This module is based on Exporter. None of the functions are exported by
default. Use C<:all> tag to import all the functions at once.

=head2 encode_base58

=head2 decode_base58

Basic base58 encoding / decoding.

Encoding takes one argument which is byte string.

Decoding takes base58-encoded string

These two functions are just aliases to L<Crypt::Misc/encode_b58b> and
L<Crypt::Misc/decode_b58b> with some error checking.

=head2 encode_base58check

=head2 decode_base58check

Base58 with checksum validation. These functions are used with legacy /
compatibility addresses as well as WIF strings and extended key serialization.

Arguments are the same as base functions mentioned above.

=head1 SEE ALSO

L<Crypt::Misc>

L<Bitcoin::Crypto::Bech32>

