use strict;
use warnings;

use Test::More;

plan skip_all => "JSON module not installed" unless eval { require JSON };
plan tests => 63;

use Crypt::PK::X25519;
use Crypt::Misc qw(read_rawfile);

{
  my ($k, $k2);

  # t/data/openssl_x25519_sk.pem
  # X25519 Private-Key:
  # priv = 002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651 == AC-T0Qulco2N2OlSdyHaujJhwLsb7957S72sYx1FRlE
  # pub  = EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41 == 6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE

  my $sk_data = pack("H*", "002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651");
  $k = Crypt::PK::X25519->new->import_key_raw($sk_data, 'private');
  ok($k, 'new+import_key_raw raw-priv');
  ok($k->is_private, 'is_private raw-priv');
  is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} raw-priv');
  is(uc($k->key2hash->{pub}),  'EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41', 'key2hash->{pub} raw-priv');
  is($k->export_key_raw('private'), $sk_data, 'export_key_raw private');

  $k2 = Crypt::PK::X25519->new->import_key($k->key2hash);
  ok($k2->is_private, 'is_private raw-priv');
  is($k->export_key_der('private'), $k2->export_key_der('private'), 'import_key hash');

  my $pk_data = pack("H*", "EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41");
  $k = Crypt::PK::X25519->new->import_key_raw($pk_data, 'public');
  ok($k, 'new+import_key_raw raw-pub');
  ok(!$k->is_private, '!is_private raw-pub');
  is(uc($k->key2hash->{pub}),  'EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41', 'key2hash->{pub} raw-pub');
  is($k->export_key_raw('public'), $pk_data, 'export_key_raw public');

  $k2 = Crypt::PK::X25519->new->import_key($k->key2hash);
  ok(!$k2->is_private, 'is_private raw-priv');
  is($k->export_key_der('public'), $k2->export_key_der('public'), 'import_key hash');

  my $sk_jwk = { kty=>"OKP",crv=>"X25519",d=>"AC-T0Qulco2N2OlSdyHaujJhwLsb7957S72sYx1FRlE",x=>"6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE" };
  $k = Crypt::PK::X25519->new($sk_jwk);
  ok($k, 'new JWKHASH/priv');
  ok($k->is_private, 'is_private JWKHASH/priv');
  is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} JWKHASH/priv');
  ok(eq_hash($sk_jwk, $k->export_key_jwk('private', 1)), 'JWKHASH export private');

  my $pk_jwk = { kty=>"OKP",crv=>"X25519",x=>"6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE"};
  $k = Crypt::PK::X25519->new($pk_jwk);
  ok($k, 'new JWKHASH/pub');
  ok(!$k->is_private, '!is_private JWKHASH/pub');
  is(uc($k->key2hash->{pub}), 'EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41', 'key2hash->{pub} JWKHASH/pub');
  ok(eq_hash($pk_jwk, $k->export_key_jwk('public', 1)), 'JWKHASH export public');

  $k = Crypt::PK::X25519->new('t/data/jwk_x25519-priv1.json');
  ok($k, 'new JWK/priv');
  ok($k->is_private, 'is_private JWK/priv');
  is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} JWK/priv');

  $k = Crypt::PK::X25519->new('t/data/jwk_x25519-pub1.json');
  ok($k, 'new JWK/pub');
  ok(!$k->is_private, '!is_private JWK/pub');
  is(uc($k->key2hash->{pub}), 'EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41', 'key2hash->{pub} JWK/pub');

  $k = Crypt::PK::X25519->new('t/data/openssl_x25519_sk.der');
  ok($k, 'new openssl_x25519_sk.der');
  ok($k->is_private, 'is_private openssl_x25519_sk.der');
  is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} openssl_x25519_sk.der');

  $k = Crypt::PK::X25519->new('t/data/openssl_x25519_sk.pem');
  ok($k, 'new openssl_x25519_sk.pem');
  ok($k->is_private, 'is_private openssl_x25519_sk.pem');
  is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} openssl_x25519_sk.pem');

### the latest openssl does not support -traditional export format for x25519 anymore
# $k = Crypt::PK::X25519->new('t/data/openssl_x25519_sk_t.pem');
# ok($k, 'new openssl_x25519_sk_t.pem');
# ok($k->is_private, 'is_private openssl_x25519_sk_t.pem');
# is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} openssl_x25519_sk_t.pem');


  $k = Crypt::PK::X25519->new('t/data/openssl_x25519_sk.pkcs8');
  ok($k, 'new openssl_x25519_sk.pkcs8');
  ok($k->is_private, 'is_private openssl_x25519_sk.pkcs8');
  is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} openssl_x25519_sk.pkcs8');

  $k = Crypt::PK::X25519->new('t/data/openssl_x25519_sk_pbes1.pkcs8', 'secret');
  ok($k, 'new openssl_x25519_sk_pbes1.pkcs8');
  ok($k->is_private, 'is_private openssl_x25519_sk_pbes1.pkcs8');
  is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} openssl_x25519_sk_pbes1.pkcs8');

  $k = Crypt::PK::X25519->new('t/data/openssl_x25519_sk_pbes2.pkcs8', 'secret');
  ok($k, 'new openssl_x25519_sk_pbes2.pkcs8');
  ok($k->is_private, 'is_private openssl_x25519_sk_pbes2.pkcs8');
  is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} openssl_x25519_sk_pbes2.pkcs8');

  $k = Crypt::PK::X25519->new('t/data/openssl_x25519_sk_pw.pem', 'secret');
  ok($k, 'new openssl_x25519_sk_pw.pem');
  ok($k->is_private, 'is_private openssl_x25519_sk_pw.pem');
  is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} openssl_x25519_sk_pw.pem');

### the latest openssl does not support -traditional export format for x25519 anymore
# $k = Crypt::PK::X25519->new('t/data/openssl_x25519_sk_pw_t.pem', 'secret');
# ok($k, 'new openssl_x25519_sk_pw_t.pem');
# ok($k->is_private, 'is_private openssl_x25519_sk_pw_t.pem');
# is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} openssl_x25519_sk_pw_t.pem');

  $k = Crypt::PK::X25519->new('t/data/openssl_x25519_pk.pem');
  ok($k, 'new openssl_x25519_pk.pem');
  ok(!$k->is_private, '!is_private openssl_x25519_pk.pem');
  is(uc($k->key2hash->{pub}), 'EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41', 'key2hash->{pub} openssl_x25519_pk.pem');
}

{
  my $k = Crypt::PK::X25519->new;
  $k->generate_key;
  ok($k, 'generate_key');
  ok($k->is_private, 'is_private');
  ok($k->export_key_der('private'), 'export_key_der pri');
  ok($k->export_key_der('public'), 'export_key_der pub');
}

{
  for (qw( openssl_x25519_pk.der openssl_x25519_pk.pem )) {
    my $k = Crypt::PK::X25519->new("t/data/$_");
    is($k->export_key_der('public'), read_rawfile("t/data/$_"), 'export_key_der public') if (substr($_, -3) eq "der");
    is($k->export_key_pem('public'), read_rawfile("t/data/$_"), 'export_key_pem public') if (substr($_, -3) eq "pem");
  }

  for (qw( openssl_x25519_sk.der openssl_x25519_sk.pem )) {
    my $k = Crypt::PK::X25519->new("t/data/$_");
    is($k->export_key_der('private'), read_rawfile("t/data/$_"), 'export_key_der private') if (substr($_, -3) eq "der");
    is($k->export_key_pem('private'), read_rawfile("t/data/$_"), 'export_key_pem private') if (substr($_, -3) eq "pem");
  }
}

{
  my $sk1 = Crypt::PK::X25519->new;
  $sk1->import_key('t/data/openssl_x25519_sk.der');
  my $pk1 = Crypt::PK::X25519->new->import_key_raw($sk1->export_key_raw('public'), 'public');
  ok(!$pk1->is_private, '!is_private');

  my $sk2 = Crypt::PK::X25519->new;
  $sk2->generate_key;
  my $pk2 = Crypt::PK::X25519->new->import_key_raw($sk2->export_key_raw('public'), 'public');
  ok(!$pk2->is_private, '!is_private');

  my $ss1 = $sk1->shared_secret($pk2);
  my $ss2 = $sk2->shared_secret($pk1);
  is(unpack("H*",$ss1), unpack("H*",$ss2), 'shared_secret');
}


# rfc7748 test vectors
{
   my $sk = Crypt::PK::X25519->new->import_key_raw(pack("H*", "a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4"), 'private');
   my $pk = Crypt::PK::X25519->new->import_key_raw(pack("H*", "e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c"), 'public');
   is(unpack("H*", $sk->shared_secret($pk)), "c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552");
}
{
   my $sk = Crypt::PK::X25519->new->import_key_raw(pack("H*", "4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d"), 'private');
   my $pk = Crypt::PK::X25519->new->import_key_raw(pack("H*", "e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493"), 'public');
   is(unpack("H*", $sk->shared_secret($pk)), "95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957");
}
{
   my $sk = Crypt::PK::X25519->new->import_key_raw(pack("H*", "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a"), 'private');
   my $pk = Crypt::PK::X25519->new->import_key_raw(pack("H*", "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f"), 'public');
   is(unpack("H*", $sk->shared_secret($pk)), "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742");
}
{
   my $sk = Crypt::PK::X25519->new->import_key_raw(pack("H*", "5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb"), 'private');
   my $pk = Crypt::PK::X25519->new->import_key_raw(pack("H*", "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a"), 'public');
   is(unpack("H*", $sk->shared_secret($pk)), "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742");
}
