This commit is contained in:
hashbro
2026-08-24 06:23:07 +08:00
parent c00396dc1f
commit db574cc629
114 changed files with 108297 additions and 266 deletions
+70
View File
@@ -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);
}
}
+126
View File
@@ -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);
}
}
+79
View File
@@ -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;
}
}
+21
View File
@@ -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)
);
}
}
+4 -4
View File
@@ -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');
});
}
}
+38
View File
@@ -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']);
}
}
+16
View File
@@ -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'));
}
}