
use strict;
use warnings;
use Test::More;


use Crypt::NaCl::Sodium qw(:utils);


my $key = join('', map { chr($_) }
    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
    0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
    0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f
);

my $nonce = join('', map { chr($_) }
    0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47
);

my $ad = join('', map { chr($_) }
    0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7 
);

my $msg = "Ladies and Gentlemen of the class of '99: If I could offer you "
    ."only one tip for the future, sunscreen would be it.";

my $expected_hex = join('', map { sprintf("%02x", $_) }
    0xd3,0x1a,0x8d,0x34,0x64,0x8e,0x60,0xdb
    ,0x7b,0x86,0xaf,0xbc,0x53,0xef,0x7e,0xc2
    ,0xa4,0xad,0xed,0x51,0x29,0x6e,0x08,0xfe
    ,0xa9,0xe2,0xb5,0xa7,0x36,0xee,0x62,0xd6
    ,0x3d,0xbe,0xa4,0x5e,0x8c,0xa9,0x67,0x12
    ,0x82,0xfa,0xfb,0x69,0xda,0x92,0x72,0x8b
    ,0x1a,0x71,0xde,0x0a,0x9e,0x06,0x0b,0x29
    ,0x05,0xd6,0xa5,0xb6,0x7e,0xcd,0x3b,0x36
    ,0x92,0xdd,0xbd,0x7f,0x2d,0x77,0x8b,0x8c
    ,0x98,0x03,0xae,0xe3,0x28,0x09,0x1b,0x58
    ,0xfa,0xb3,0x24,0xe4,0xfa,0xd6,0x75,0x94
    ,0x55,0x85,0x80,0x8b,0x48,0x31,0xd7,0xbc
    ,0x3f,0xf4,0xde,0xf0,0x8e,0x4b,0x7a,0x9d
    ,0xe5,0x76,0xd2,0x65,0x86,0xce,0xc6,0x4b
    ,0x61,0x16,0x1a,0xe1,0x0b,0x59,0x4f,0x09
    ,0xe2,0x6a,0x7e,0x90,0x2e,0xcb,0xd0,0x60
    ,0x06,0x91
);

my $expected_no_ad_hex = join('', map { sprintf("%02x", $_) }
    0xd3,0x1a,0x8d,0x34,0x64,0x8e,0x60,0xdb
    ,0x7b,0x86,0xaf,0xbc,0x53,0xef,0x7e,0xc2
    ,0xa4,0xad,0xed,0x51,0x29,0x6e,0x08,0xfe
    ,0xa9,0xe2,0xb5,0xa7,0x36,0xee,0x62,0xd6
    ,0x3d,0xbe,0xa4,0x5e,0x8c,0xa9,0x67,0x12
    ,0x82,0xfa,0xfb,0x69,0xda,0x92,0x72,0x8b
    ,0x1a,0x71,0xde,0x0a,0x9e,0x06,0x0b,0x29
    ,0x05,0xd6,0xa5,0xb6,0x7e,0xcd,0x3b,0x36
    ,0x92,0xdd,0xbd,0x7f,0x2d,0x77,0x8b,0x8c
    ,0x98,0x03,0xae,0xe3,0x28,0x09,0x1b,0x58
    ,0xfa,0xb3,0x24,0xe4,0xfa,0xd6,0x75,0x94
    ,0x55,0x85,0x80,0x8b,0x48,0x31,0xd7,0xbc
    ,0x3f,0xf4,0xde,0xf0,0x8e,0x4b,0x7a,0x9d
    ,0xe5,0x76,0xd2,0x65,0x86,0xce,0xc6,0x4b
    ,0x61,0x16,0x6a,0x23,0xa4,0x68,0x1f,0xd5
    ,0x94,0x56,0xae,0xa1,0xd2,0x9f,0x82,0x47
    ,0x72,0x16
);

my $crypto_aead = Crypt::NaCl::Sodium->aead();

my $secret = $crypto_aead->ietf_encrypt( $msg, $ad, $nonce, $key );
my $secret_hex = bin2hex($secret);
my $s_secret = "$secret"; # from byteslocker

is( length($secret), length($msg) + $crypto_aead->ABYTES(),
    "Encrypted message is of correct length");
is($secret_hex, $expected_hex, "...and correctly encrypted: $secret_hex");

my $decrypted = $crypto_aead->ietf_decrypt( $secret, $ad, $nonce, $key );

is( length($decrypted), length($msg),
    "Decrypted message is of correct length");
is($decrypted, $msg, "... and was correctly decrypted");

for my $i ( 0 .. length($secret) - 1 ) {
    my $c = ord(substr($s_secret, $i, 1));
    $c ^= ( $i + 1 );
    substr($s_secret, $i, 1, chr($c));

    eval {
        my $decrypted = $crypto_aead->ietf_decrypt( $s_secret, $ad, $nonce, $key );
    };

    like($@, qr/Message forged/, "Message cannot be forged (change at position: $i)");

    $c ^= ( $i + 1 );
    substr($s_secret, $i, 1, chr($c));
}

my $secret_no_ad = $crypto_aead->ietf_encrypt( $msg, '', $nonce, $key );
my $secret_no_ad_hex = bin2hex($secret_no_ad);

is( length($secret_no_ad), length($msg) + $crypto_aead->ABYTES(),
    "Encrypted message without additional data is of correct length");
is($secret_no_ad_hex, $expected_no_ad_hex, "...and correctly encrypted: $secret_no_ad_hex");

my $decrypted_no_ad = $crypto_aead->ietf_decrypt( $secret_no_ad, '', $nonce, $key );

is( length($decrypted_no_ad), length($msg),
    "Decrypted message without additional data is of correct length");
is($decrypted_no_ad, $msg, "... and was correctly decrypted");

ok($crypto_aead->KEYBYTES() > 0, "KEYBYTES > 0");
ok($crypto_aead->IETF_NPUBBYTES() > 0, "IETF_NPUBBYTES > 0");
ok($crypto_aead->ABYTES() > 0, "ABYTES > 0");

done_testing();

