PHP WebShell

Текущая директория: /var/www/bitcardoApp/models/crypto

Просмотр файла: create_tron_wallet.php

<?php
// models/crypto/create_tron_wallet.php

namespace Models\Crypto;

require_once __DIR__ . '/../../vendor/autoload.php';

use mysqli;
use Elliptic\EC;
use kornrunner\Keccak;
use Exception;

class CreateTronWallet
{
    /** @var mysqli */
    private $db;

    public function __construct(mysqli $conn)
    {
        $this->db = $conn;
    }

    /**
     * Create TRX + USDT-TRC20 wallets for a user.
     *
     * @param int $userId
     * @return array ['success' => bool, 'message' => string, 'address' => string|null]
     */
    public function create(int $userId): array
    {
        try {
            // Block duplicates: only 1 TRX wallet per user
            if ($this->userHasTrxWallet($userId)) {
                return [
                    'success' => false,
                    'message' => 'TRX wallet already exists for this user.',
                    'address' => null,
                ];
            }

            // 1) Generate keypair
            $privateKeyHex = $this->generatePrivateKey();
            $address       = $this->tronAddressFromPrivateKey($privateKeyHex); // Base58, matches TronLink

            // 2) Extra safety check (same logic as check_tron_wallet_keys.php)
            if (!$this->verifyKeyAndAddress($privateKeyHex, $address)) {
                // Do NOT touch DB if derivation is not stable
                return [
                    'success' => false,
                    'message' => 'TRX wallet cannot be created at this time. Please try again later.',
                    'address' => null,
                ];
            }

            // 3) Start DB transaction
            $this->db->begin_transaction();

            // Save private key
            $this->insertWalletKey($userId, $address, $privateKeyHex);

            // Save TRX + USDT wallets (same address)
            $this->insertUserWallets($userId, $address);

            $this->db->commit();

            return [
                'success' => true,
                'message' => 'TRX and USDT-TRC20 wallets created successfully.',
                'address' => $address,
            ];
        } catch (Exception $e) {
            // Rollback if transaction started
            @ $this->db->rollback();

            return [
                'success' => false,
                'message' => 'Error creating TRX wallet: ' . $e->getMessage(),
                'address' => null,
            ];
        }
    }

    /**
     * Check if user already has a TRX wallet.
     */
    private function userHasTrxWallet(int $userId): bool
    {
        $sql  = "SELECT COUNT(*) AS cnt 
                 FROM user_wallets 
                 WHERE user_id = ? AND coin = 'TRX' AND wallet_status = 'active'";
        $stmt = $this->db->prepare($sql);
        if (!$stmt) {
            throw new Exception("DB error preparing userHasTrxWallet: " . $this->db->error);
        }
        $stmt->bind_param("i", $userId);
        $stmt->execute();
        $res = $stmt->get_result();
        $row = $res->fetch_assoc();
        $stmt->close();

        return ((int)($row['cnt'] ?? 0)) > 0;
    }

    /**
     * Secure random 32-byte private key (hex).
     */
    private function generatePrivateKey(): string
    {
        $bytes = random_bytes(32);
        $hex   = bin2hex($bytes);

        if (strlen($hex) !== 64) {
            throw new Exception("Generated private key has invalid length.");
        }

        return $hex;
    }

    /**
     * Extra safety: verify that privateKey → derived address matches the one we plan to save.
     * This mirrors the logic used in check_tron_wallet_keys.php.
     */
    private function verifyKeyAndAddress(string $privateKeyHex, string $address): bool
    {
        $derived = $this->tronAddressFromPrivateKey($privateKeyHex);
        // hash_equals protects against timing attacks, but here it's mostly just a clean comparison
        return hash_equals($derived, $address);
    }

    /**
     * Correct TRON address derivation from private key (matches TronLink/TronWeb).
     */
    private function tronAddressFromPrivateKey(string $privateKeyHex): string
    {
        $ec  = new EC('secp256k1');
        $key = $ec->keyFromPrivate($privateKeyHex, 'hex');

        // 1) Uncompressed public key: 0x04 + X(32) + Y(32)
        $pubHex = $key->getPublic(false, 'hex'); // "04...."
        $pubHex = substr($pubHex, 2);           // drop "04"

        // 2) Keccak-256 on the *binary* public key (Ethereum/Tron style)
        //    -> keccak256( hex2bin(pubkey_without_04) )
        $hashHex = Keccak::hash(hex2bin($pubHex), 256);

        // 3) Last 20 bytes (40 hex chars)
        $ethPart = substr($hashHex, -40);

        // 4) Tron mainnet prefix 0x41
        $tronHex = '41' . $ethPart;
        $addrBin = hex2bin($tronHex);

        // 5) Base58Check: address + 4-byte checksum (double SHA256)
        $checksum = substr(
            hash('sha256', hash('sha256', $addrBin, true), true),
            0,
            4
        );
        $payload = $addrBin . $checksum;

        return $this->base58encode($payload);
    }

    /**
     * Base58 encoding with Bitcoin alphabet (TRON uses same).
     */
    private function base58encode(string $data): string
    {
        $alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';

        $num = gmp_init(0, 10);
        $len = strlen($data);

        for ($i = 0; $i < $len; $i++) {
            $num = gmp_add(
                gmp_mul($num, 256),
                ord($data[$i])
            );
        }

        $encoded = '';
        while (gmp_cmp($num, 0) > 0) {
            [$num, $rem] = [
                gmp_div_q($num, 58),
                gmp_intval(gmp_mod($num, 58))
            ];
            $encoded = $alphabet[$rem] . $encoded;
        }

        // Preserve leading zeros as '1'
        $i = 0;
        while ($i < $len && $data[$i] === "\x00") {
            $encoded = '1' . $encoded;
            $i++;
        }

        return $encoded;
    }

    /**
     * Insert private key row.
     */
    private function insertWalletKey(int $userId, string $address, string $privateKeyHex): void
    {
        $sql  = "INSERT INTO wallet_keys (user_id, wallet_add, private_key, created_at)
                 VALUES (?, ?, ?, NOW())";
        $stmt = $this->db->prepare($sql);
        if (!$stmt) {
            throw new Exception("DB error preparing insertWalletKey: " . $this->db->error);
        }
        $stmt->bind_param("iss", $userId, $address, $privateKeyHex);
        if (!$stmt->execute()) {
            $err = $stmt->error;
            $stmt->close();
            throw new Exception("DB error executing insertWalletKey: " . $err);
        }
        $stmt->close();
    }

    /**
     * Insert TRX and USDT-TRC20 wallets using the same address.
     */
    private function insertUserWallets(int $userId, string $address): void
    {
        $icon    = 'trx.png';
        $type    = 'crypto';
        $status  = 'active';
        $balance = '0.000000'; // 6 decimals for TRX/USDT

        // TRX wallet
        $sql = "INSERT INTO user_wallets
                (user_id, wallet_add, coin, label, balance, type, icon, wallet_status, created_at)
                VALUES (?, ?, 'TRX', 'TRX Wallet', ?, ?, ?, ?, NOW())";
        $stmt = $this->db->prepare($sql);
        if (!$stmt) {
            throw new Exception("DB error preparing insert TRX wallet: " . $this->db->error);
        }
        // user_id (i), wallet_add (s), balance (s), type (s), icon (s), status (s)
        $stmt->bind_param("isssss", $userId, $address, $balance, $type, $icon, $status);
        if (!$stmt->execute()) {
            $err = $stmt->error;
            $stmt->close();
            throw new Exception("DB error executing insert TRX wallet: " . $err);
        }
        $stmt->close();

        // USDT-TRC20 wallet
        $icon = 'usdt.png';
        $sql = "INSERT INTO user_wallets
                (user_id, wallet_add, coin, label, balance, type, icon, wallet_status, created_at)
                VALUES (?, ?, 'USDT-TRC20', 'USDT-TRC20 Wallet', ?, ?, ?, ?, NOW())";
        $stmt = $this->db->prepare($sql);
        if (!$stmt) {
            throw new Exception("DB error preparing insert USDT wallet: " . $this->db->error);
        }
        $stmt->bind_param("isssss", $userId, $address, $balance, $type, $icon, $status);
        if (!$stmt->execute()) {
            $err = $stmt->error;
            $stmt->close();
            throw new Exception("DB error executing insert USDT wallet: " . $err);
        }
        $stmt->close();
    }
}

Выполнить команду


Для локальной разработки. Не используйте в интернете!