DigiByte-DigiID
view release on metacpan or search on metacpan
lib/DigiByte/DigiID.pm view on Meta::CPAN
our @EXPORT_OK = qw(
extract_nonce
get_qrcode
verify_signature
);
use Crypto::ECC;
use Crypt::OpenPGP::Digest; ## RIPEMD160
use Data::UUID;
use Digest::SHA qw(sha256);
use Math::BigInt lib => 'GMP';
use MIME::Base64 qw(decode_base64);
use String::Pad qw(pad);
use URI::Escape qw(uri_escape);
my $STR_PAD_LEFT = 'l';
my %SECP256K1 = (
a => 00,
b => 07,
prime =>
'0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F',
x => '0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798',
y => '0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8',
order =>
'0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141',
);
sub get_qrcode {
my ( $server_domain, %options ) = @_;
my $nonce = $options{nonce} // Data::UUID->new->create_hex;
my $path = $options{path} // '/callback';
my $url = "digiid://$server_domain$path?x=$nonce";
if ( $options{nossl} ) {
$url .= '&u=1';
}
my $str = uri_escape($url);
return (
nonce => $nonce,
callback => $url,
image =>
"https://chart.googleapis.com/chart?chs=200x200&cht=qr&chl=$str",
);
}
sub extract_nonce {
my ($uri) = @_;
my ($nonce) = ( $uri =~ m/[\?\&]x=([^\&]+)/ );
return $nonce;
}
sub verify_signature {
my ( $address, $signature, $message, $testnet ) = @_;
my $decoded_address = _base58check_decode( $address, $testnet );
my @decoded_address = split //, $decoded_address;
if ( length($decoded_address) != 21
|| ( $decoded_address[0] ne "\x1E" && !$testnet )
|| ( $decoded_address[0] ne "\x6F" && $testnet ) )
{
die "invalid DigiByte address";
}
my $decoded_signature = decode_base64($signature);
my @decoded_signature = split //, $decoded_signature;
if ( length($decoded_signature) != 65 ) {
die "invalid signature";
}
my $recovery_flags = ord( $decoded_signature[0] ) - 27;
if ( $recovery_flags < 0 || $recovery_flags > 7 ) {
die "invalid signature type";
}
my $is_compressed = ( $recovery_flags & 4 ) != 0;
my $message_hash = sha256(
sha256(
"\x19DigiByte Signed Message:\n"
. _num_to_var_int_string( length($message) )
. $message
)
);
my $pubkey = do {
my $r = _bin2gmp( substr( $decoded_signature, 1, 32 ) );
my $s = _bin2gmp( substr( $decoded_signature, 33, 32 ) );
my $e = _bin2gmp($message_hash);
my $g = $Point->new(%SECP256K1);
_recover_pubkey( $r, $s, $e, $recovery_flags, $g );
};
if ( !$pubkey ) {
die 'unable to recover key';
}
my $point = $pubkey->point;
my $pub_bin_str;
## see that the key we recovered is for the address given
if ($is_compressed) {
$pub_bin_str = ( _is_bignum_even( $point->y ) ? "\x02" : "\x03" )
. pad( _gmp2bin( $point->x ), 32, $STR_PAD_LEFT, "\x00" );
}
else {
$pub_bin_str = "\x04"
. pad( _gmp2bin( $point->x ), 32, $STR_PAD_LEFT, "\x00" )
. pad( _gmp2bin( $point->y ), 32, $STR_PAD_LEFT, "\x00" );
}
my $ripemd160 = Crypt::OpenPGP::Digest->new('RIPEMD160');
my $derived_address;
if ($testnet) {
$derived_address = "\x6F" . $ripemd160->hash( sha256($pub_bin_str) );
}
else {
$derived_address = "\x1E" . $ripemd160->hash( sha256($pub_bin_str) );
}
return $decoded_address eq $derived_address;
}
sub _base58check_decode {
my ( $address, $testnet ) = @_;
my $decoded_address = $address;
$decoded_address =~
tr{123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz}
{0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv};
$decoded_address =~ s/^0+//;
my $v = Math::BigInt->from_base( $decoded_address, 58 );
$v = _gmp2bin($v);
foreach my $chr ( split //, $address ) {
if ( $chr ne '1' ) {
last;
}
if ($testnet) {
$v = "\x6F$v";
}
else {
$v = "\x00$v";
}
}
my $checksum = substr $v, -4;
$v = substr $v, 0, -4;
my $exp_check_sum = substr sha256( sha256($v) ), 0, 4;
if ( $exp_check_sum ne $checksum ) {
die "Invalid checksum";
}
return $v;
}
sub _num_to_var_int_string {
my ($i) = @_;
if ( $i < 0xfd ) {
return chr($i);
}
elsif ( $i <= 0xffff ) {
return pack( 'Cv', 0xfd, $i );
}
elsif ( $i <= 0xffffffff ) {
return pack( 'CV', 0xfe, $i );
}
else {
die 'int too large';
}
}
sub _bin2gmp {
my ($bin_str) = @_;
my $v = Math::BigInt->new(0);
foreach my $ch ( split //, $bin_str ) {
$v *= 256;
$v += ord $ch;
}
return $v;
}
sub _gmp2bin {
my ($v) = @_;
( run in 0.582 second using v1.01-cache-2.11-cpan-0b5f733616e )