HEAT-Crypto-X25519

 view release on metacpan or  search on metacpan

lib/HEAT/Crypto.pm  view on Meta::CPAN

#!/usr/bin/perl

package HEAT::Crypto;

use strict;
use warnings;

use Carp;
use HEAT::Crypto::X25519;
use Crypt::Mode::CBC;

use Exporter qw(import);
our @EXPORT_OK = qw(hash heat_key get_private_key keygen priv_to_pub_key
	shared_key sign verify encrypt decrypt account_id random_bytes tohex unhex);

our $VERSION = '0.04';

my $cbc = Crypt::Mode::CBC->new('AES');
*hash = \&HEAT::Crypto::X25519::hash;

sub tohex($)
{
	return undef unless defined $_[0];
	unpack('H*', $_[0]);
}

sub unhex($)
{
	return undef unless defined $_[0];
	pack('H*', $_[0]);
}

sub heat_key($)
{
	my $spec = shift;

	unless (defined $spec) {
		croak('undefined key spec');
	} elsif (length $spec == HEAT::Crypto::X25519::KEYSIZE) {
		return $spec;
	} elsif ($spec =~ /^[[:xdigit:]]{64}$/) {
		return unhex($spec);
	} else {
		croak('invalid key spec: %q', $spec);
	}
}

sub get_private_key($)
{
	my $spec = shift;

	if ($spec =~ /^([a-z]{3,12}( |\Z)){12}$/) {
		return HEAT::Crypto::X25519::keyhash($spec);
	} else {
		return heat_key($spec);
	}
}

sub priv_to_pub_key($)
{
	my $p = get_private_key($_[0]);
	my $r = HEAT::Crypto::X25519::keygen($p);
	return tohex($r->{p});
}

sub account_id($)
{
	my $k = heat_key($_[0]);
	my $h = hash($k);

	my ($id, $t1, $t2) = (0);

	for (my $i = 7; $i >= 0; $i--) {
		$t1 = $id * 256;
		$t2 = $t1 + vec($h, $i, 8);
		$id = $t2;
	}

	return $id;
}

sub random_bytes(;$)
{
	my $n = $_[0] || 32;
	my $b = '';

	while (length $b < $n)
	{
		$b .= pack('i', int rand(0xffffffff));
	}

	return $b;
}

sub keygen(;$)
{
	my $k = shift // random_bytes(HEAT::Crypto::X25519::KEYSIZE);
	my $r = HEAT::Crypto::X25519::keygen($k);

	return {
		p => tohex($r->{p}),
		s => tohex($r->{s}),
		k => tohex($r->{k}),
	};
}

sub shared_key($$)
{
	my $k = get_private_key($_[0]);
	my $p = heat_key($_[1]);

	return tohex(HEAT::Crypto::X25519::shared($k, $p));
}

sub sign($$)
{
	my $k = get_private_key($_[0]);
	my $msg = $_[1];
	return tohex(HEAT::Crypto::X25519::sign($k, $msg));
}

sub verify($$$)
{
	my ($s, $m) = @_;
	my $k = heat_key($_[2]);

	unless (defined $s) {
		croak('undefined signature');
	} elsif ($s =~ /^[[:xdigit:]]{128}$/) {
		$s = unhex($s);
	} elsif (length $s != 64) {
		croak('invalid signature: %q', $s);
	}

	HEAT::Crypto::X25519::verify($s, $m, $k);
}

sub encrypt($$;$)
{
	my ($data, $k, $p) = @_;

	my $key = @_ == 3 ? unhex(shared_key($k, $p)) : heat_key($k);

	my $iv = random_bytes(16);
	my $nonce = random_bytes(32);

	for (my $i = 0; $i < 32; $i++) {
		vec($key, $i, 8) = vec($key, $i, 8) ^ vec($nonce, $i, 8);
	}

	my $encrypted = eval { $cbc->encrypt($data, hash($key), $iv) };
	return undef if $@;

	return wantarray ? ($nonce, $iv, $encrypted) : $nonce . $iv . $encrypted;
}

sub decrypt($$;$)
{
	my ($data, $k, $p) = @_;

	my $key = @_ == 3 ? unhex(shared_key($k, $p)) : heat_key($k);

	my ($nonce, $iv, $encrypted) = ref($data) eq 'ARRAY'
		? @{$data} : unpack('a32 a16 a*', $data);

	for (my $i = 0; $i < 32; $i++) {
		vec($key, $i, 8) = vec($key, $i, 8) ^ vec($nonce, $i, 8);
	}

	my $decrypted = eval { $cbc->decrypt($encrypted, hash($key), $iv) };
	return undef if $@;

	return $decrypted;
}

1;

__END__

=head1 NAME

HEAT::Crypto - HEAT cryptographic routines

=head1 SYNOPSIS

  use HEAT::Crypto qw(keygen shared_key sign verify encrypt decrypt);
 
  # generate public-private key pairs
  my $alice = keygen($seed);
  my $bob = keygen($seed);
 
  # compute shared secret
  my $secret = shared_key($alice->{k}, $bob->{p});
  shared_key($bob->{k}, $alice->{p}) eq $secret or die;
 
  # message signing and verifying
  my $signature = sign($alice->{k}, $message);
  verify($signature, $message, $alice->{p}) or die;
 
  # message encryption and decryption
  my $encrypted = encrypt($message, $secret);
  decrypt($encrypted, $secret) eq $message or die;

=head1 DESCRIPTION

This module provides HEAT compatible ECDH key agreement, signing and
encryption ported to perl from the HEAT SDK.

The functions below are not exported by default and need to be imported
explicitly:

=over 4

=item keygen()

=item keygen( $seed );

Generate a new key pair. It returns a hash with 3 values:

  {
    p => <public key bytes>,
    k => <private key bytes>,
    s => <signing key bytes>,
  }

=item shared_key( $private_key, $public_key );

Compute shared secret.

Returns the key as a hexadecimal string.

=item sign( $private_key, $message );

Sign message with the private key.

Returns the signature as a hexadecimal string.

=item verify( $signature, $message, $public_key );

Verifies the message signature against the public key.

Returns 1 on success.

=item encrypt( $data, $key );

Encrypts data with the given key.

In array context it returns the encryption nonce, initialization vector and
cypher text. In scalar context it concatenates them.

=item decrypt( $data, $key );

Decrypts data with the given key. Data is expected to be returned by encrypt();

It returns the decrypted data on success. This function might die in case of errors.

=item priv_to_pub_key( $private_key )

Derives the public key from the private key.

Returns the public key as a hexadecimal string.

=item account_id( $public_key )

Derives the account ID from the public key.

Returns an integer.

=back

=head1 AUTHOR

Toma Mazilu

=head1 LICENSE

This library is free software; you can redistribute it and/or modify it
under the same terms as Perl itself

=head1 SEE ALSO

=over

=item * L<https://github.com/heatcrypto/heat-sdk>

=back

=cut



( run in 1.764 second using v1.01-cache-2.11-cpan-13bb782fe5a )