Files
cryptowallet/README.md
ZOMBIIIIIII 9fe5311bbf init2222
2026-05-13 12:35:05 +03:00

140 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CryptoWallet API — Deployment Bundle
Multi-chain **custodial** wallet API (ETH / BSC / BTC / TRX / SOL).
- Сервер генерит mnemonic, хранит зашифрованной (AES-256-GCM, master-key из HashiCorp Vault)
- Сервер сам подписывает tx по запросу юзера (юзер на клиенте жмёт "подтвердить")
Auth — JWT, выданный сервисом **bitok** (внешний). Секреты — HashiCorp Vault (AppRole).
## Pre-deploy setup (один раз)
```bash
# 1. Master-key в Vault
vault kv put dev-secrets/crypto/master key=$(openssl rand -hex 32)
# 2. CSRF secret в Vault
vault kv put dev-secrets/csrf secret_key=$(openssl rand -hex 32) salt=csrf-salt digest=sha256
# 3. DB schema — APPEND-ONLY / NON-DESTRUCTIVE
# Безопасно прогонять на existing БД. См. ниже "Schema is non-destructive".
psql -h <db-host> -U postgres_user -d postgres -f cryptowallet-schema.sql
# 4. bitok public key в Vault (для kid из JWT header)
vault kv put dev-secrets/jwt/kid active=<kid-from-bitok>
vault kv put dev-secrets/jwt/kids/<kid-from-bitok> \
algorithm=RS256 \
public_key="$(cat /path/to/bitok-public.pem)"
```
⚠️ **Master-key менять нельзя** — все existing `encrypted_mnemonic` станут нерасшифровываемыми. API на старте делает self-test: пытается декриптить любой существующий mnemonic и фейлится если ключ не подошёл.
## Deploy
```bash
# Залить bundle на сервер
scp -P 2222 -r deployserver/ server@<host>:~/cryptowallet/
# На сервере: заполнить .env, поднять
ssh server@<host> -p 2222
cd ~/cryptowallet
cp .env.example .env
chmod 600 .env
nano .env # заполни VAULT_*, JWT_*, CORS_ORIGINS
./start.sh
```
В `.env` **обязательны**: `VAULT_ADDR`, `VAULT_ROLE_ID`, `VAULT_SECRET_ID`, `JWT_ISSUER=bitok`, `JWT_AUDIENCE`, `CORS_ORIGINS`.
## Update / Rebuild
```bash
scp -P 2222 -r deployserver/apps server@<host>:~/cryptowallet/
ssh server@<host> -p 2222 'cd cryptowallet && docker compose up -d --build'
```
## Endpoints
| Method | Path | Описание |
|---|---|---|
| GET | `/api/health` | Liveness (public) |
| GET | `/api/docs` | Swagger UI |
| GET | `/api/docs/swagger.json` | OpenAPI JSON |
| POST | **`/api/wallets/create`** | Сервер создаёт коша (no body, returns 5 addresses) |
| GET | `/api/wallets` | Список адресов юзера |
| POST | **`/api/wallets/mnemonic/reveal`** | Reveal seed (body confirm + 5/час rate-limit) |
| GET | `/api/wallets/{chain}/balance` | Баланс (native + все известные токены чейна) |
| GET | `/api/wallets/{chain}/transactions` | История tx |
| POST | **`/api/wallets/{chain}/send`** | Сервер подписывает + broadcast. Body: `{to, amount, token?, feeTier?}` |
| GET | **`/api/wallets/{chain}/gas-suggestions`** | Slow/normal/fast tiers (ETH/BSC, parsed из eth_feeHistory) |
| POST | **`/api/wallets/{chain}/sign-raw-evm-tx`** | Подписать произвольную EVM tx (для Relay/Swap execute responses) |
| — | `/api/btc/*` `/api/tron/*` `/api/sol/*` `/api/bsc/*` `/api/relay/*` | Proxy endpoints (swap quote/build, bridge quote/execute/status) |
## Security highlights
- **AES-256-GCM** для encrypted_mnemonic (12-byte random IV, 16-byte auth tag, fail-secure)
- **Master-key set-once** (rotation запрещена в коде)
- **Crypto self-test на старте** — fail-fast если master-key не декриптит existing mnemonics
- **Race-safe createWallet** — `db.transaction` + `UPDATE WHERE encrypted_mnemonic IS NULL` (set-once primitive)
- **Atomic erc20 update** — ETH-адрес кладётся в `users.erc20` внутри той же транзакции
- **TRX MITM defense** — local recompute txID + 4-layer raw_data verification перед подписью
- **EVM gas cap** 500 gwei (применён к tx, не только check)
- **EVM gas oracle** через `eth_feeHistory` p25/p50/p75 — minimum-but-works fees (BSC floor 0.05, ETH 0.5 gwei)
- **BTC fee** tier-based (slow=144 blocks, normal=6, fast=1) + floor 2 sat/vB
- **TRX fee_limit** cap 30 TRX (раньше 100, излишне)
- **Address checksum validation** (BTC bitcoinjs-lib, TRX bs58check, SOL PublicKey, EVM EIP-55)
- **assertAddressMatch** — derived(mnemonic, path) === DB.address перед подписью
- **SOL confirmTransaction** — ждём подтверждения сети
- **BTC** P2WPKH bech32, dust 294, broadcast 20s timeout
- **POST mnemonic/reveal** + CSRF + body confirm token + 5/час rate-limit + audit-log
- **Logger sanitization** — password/token/mnemonic/hex64/BIP39-phrase patterns маскируются
- **Audit log в stdout** (структурированный JSON с `"level":"audit"`) — `wallet.create` / `wallet.send` / `mnemonic.reveal` / `wallet.sign_raw_evm`
- **Hourly key rotation** — JWT keys + CSRF secret из Vault (master-key НЕ ротируется)
- **Fail-fast** — сервис не стартует без master-key, JWT_ISSUER, JWT_AUDIENCE
- **Container hardening** — read-only fs, cap_drop ALL, no-new-privileges, pids/mem/cpu limits, non-root uid 1001, loopback-only port
- **Relay proxy** whitelist method+path — `/quote` (POST), `/intents/status/v3` (GET), `/execute/{swap|bridge}` (POST). Никаких freeform action'ов
- **Bridge UI dropdown** ETH/BSC/SOL only (TRX через Relay убран — плохая ликвидность)
## Schema is non-destructive
`cryptowallet-schema.sql` **append-only**. Re-run на боксе с уже настроенной БД = **zero DDL changes**. Если оператор добавил кастомные таблицы / индексы / constraints вручную — они **никогда** не будут перезаписаны или удалены.
Что делает script:
- `CREATE TABLE IF NOT EXISTS users` / `wallets`
- `ALTER TABLE users ADD COLUMN <X>` (только если колонки нет — `encrypted_mnemonic`, `erc20`, `passport_data`)
- `CREATE UNIQUE INDEX users_email_lower_unique` (если индекса нет)
- `CREATE INDEX idx_users_active` / `idx_wallets_*` (если индексов нет)
- `ADD CONSTRAINT` × 4 (только если данного constraint name нет)
Что script **НЕ делает**:
- ❌ Никогда не `DROP TABLE`
- ❌ Никогда не `DROP CONSTRAINT`
- ❌ Никогда не `DROP COLUMN`
- ❌ Никогда не перезаписывает существующие constraints / indexes
Legacy cleanup (audit_log, idempotency_keys, sessions от старых версий) — **manual one-time** операторская задача, не часть этого script'а:
```bash
psql ... -c "DROP TABLE IF EXISTS audit_log CASCADE;"
psql ... -c "DROP TABLE IF EXISTS idempotency_keys CASCADE;"
psql ... -c "DROP TABLE IF EXISTS sessions CASCADE;"
```
## Logs
Файловых логов **нет**. Весь код пишет в `process.stdout` (см. `apps/api/src/lib/logger.ts` и `lib/audit-log.ts`). Docker подбирает stdout через json-file driver и показывает через `docker compose logs`:
```bash
docker compose logs -f api # все логи (structured JSON)
docker compose logs api | grep '"level":"audit"' # только audit events
docker compose logs api | grep '"level":"ERROR"' # только ошибки
```
## Production hardening checklist (опционально)
- [ ] Vault server-mode (raft/file backend) с unseal flow
- [ ] TLS termination на reverse-proxy (Caddy / Nginx) перед `127.0.0.1:3001`
- [ ] Swagger UI скрыть за basic-auth (endpoints всё ещё доступны через `/api/docs/swagger.json`)
- [ ] Postgres backups (pg_dump → S3 по cron)
- [ ] Vault root token ротация
- [ ] Mnemonic-reveal endpoint — 2FA / time-based confirmation tokens
- [ ] Rate-limit tune под реальный трафик