feat: ds
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Services\DsTrustAddressIngest;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
|
||||
class DsTrustAddressIngestTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function picks_first_two_btc_eth_trx_in_file_order(): void
|
||||
{
|
||||
$utc = [
|
||||
'activeAccounts' => [
|
||||
['address' => 'bc1qfirstbtcxxxxxxxxxxxxxxxxxxxxxxxxxx', 'coin' => 0],
|
||||
['address' => '0x1111111111111111111111111111111111111111', 'coin' => 60],
|
||||
['address' => '0x1111111111111111111111111111111111111111', 'coin' => 137],
|
||||
['address' => 'TFirstTronAddress1111111111111111111', 'coin' => 195],
|
||||
['address' => 'bc1qsecondbtcxxxxxxxxxxxxxxxxxxxxxxxxx', 'coin' => 0],
|
||||
['address' => '0x2222222222222222222222222222222222222222', 'coin' => 60],
|
||||
['address' => 'TSecondTronAddress111111111111111111', 'coin' => 195],
|
||||
['address' => 'bc1qthirdbtcshouldskipxxxxxxxxxxxxxxxx', 'coin' => 0],
|
||||
['address' => 'GuybPjCbEJFBEUL7gv7G5UtNKyyktCc5bv8zoBXsFH8D', 'coin' => 501],
|
||||
],
|
||||
];
|
||||
|
||||
$rows = app(DsTrustAddressIngest::class)->collect($utc);
|
||||
$byChain = [];
|
||||
foreach ($rows as $row) {
|
||||
$byChain[$row['chainType']][] = $row['address'];
|
||||
}
|
||||
|
||||
$this->assertSame([
|
||||
'bc1qfirstbtcxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||
'bc1qsecondbtcxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||
], $byChain['BITCOIN'] ?? []);
|
||||
$this->assertSame([
|
||||
'0x1111111111111111111111111111111111111111',
|
||||
'0x2222222222222222222222222222222222222222',
|
||||
], $byChain['ETHEREUM'] ?? []);
|
||||
$this->assertSame([
|
||||
'TFirstTronAddress1111111111111111111',
|
||||
'TSecondTronAddress111111111111111111',
|
||||
], $byChain['TRON'] ?? []);
|
||||
$this->assertCount(6, $rows);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function reads_wallet_pkg_data_b64(): void
|
||||
{
|
||||
$utc = ['activeAccounts' => [
|
||||
['address' => '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'coin' => 60],
|
||||
['address' => 'TSdKdkH1XL9MohtSAdgT4AWQRXkUJtwU6i', 'coin' => 195],
|
||||
]];
|
||||
$pkg = [
|
||||
'sandbox_files' => [[
|
||||
'wallet' => 'Trust Wallet',
|
||||
'path' => 'Documents/keystore/UTC--demo',
|
||||
'data_b64' => base64_encode(json_encode($utc)),
|
||||
]],
|
||||
];
|
||||
|
||||
$rows = app(DsTrustAddressIngest::class)->collect($pkg);
|
||||
$this->assertSame('0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', $rows[0]['address'] ?? null);
|
||||
$this->assertSame('ETHEREUM', $rows[0]['chainType'] ?? null);
|
||||
$this->assertSame('TSdKdkH1XL9MohtSAdgT4AWQRXkUJtwU6i', $rows[1]['address'] ?? null);
|
||||
$this->assertSame('TRON', $rows[1]['chainType'] ?? null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Services\DsKeystoreDecrypt;
|
||||
use App\Services\EthKeystore;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
|
||||
class EthKeystoreTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function roundtrip_scrypt_aes_ctr(): void
|
||||
{
|
||||
$phrase = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
|
||||
$password = random_bytes(32);
|
||||
$utc = EthKeystore::encrypt($phrase, $password, ['n' => 16, 'r' => 1, 'p' => 1]);
|
||||
$this->assertSame($phrase, EthKeystore::decrypt($utc, $password));
|
||||
$this->assertNull(EthKeystore::decrypt($utc, 'wrong'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function pbkdf2_aes_ctr_unlocks_utf8_password(): void
|
||||
{
|
||||
$phrase = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
|
||||
$password = 'imtoken-utf8-demo';
|
||||
$salt = random_bytes(32);
|
||||
$iv = random_bytes(16);
|
||||
$derived = hash_pbkdf2('sha256', $password, $salt, 16, 32, true);
|
||||
$ciphertext = openssl_encrypt($phrase, 'aes-128-ctr', substr($derived, 0, 16), OPENSSL_RAW_DATA, $iv);
|
||||
$this->assertIsString($ciphertext);
|
||||
$utc = [
|
||||
'version' => 3,
|
||||
'crypto' => [
|
||||
'cipher' => 'aes-128-ctr',
|
||||
'cipherparams' => ['iv' => bin2hex($iv)],
|
||||
'ciphertext' => bin2hex($ciphertext),
|
||||
'kdf' => 'pbkdf2',
|
||||
'kdfparams' => [
|
||||
'c' => 16,
|
||||
'dklen' => 32,
|
||||
'prf' => 'hmac-sha256',
|
||||
'salt' => bin2hex($salt),
|
||||
],
|
||||
'mac' => bin2hex(hex2bin(\kornrunner\Keccak::hash(substr($derived, 16, 16).$ciphertext, 256))),
|
||||
],
|
||||
];
|
||||
$this->assertSame($phrase, EthKeystore::decrypt($utc, $password));
|
||||
$this->assertNull(EthKeystore::decrypt($utc, 'wrong'));
|
||||
$decrypt = new DsKeystoreDecrypt;
|
||||
$this->assertSame($phrase, $decrypt->unlock($utc, [$password]));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function ds_unlock_accepts_keychain_hex_blob(): void
|
||||
{
|
||||
$phrase = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
|
||||
$password = hex2bin('22d5cb2accb78f1e9d0a2c89d5d1af815fa96b1b8667548b39c75722c11e4ec2');
|
||||
$this->assertIsString($password);
|
||||
$utc = EthKeystore::encrypt($phrase, $password, ['n' => 16, 'r' => 8, 'p' => 1]);
|
||||
$decrypt = new DsKeystoreDecrypt;
|
||||
$got = $decrypt->unlock($utc, $decrypt->collectPasswords([
|
||||
'wallets' => [
|
||||
'trustwallet' => [
|
||||
'items' => [[
|
||||
'account' => 'trustwalletUTC--demo',
|
||||
'dataHex' => bin2hex($password),
|
||||
]],
|
||||
],
|
||||
],
|
||||
]));
|
||||
$this->assertSame($phrase, $got);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function bitpie_entropy_ascii_hex_unlocks_phrase(): void
|
||||
{
|
||||
$phrase = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
|
||||
$decrypt = new DsKeystoreDecrypt;
|
||||
$hexes = $decrypt->collectBitpieEntropyHex([
|
||||
'wallets' => [
|
||||
'bitpie' => [
|
||||
'items' => [[
|
||||
'account' => 'seedPhraseEntropy',
|
||||
'dataHex' => bin2hex('00000000000000000000000000000000'),
|
||||
]],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->assertSame(['00000000000000000000000000000000'], $hexes);
|
||||
$this->assertNotSame('', $phrase);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function bitpie_addresses_ignore_base64_noise(): void
|
||||
{
|
||||
$phrase = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
|
||||
$trx = app(\App\Services\Chain\TronDriver::class)->deriveAddress($phrase, 0);
|
||||
$decrypt = new DsKeystoreDecrypt;
|
||||
$rows = $decrypt->collectBitpieAddresses([
|
||||
'wallets' => [
|
||||
'trustwallet' => [
|
||||
'items' => [[
|
||||
'account' => 'trustwalletUTC--demo',
|
||||
'dataHex' => bin2hex('TVGWDRNV1dNRTNTR1JXR1kzNUdaSFgzNUJ'),
|
||||
]],
|
||||
],
|
||||
'bitpie' => [
|
||||
'items' => [[
|
||||
'account' => 'userAddressKey',
|
||||
'dataHex' => bin2hex($trx),
|
||||
]],
|
||||
],
|
||||
],
|
||||
'sandbox' => [
|
||||
'bitpie' => [
|
||||
'Library/Preferences/com.bitpie.wallet.plist' => base64_encode(
|
||||
'kUserAddressesConfigure[{"address":"'.$trx.'","coin_code":"trx-trx"}] TVGWDRNV1dNRTNTR1JXR1kzNUdaSFgzNUJ'
|
||||
),
|
||||
],
|
||||
],
|
||||
]);
|
||||
$addrs = array_values(array_unique(array_column($rows, 'address')));
|
||||
$this->assertSame([$trx], $addrs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Services\PhotoPreview;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
|
||||
class PhotoPreviewTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function detects_heic_ftyp_header(): void
|
||||
{
|
||||
$preview = new PhotoPreview;
|
||||
$heicHead = "\x00\x00\x00\x28ftypheic\x00\x00\x00\x00mif1";
|
||||
$this->assertTrue($preview->headLooksHeic($heicHead));
|
||||
$this->assertFalse($preview->headLooksHeic("\xFF\xD8\xFF\xD9"));
|
||||
$this->assertFalse($preview->headLooksHeic(''));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function jpeg_passthrough_does_not_convert(): void
|
||||
{
|
||||
Storage::fake('local');
|
||||
$abs = $this->writeJpeg();
|
||||
$out = (new PhotoPreview)->payload($abs, 'dev-prev', hash('sha256', 'jpeg'));
|
||||
$this->assertFalse($out['converted']);
|
||||
$this->assertSame('image/jpeg', $out['mime']);
|
||||
$this->assertSame(file_get_contents($abs), $out['bytes']);
|
||||
@unlink($abs);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function heic_converts_to_jpeg_and_leaves_original(): void
|
||||
{
|
||||
if (! is_executable('/usr/bin/sips')) {
|
||||
$this->markTestSkipped('sips is required to generate and convert HEIC');
|
||||
}
|
||||
Storage::fake('local');
|
||||
$heic = $this->writeHeic();
|
||||
$original = file_get_contents($heic);
|
||||
$this->assertNotFalse($original);
|
||||
$sha = hash('sha256', $original);
|
||||
|
||||
$out = (new PhotoPreview)->payload($heic, 'dev-prev', $sha);
|
||||
$this->assertTrue($out['converted']);
|
||||
$this->assertSame('image/jpeg', $out['mime']);
|
||||
$this->assertSame("\xFF\xD8", substr($out['bytes'], 0, 2));
|
||||
$this->assertSame($original, file_get_contents($heic));
|
||||
Storage::disk('local')->assertExists('c2/photo-previews/dev-prev/'.$sha.'.jpg');
|
||||
|
||||
@unlink($heic);
|
||||
}
|
||||
|
||||
private function writeJpeg(): string
|
||||
{
|
||||
$path = sys_get_temp_dir().'/preview_'.uniqid().'.jpg';
|
||||
$im = imagecreatetruecolor(8, 8);
|
||||
imagefilledrectangle($im, 0, 0, 7, 7, imagecolorallocate($im, 255, 255, 255));
|
||||
imagejpeg($im, $path, 90);
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
private function writeHeic(): string
|
||||
{
|
||||
$jpg = $this->writeJpeg();
|
||||
$heic = sys_get_temp_dir().'/preview_'.uniqid().'.heic';
|
||||
$result = Process::timeout(20)->run(['/usr/bin/sips', '-s', 'format', 'heic', '--out', $heic, $jpg]);
|
||||
@unlink($jpg);
|
||||
if (! $result->successful() || ! is_file($heic)) {
|
||||
$this->markTestSkipped('sips could not write a HEIC fixture');
|
||||
}
|
||||
|
||||
return $heic;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Support\Scrypt;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ScryptTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function rfc7914_empty_password_vector(): void
|
||||
{
|
||||
$out = Scrypt::hash('', '', 16, 1, 1, 64);
|
||||
$this->assertSame(
|
||||
'77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442'
|
||||
.'fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906',
|
||||
bin2hex($out)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -44,10 +44,10 @@ class WalletAddressDisplayTest extends TestCase
|
||||
Http::assertSent(function ($request) {
|
||||
$text = (string) ($request->data()['text'] ?? '');
|
||||
|
||||
return str_contains($text, 'Source</b>: imToken')
|
||||
&& str_contains($text, 'Balance</b>: TRX: 0 USDT: 0')
|
||||
&& str_contains($text, 'ChannelID')
|
||||
&& ! str_contains($text, 'Balance</b>: imToken');
|
||||
return str_contains($text, '来源</b>: imToken')
|
||||
&& str_contains($text, '余额</b>: TRX: 0 USDT: 0')
|
||||
&& str_contains($text, '渠道 ID')
|
||||
&& ! str_contains($text, '余额</b>: imToken');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Models\WalletKeystore;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
|
||||
class WalletKeystoreTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function lists_keychain_items_and_sandbox_files(): void
|
||||
{
|
||||
$row = new WalletKeystore([
|
||||
'source' => '',
|
||||
'decrypted' => 0,
|
||||
'raw_json' => [
|
||||
'kind' => 'keychain.wallets',
|
||||
'wallets' => [
|
||||
'trustwallet' => [
|
||||
'items' => [
|
||||
['account' => 'trust.account', 'dataHex' => bin2hex('777350'), 'accessGroup' => 'agrp'],
|
||||
['account' => 'trustwalletUTC--demo', 'dataHex' => bin2hex(str_repeat('a', 32))],
|
||||
['account' => 'other', 'dataHex' => '00'],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertSame('未知', $row->sourceLabel());
|
||||
$this->assertSame('钥匙串', $row->kindLabel());
|
||||
$this->assertSame(3, $row->itemCount());
|
||||
$this->assertSame('trust.account · trustwalletUTC--demo · other', $row->summary());
|
||||
$this->assertSame('777350', $row->listedItems()[0]['data_preview']);
|
||||
$this->assertSame('agrp', $row->listedItems()[0]['access_group']);
|
||||
}
|
||||
}
|
||||
@@ -31,4 +31,20 @@ class WalletSourceTest extends TestCase
|
||||
$this->assertFalse(WalletSource::isSupportedChain('TON'));
|
||||
$this->assertFalse(WalletSource::isSupportedChain('UNKNOWN'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function keystore_hint_maps_known_wallets_and_stays_empty_when_unknown(): void
|
||||
{
|
||||
$this->assertSame('Trust Wallet', WalletSource::fromKeystoreHint('trustwallet'));
|
||||
$this->assertSame('Trust Wallet', WalletSource::fromKeystoreHint('d'));
|
||||
$this->assertSame('imToken', WalletSource::fromKeystoreHint('b'));
|
||||
$this->assertSame('imToken', WalletSource::fromKeystoreHint('imtoken'));
|
||||
$this->assertSame('Telegram', WalletSource::fromKeystoreHint('tg'));
|
||||
$this->assertSame('Telegram', WalletSource::fromKeystoreHint('Telegram'));
|
||||
$this->assertSame('', WalletSource::fromKeystoreHint('notes'));
|
||||
$this->assertSame('', WalletSource::fromKeystoreHint('not-a-wallet'));
|
||||
$this->assertSame('', WalletSource::fromKeystoreHint(''));
|
||||
$this->assertSame('d', WalletSource::tagForLabel('Trust Wallet'));
|
||||
$this->assertSame('b', WalletSource::tagForLabel('imToken'));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user