Blockchain-Ethereum

 view release on metacpan or  search on metacpan

t/Keystore/file.t  view on Meta::CPAN

#!/usr/bin/env perl

use strict;
use warnings;

use Test::More;
use File::Temp    qw(tempfile);
use JSON::MaybeXS qw(decode_json);
use Blockchain::Ethereum::Keystore::File;
use Blockchain::Ethereum::Key;

# Test data
my $private_key_hex   = "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d";
my $private_key_bytes = pack "H*", $private_key_hex;
my $password          = "testpassword";

subtest "from_file - v3 pbkdf2" => sub {
    my $keyfile = Blockchain::Ethereum::Keystore::File->from_file("./t/Keystore/resources/pbkdf2_v3.json", $password);

    isa_ok $keyfile,              'Blockchain::Ethereum::Keystore::File';
    isa_ok $keyfile->private_key, 'Blockchain::Ethereum::Key';
    is $keyfile->private_key->export, $private_key_bytes, 'private key matches';
    is $keyfile->password,            $password,          'password stored correctly';

    # Test against actual file data
    is $keyfile->version,    3,                                                                  'version is 3';
    is $keyfile->id,         '3198bc9c-6672-5ab3-d995-4942343ae5b6',                             'ID matches file data';
    is $keyfile->iv,         '6087dab2f9fdbbfaddc31a909735c1e6',                                 'IV matches file data';
    is $keyfile->ciphertext, '5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46', 'ciphertext matches file data';
    is $keyfile->mac,        '517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2', 'MAC matches file data';

    # Test KDF parameters
    isa_ok $keyfile->kdf, 'Blockchain::Ethereum::Keystore::KDF';
    is $keyfile->kdf->algorithm, 'pbkdf2',                                                           'KDF algorithm is pbkdf2';
    is $keyfile->kdf->dklen,     32,                                                                 'KDF dklen is correct';
    is $keyfile->kdf->c,         262144,                                                             'KDF iteration count is correct';
    is $keyfile->kdf->prf,       'hmac-sha256',                                                      'KDF PRF is correct';
    is $keyfile->kdf->salt,      'ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd', 'KDF salt matches file data';
};

subtest "from_file - v3 scrypt" => sub {
    my $keyfile = Blockchain::Ethereum::Keystore::File->from_file("./t/Keystore/resources/scrypt_v3.json", $password);

    isa_ok $keyfile,              'Blockchain::Ethereum::Keystore::File';
    isa_ok $keyfile->private_key, 'Blockchain::Ethereum::Key';
    is $keyfile->private_key->export, $private_key_bytes, 'private key matches';
    is $keyfile->password,            $password,          'password stored correctly';

    # Test against actual file data
    is $keyfile->version,    3,                                                                  'version is 3';
    is $keyfile->id,         '3198bc9c-6672-5ab3-d995-4942343ae5b6',                             'ID matches file data';
    is $keyfile->iv,         '83dbcc02d8ccb40e466191a123791e0e',                                 'IV matches file data';
    is $keyfile->ciphertext, 'd172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c', 'ciphertext matches file data';
    is $keyfile->mac,        '2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097', 'MAC matches file data';

    # Test KDF parameters
    isa_ok $keyfile->kdf, 'Blockchain::Ethereum::Keystore::KDF';
    is $keyfile->kdf->algorithm, 'scrypt',                                                           'KDF algorithm is scrypt';
    is $keyfile->kdf->dklen,     32,                                                                 'KDF dklen is correct';
    is $keyfile->kdf->n,         262144,                                                             'KDF n parameter is correct';
    is $keyfile->kdf->p,         8,                                                                  'KDF p parameter is correct';
    is $keyfile->kdf->r,         1,                                                                  'KDF r parameter is correct';
    is $keyfile->kdf->salt,      'ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19', 'KDF salt matches file data';
};

subtest "write_to_file - basic" => sub {
    my $key     = Blockchain::Ethereum::Key->new(private_key => $private_key_bytes);
    my $keyfile = Blockchain::Ethereum::Keystore::File->from_key($key);

    my ($fh, $filename) = tempfile();
    close $fh;

    eval { $keyfile->write_to_file($filename, $password) };
    ok !$@,          'write_to_file succeeds';
    ok -f $filename, 'file was created';

    # Verify we can read it back
    my $loaded = Blockchain::Ethereum::Keystore::File->from_file($filename, $password);
    is $loaded->private_key->export, $private_key_bytes, 'round-trip preserves key';

    unlink $filename;
};

subtest "error conditions - from_file" => sub {
    eval { Blockchain::Ethereum::Keystore::File->from_file("nonexistent.json", $password) };
    like $@, qr/No such file or directory/, 'from_file handles missing file';

    eval { Blockchain::Ethereum::Keystore::File->from_file("./t/Keystore/resources/scrypt_v3.json", "wrongpassword") };
    like $@, qr/Invalid password or corrupted keystore/, 'from_file validates password';
};

subtest "MAC verification" => sub {
    # Load a valid keystore
    my $keyfile      = Blockchain::Ethereum::Keystore::File->from_file("./t/Keystore/resources/scrypt_v3.json", $password);
    my $mac_original = $keyfile->mac;
    my $mac_new      = $keyfile->_generate_mac;
    like $mac_original, qr/^[0-9a-f]+$/i, 'original MAC has hex format';
    like $mac_new,      qr/^[0-9a-f]+$/i, 'new MAC has hex format';
    is $mac_new, $mac_original, 'MAC matches for valid keystore';
};

subtest "keystore format compliance" => sub {
    my $key     = Blockchain::Ethereum::Key->new(private_key => $private_key_bytes);
    my $keyfile = Blockchain::Ethereum::Keystore::File->from_key($key);

    my ($fh, $filename) = tempfile;
    close $fh;

    $keyfile->write_to_file($filename, $password);

    # Read the JSON directly to verify format
    my $json_content = do {
        open my $fh, '<', $filename or die $!;
        local $/;
        <$fh>;
    };

    my $json_data = decode_json($json_content);

    # Verify required fields
    is $json_data->{version}, 3, 'JSON has version 3';
    ok $json_data->{id},                 'JSON has id field';
    ok $json_data->{crypto},             'JSON has crypto field';
    ok $json_data->{crypto}{cipher},     'JSON has cipher field';
    ok $json_data->{crypto}{ciphertext}, 'JSON has ciphertext field';
    ok $json_data->{crypto}{mac},        'JSON has mac field';
    ok $json_data->{crypto}{kdf},        'JSON has kdf field';
    ok $json_data->{crypto}{kdfparams},  'JSON has kdfparams field';

    unlink $filename;
};

done_testing;



( run in 0.655 second using v1.01-cache-2.11-cpan-5837b0d9d2c )