Skip to content
Learning Platform

Troubleshooting криптографии и блокчейна

База знаний типичных ошибок при разработке с криптографией и блокчейном — от ключей и транзакций до смарт-контрактов и ZK. Используйте фильтры или Cmd+K для поиска.

Фильтр по модулю

Фильтр по категории

Showing 29 of 29 errors

Symptoms

  • RSA шифрование падает с ValueError при попытке зашифровать данные
  • Размер открытого текста превышает размер ключа RSA
  • Ошибка возникает при прямом шифровании файлов или длинных строк через RSA

Cause

RSA может шифровать только блоки данных размером не больше длины ключа минус размер padding. Для RSA-2048 это максимум 245 байт (OAEP) или 214 байт (PKCS1v15). Прямое шифрование файлов или длинных строк через RSA невозможно — используйте гибридное шифрование (AES + RSA).

Solution

  1. Проверьте размер данных: len(plaintext) должен быть меньше key_size_bytes - padding_overhead
  2. Для RSA-2048 с OAEP: максимум 190 байт (с SHA-256 hash), для PKCS1v15: 245 байт
  3. Используйте гибридное шифрование: сгенерируйте случайный AES-ключ, зашифруйте данные AES, затем зашифруйте AES-ключ через RSA
  4. Пример: aes_key = os.urandom(32) -> шифруем данные AES-GCM -> шифруем aes_key через RSA

Symptoms

  • Расшифровка AES падает с ошибкой wrong final block length
  • Зашифрованные данные повреждены или обрезаны
  • Несовпадение режима шифрования при шифровании и расшифровке

Cause

Ошибка padding при AES в режиме CBC/ECB. Типичные причины: неправильный ключ, неправильный IV, повреждённые данные, несовпадение режима (зашифровано в CBC, расшифровывается в ECB), или данные не выровнены по размеру блока (16 байт).

Solution

  1. Убедитесь, что используете тот же ключ и IV для расшифровки: ключ и IV должны быть идентичны тем, что были при шифровании
  2. Проверьте режим шифрования: AES-256-CBC при шифровании = AES-256-CBC при расшифровке
  3. Убедитесь, что данные не повреждены при передаче (base64 encode/decode корректен)
  4. Используйте AES-GCM вместо CBC — он автоматически проверяет целостность и не требует ручного padding

Symptoms

  • Генерация или импорт приватного ключа ECDSA падает с ошибкой
  • Приватный ключ = 0 или >= порядка кривой n
  • Ключ выглядит валидным (32 байта), но отклоняется библиотекой

Cause

Приватный ключ secp256k1 должен быть целым числом в диапазоне [1, n-1], где n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141. Значение 0 или >= n недопустимо. Это может произойти при ручной генерации ключей без проверки диапазона.

Solution

  1. Используйте криптографически безопасный генератор: from ecdsa import SigningKey, SECP256k1; sk = SigningKey.generate(curve=SECP256k1)
  2. При ручном создании проверяйте диапазон: 1 <= private_key_int < curve_order
  3. Для ethers.js: const wallet = ethers.Wallet.createRandom() вместо ручного ввода
  4. Никогда не используйте слабые источники энтропии (random.randint, Math.random()) для генерации ключей

Symptoms

  • Python `hashlib.sha256()` падает при передаче строки вместо bytes
  • Ошибка TypeError: Unicode-objects must be encoded before hashing
  • Хеш-функция отказывается принимать текстовые данные

Cause

Хеш-функции в Python работают только с байтами (bytes), а не со строками (str). Строку нужно предварительно закодировать в байты через `.encode()`. Это частая ошибка при первом знакомстве с криптографией в Python.

Solution

  1. Используйте .encode(): hashlib.sha256('hello'.encode('utf-8')).hexdigest()
  2. Или передавайте bytes литерал: hashlib.sha256(b'hello').hexdigest()
  3. Для бинарных данных (файлы, ключи) используйте rb режим чтения: open('file', 'rb').read()
  4. Помните: один и тот же текст в разных кодировках даёт разные хеши (UTF-8 vs Latin-1)

Symptoms

  • Bitcoin Core (bitcoind) не отвечает на RPC-запросы
  • Команды bitcoin-cli возвращают ошибку -28
  • Нода запущена, но ещё не готова к работе

Cause

Bitcoin Core загружает и проверяет блокчейн при запуске. Этот процесс может занять от нескольких минут (pruned node) до нескольких дней (full node, первая синхронизация). Ошибка -28 означает, что нода ещё не завершила инициализацию.

Solution

  1. Дождитесь завершения синхронизации: bitcoin-cli getblockchaininfo покажет прогресс в поле verificationprogress
  2. Для ускорения используйте pruned режим: prune=550 в bitcoin.conf
  3. Для лабораторных работ используйте regtest: bitcoind -regtest — моментальный запуск без синхронизации
  4. Проверьте прогресс: bitcoin-cli -getinfo должен показать blocks и headers

Symptoms

  • Создание Bitcoin-транзакции падает с ошибкой insufficient funds
  • Баланс кошелька показывает средства, но отправка не работает
  • UTXO недостаточны для суммы + комиссия

Cause

В модели UTXO баланс — это сумма неизрасходованных выходов. Ошибка возникает когда сумма доступных UTXO меньше суммы отправки + комиссия (fee). Также возможно: UTXO ещё не подтверждены (0 confirmations) или заблокированы другой транзакцией.

Solution

  1. Проверьте доступные UTXO: bitcoin-cli listunspent
  2. Учтите комиссию: сумма входов >= сумма выходов + fee
  3. Включите неподтверждённые UTXO: bitcoin-cli listunspent 0 (минимум 0 подтверждений)
  4. В regtest сгенерируйте блоки для подтверждения: bitcoin-cli -regtest generatetoaddress 1
  5. Используйте fundrawtransaction для автоматического выбора UTXO и расчёта change

Symptoms

  • Bitcoin транзакция отклоняется при broadcast
  • Скрипт валидации возвращает false
  • P2PKH или P2SH скрипт не проходит верификацию

Cause

ScriptSig (unlocking script) не удовлетворяет условиям ScriptPubKey (locking script). Типичные причины: неправильная подпись (подписан другой ключ или другие данные), неправильный порядок элементов в стеке, или попытка потратить output с неправильным ключом.

Solution

  1. Проверьте, что подпись соответствует правильному приватному ключу для данного P2PKH адреса
  2. Убедитесь, что sighash type совпадает: SIGHASH_ALL (0x01) — самый частый
  3. Для P2SH мультисиг: проверьте порядок подписей — они должны идти в том же порядке, что и публичные ключи
  4. Используйте bitcoin-cli decoderawtransaction для анализа структуры транзакции
  5. Для отладки: bitcoin-cli testmempoolaccept '["hex"]' покажет причину отклонения

Symptoms

  • Транзакция отклоняется нодой при broadcast
  • Ошибка min relay fee not met
  • Транзакция с нулевой или слишком низкой комиссией

Cause

Комиссия транзакции ниже минимального порога relay fee ноды. Bitcoin ноды имеют параметр `minrelaytxfee` (по умолчанию 0.00001 BTC/kB), и отклоняют транзакции с комиссией ниже этого порога для защиты от спама.

Solution

  1. Увеличьте комиссию: fee = размер транзакции (в vbytes) * fee rate (sat/vB)
  2. Проверьте текущий fee rate: bitcoin-cli estimatesmartfee 6 (для подтверждения в ~6 блоках)
  3. Для regtest: bitcoin-cli -regtest settxfee 0.0001
  4. При ручном создании: используйте fundrawtransaction с параметром feeRate
  5. Помните: SegWit транзакции легче (меньше vbytes), значит дешевле

Symptoms

  • Отправка транзакции в Ethereum падает с ошибкой insufficient funds
  • Баланс аккаунта меньше суммы перевода + gas * gasPrice
  • Деплой контракта падает из-за нехватки ETH на газ

Cause

Баланс отправителя меньше, чем value + gasLimit * gasPrice (для legacy tx) или value + gasLimit * maxFeePerGas (для EIP-1559). Минимальная стоимость газа для простого перевода: 21000 * gasPrice.

Solution

  1. Проверьте баланс: await provider.getBalance(address) (ethers.js) или eth_getBalance RPC
  2. Для локальной ноды (Hardhat/Anvil): аккаунты уже имеют 10000 ETH
  3. Для тестнета: используйте faucet для получения тестовых ETH (Sepolia, Holesky)
  4. Уменьшите gasLimit или используйте estimateGas() для точного расчёта
  5. Для EIP-1559: проверьте maxFeePerGas и maxPriorityFeePerGas — они влияют на итоговую стоимость

Symptoms

  • Транзакция отклоняется с ошибкой nonce too low
  • Повторная отправка транзакции не проходит
  • replacement transaction underpriced при попытке заменить pending tx

Cause

Nonce — это порядковый номер транзакции аккаунта. Ошибка nonce too low: транзакция с таким nonce уже подтверждена. Ошибка underpriced: попытка заменить pending транзакцию с тем же nonce, но gasPrice ниже предыдущей (нужно минимум +10%).

Solution

  1. Получите актуальный nonce: await provider.getTransactionCount(address, 'pending')
  2. Для замены pending tx: увеличьте gasPrice минимум на 10%: newGasPrice = oldGasPrice * 1.1
  3. Для отмены tx: отправьте tx с тем же nonce, value=0, to=свой адрес, повышенный gasPrice
  4. В Hardhat: await network.provider.send('hardhat_setNonce', [address, '0x0']) для сброса

Symptoms

  • Вызов функции контракта падает с execution reverted
  • Solidity require() или revert() сработал
  • Транзакция потребляет весь газ и откатывается

Cause

Контракт явно отклонил транзакцию через require(), revert() или assert(). Reason string содержит описание ошибки (если указано). Типичные причины: не выполнено условие require, вызов от неавторизованного адреса, недостаточный баланс токенов.

Solution

  1. Прочитайте reason string — он объясняет причину: Error: execution reverted: Ownable: caller is not the owner
  2. Используйте staticCall() (ethers v6) или callStatic (ethers v5) для тестирования без отправки транзакции
  3. Для custom errors (Solidity 0.8.4+): декодируйте через contract.interface.parseError(data)
  4. Проверьте порядок вызовов: approve() перед transferFrom(), deposit() перед withdraw()

Symptoms

  • Компиляция Solidity падает с Stack too deep
  • Функция имеет слишком много локальных переменных
  • Ошибка возникает при сложных функциях с >16 переменными

Cause

EVM имеет ограничение стека: максимум 16 слотов доступны для операций. Когда функция использует больше 16 локальных переменных (включая параметры и return values), компилятор не может разместить их все в стеке.

Solution

  1. Разбейте функцию на несколько меньших: выделите логические блоки в internal функции
  2. Используйте struct для группировки связанных переменных: struct Params { uint a; uint b; uint c; }
  3. Используйте scoping блоки { } для ограничения области видимости временных переменных
  4. Включите via-ir optimizer в solc: settings: { viaIR: true } — он обходит ограничение стека

Symptoms

  • Отправка транзакции через remote provider (Infura, Alchemy) падает
  • Ошибка eth_sendTransaction does not exist
  • Код работает на Hardhat/Anvil, но не работает с внешним провайдером

Cause

Remote RPC провайдеры (Infura, Alchemy) не хранят приватные ключи и не поддерживают eth_sendTransaction. Для отправки транзакций нужно подписывать их локально (eth_sendRawTransaction) с помощью Wallet/Signer.

Solution

  1. Используйте Wallet с провайдером: const wallet = new ethers.Wallet(privateKey, provider)
  2. Или через viem: const walletClient = createWalletClient({ account, chain, transport: http(rpcUrl) })
  3. Для Hardhat: переключитесь на hre.network.provider вместо внешнего провайдера для тестов
  4. Никогда не храните приватные ключи в коде — используйте переменные окружения: process.env.PRIVATE_KEY

Symptoms

  • Hardhat не может подключиться к локальной или удалённой ноде
  • Ошибка HH108 при запуске тестов или деплое
  • localhost:8545 не отвечает

Cause

Hardhat пытается подключиться к JSON-RPC ноде, но она не запущена или недоступна. Возможно: забыли запустить `npx hardhat node`, Docker контейнер с нодой не запущен, или URL ноды указан неверно в hardhat.config.

Solution

  1. Запустите локальную ноду: npx hardhat node (в отдельном терминале)
  2. Для Docker: docker compose up -d hardhat и проверьте docker compose ps
  3. Проверьте URL в hardhat.config: networks: { localhost: { url: 'http://127.0.0.1:8545' } }
  4. Для тестов без ноды: используйте встроенную сеть Hardhat (npx hardhat test без --network)

Symptoms

  • Создание аккаунта Solana падает с ошибкой allocation failed
  • Программа не может записать данные в аккаунт
  • AccountInfo data слишком мал для хранимых данных

Cause

В Solana размер аккаунта фиксируется при создании и не может быть увеличен позже. Если при создании указан слишком маленький space, данные не поместятся. Также ошибка возникает при попытке записать данные в аккаунт, размер которого уже полностью использован.

Solution

  1. Рассчитайте правильный размер: space = 8 (discriminator) + serialized_data_size
  2. Для Anchor: размер вычисляется автоматически через #[account(init, space = 8 + MyStruct::INIT_SPACE)]
  3. Используйте #[derive(InitSpace)] на структуре для автоматического расчёта
  4. Для динамических данных (String, Vec): добавьте 4 байта prefix + максимальный размер данных

Symptoms

  • Транзакция Solana падает при симуляции
  • Аккаунт не имеет SOL для оплаты транзакции
  • Новый кошелёк на devnet/localnet без средств

Cause

Аккаунт отправителя не имеет SOL для оплаты rent и transaction fee. На devnet/localnet нужно предварительно получить SOL через airdrop. На mainnet нужно перевести SOL на аккаунт.

Solution

  1. На localnet: solana airdrop 2
    --url localhost
  2. На devnet: solana airdrop 2
    --url devnet (лимит ~2 SOL за запрос)
  3. В тестах Anchor: await provider.connection.requestAirdrop(wallet.publicKey, 2 * LAMPORTS_PER_SOL)
  4. Проверьте баланс: solana balance
    --url localhost

Symptoms

  • Вызов Anchor программы падает с AccountNotInitialized
  • PDA аккаунт существует, но не инициализирован
  • Попытка читать данные из неинициализированного аккаунта

Cause

Anchor ожидает, что аккаунт инициализирован (содержит discriminator и данные). Если аккаунт ещё не создан через `init` constraint, или был создан вручную без правильного discriminator, Anchor не может десериализовать его данные.

Solution

  1. Убедитесь, что аккаунт создан через #[account(init, ...)] в отдельной инструкции инициализации
  2. Порядок вызова: сначала initialize(), потом update() / read()
  3. Для проверки существования: используйте #[account(init_if_needed)] (требует realloc feature)
  4. Проверьте seeds PDA: #[account(seeds = [b"seed", user.key().as_ref()], bump)] должны совпадать

Symptoms

  • Anchor транзакция падает с custom program error
  • Hex код ошибки (0x1, 0x7d1, и т.д.) вместо читаемого сообщения
  • Constraint violation в Anchor программе

Cause

Anchor преобразует constraint violations в числовые коды ошибок. 0x1 = AccountConstraintViolation (has_one, seeds mismatch), 0x7d1 = AccountNotInitialized, 0xbc4 = ConstraintMut. Для кастомных ошибок: 0x1770 + index в #[error_code] enum.

Solution

  1. Декодируйте ошибку: anchor idl errors покажет все ошибки программы
  2. Частые коды: 0x0 = InstructionMissing, 0x1 = InstructionFallbackNotFound, 0x7d0 = AccountDiscriminatorAlreadySet
  3. Проверьте constraints: has_one = authority требует, чтобы поле authority аккаунта совпадало с переданным
  4. Включите anchor test --provider.cluster localnet для полных логов ошибок

Symptoms

  • Swap на Uniswap/DEX падает с UNPREDICTABLE_GAS_LIMIT
  • DeFi контракт reverts при вызове
  • estimateGas() падает до отправки транзакции

Cause

DeFi транзакция откатывается в процессе симуляции. Типичные причины: slippage превышен (цена изменилась), deadline истёк, insufficient allowance (не сделан approve), insufficient liquidity, или обращение к паре которая не существует.

Solution

  1. Проверьте approve: await token.approve(routerAddress, amount) перед swap/deposit
  2. Увеличьте slippage tolerance: amountOutMin = amountOut * 0.95 (5% slippage)
  3. Увеличьте deadline: Math.floor(Date.now() / 1000) + 60 * 20 (20 минут)
  4. Для fork тестирования: используйте актуальный блок --fork-block-number для консистентного state

Symptoms

  • transferFrom() или DeFi операция падает с insufficient allowance
  • Контракт не может списать токены от имени пользователя
  • Approve был сделан, но на другой адрес или сумму

Cause

ERC-20 стандарт требует двухшаговый процесс: сначала owner вызывает approve(spender, amount), затем spender может вызвать transferFrom(). Ошибка возникает когда allowance = 0 или меньше запрашиваемой суммы.

Solution

  1. Перед DeFi операцией вызовите approve: await token.approve(protocolAddress, ethers.MaxUint256)
  2. Проверьте текущий allowance: await token.allowance(owner, spender)
  3. Используйте MaxUint256 для одноразового безлимитного approve (удобно для тестов)
  4. Для продакшена: approve точную сумму, не MaxUint256 (безопасность)

Symptoms

  • Lending позиция ликвидирована
  • Health factor упал ниже 1.0
  • Невозможно занять больше средств — позиция undercollateralized

Cause

Health Factor = (collateral * liquidation_threshold) / debt. Когда HF < 1, позиция может быть ликвидирована. Причины: цена залога упала, долг вырос (проценты), или liquidation threshold для актива изменился.

Solution

  1. Мониторьте Health Factor: aave.getUserAccountData(address) возвращает healthFactor
  2. Увеличьте collateral: добавьте залог через supply() для повышения HF
  3. Уменьшите долг: частично погасите через repay() для повышения HF
  4. Установите алерт: HF < 1.5 — предупреждение, HF < 1.2 — критическое
  5. Используйте стабильные залоги (USDC, DAI) для предсказуемого HF

Symptoms

  • Slither выдаёт High severity finding: reentrancy
  • Функция делает внешний вызов до обновления состояния
  • Паттерн: external call -> state change

Cause

Функция выполняет внешний вызов (transfer, call) до обновления внутреннего состояния контракта. Атакующий может вызвать функцию повторно через fallback/receive до того, как состояние обновится, и вывести средства многократно.

Solution

  1. Применяйте паттерн Checks-Effects-Interactions (CEI): 1) проверки, 2) обновление состояния, 3) внешние вызовы
  2. Используйте ReentrancyGuard от OpenZeppelin: contract MyContract is ReentrancyGuard { function withdraw() external nonReentrant { ... } }
  3. Замените call{value: amount}("") на Address.sendValue() с проверкой
  4. Для pull-payment: используйте PullPayment pattern (баланс хранится, пользователь забирает)

Symptoms

  • Вызов контракта от другого контракта отклоняется
  • require(msg.sender == tx.origin) блокирует вызов
  • Мультисиг или контрактный кошелёк не может взаимодействовать с протоколом

Cause

Контракт использует tx.origin проверку или require(msg.sender.code.length == 0) для ограничения вызовов только от EOA. Это антипаттерн: он блокирует контрактные кошельки (Gnosis Safe, Account Abstraction) и не защищает от атак (tx.origin может быть обманут через phishing).

Solution

  1. Не используйте tx.origin для авторизации — это уязвимость (phishing attack)
  2. Для защиты от flash loan атак: используйте time-lock или multi-block delay вместо onlyEOA
  3. Для Account Abstraction совместимости: проверяйте msg.sender через access control, не через code.length
  4. Паттерн: modifier onlyAuthorized() { require(hasRole(ROLE, msg.sender)); }

Symptoms

  • Компиляция Circom цепи падает с ошибкой T3001
  • Constraint содержит умножение трёх и более сигналов
  • Выражение вида `a * b * c === d` отклоняется компилятором

Cause

R1CS (Rank-1 Constraint System) допускает только квадратичные constraints вида A * B = C, где A, B, C — линейные комбинации сигналов. Выражения с тремя и более перемножениями сигналов не квадратичны и должны быть разбиты на промежуточные шаги.

Solution

  1. Разбейте на промежуточные сигналы: signal tmp <-- a * b; tmp * c === d;
  2. Добавьте constraint для промежуточного: signal tmp; tmp <== a * b; tmp * c === d;
  3. Используйте <== (assign + constrain) вместо <-- + отдельный constraint
  4. Для деления: signal inv <-- 1 / b; inv * b === 1; a * inv === result;

Symptoms

  • Верификация ZK доказательства возвращает false
  • snarkjs verify выводит INVALID_PROOF
  • On-chain verifier отклоняет proof

Cause

Доказательство не соответствует verification key или public inputs. Причины: witness изменился после генерации proof, public inputs переданы в неправильном порядке, proof сгенерирован для другой цепи (другой .zkey файл), или proof испорчен при передаче.

Solution

  1. Убедитесь, что public inputs идентичны при генерации proof и верификации
  2. Проверьте порядок public inputs: snarkjs zkey export verificationkey показывает ожидаемый порядок
  3. Перегенерируйте proof с актуальным witness: snarkjs groth16 prove circuit.zkey witness.wtns proof.json public.json
  4. Для on-chain: убедитесь, что Verifier.sol сгенерирован из того же .zkey, что и proof

Symptoms

  • Генерация witness падает с ошибкой
  • Входные данные не удовлетворяют constraints цепи
  • assert внутри template Circom не выполняется

Cause

Входные данные (input.json) не удовлетворяют математическим ограничениям цепи. Например: для цепи доказательства знания прообраза хеша, если указан неправильный прообраз, hash(input) != expected_hash, и constraint не выполняется.

Solution

  1. Проверьте входные данные в input.json: все значения должны быть строками чисел
  2. Для hash preimage: убедитесь, что hash(preimage) действительно равен ожидаемому хешу
  3. Используйте circom --inspect для анализа constraints перед генерацией witness
  4. Проверьте типы: Circom работает с полем Fr (модуль p), отрицательные числа = p - |n|

Symptoms

  • Docker контейнер не запускается из-за конфликта имён
  • Ошибка Conflict при docker run или docker compose up
  • Предыдущий контейнер с тем же именем не удалён

Cause

Docker контейнер с указанным именем уже существует (возможно остановлен, но не удалён). Docker не позволяет создать два контейнера с одинаковым именем.

Solution

  1. Удалите существующий контейнер: docker rm -f
  2. Или используйте docker compose: docker compose down && docker compose up -d
  3. Для полной очистки: docker compose down -v (удалит и volumes)
  4. Используйте docker compose up -d --force-recreate для пересоздания контейнеров

Symptoms

  • Import модуля падает с Cannot find module (Node.js) или No module named (Python)
  • Зависимости не установлены
  • Пакет установлен глобально, но не в проекте

Cause

Необходимый пакет не установлен в текущем окружении. Для Node.js: пакет отсутствует в node_modules. Для Python: пакет не установлен в текущем virtualenv. Часто происходит после клонирования репозитория без установки зависимостей.

Solution

  1. Для Node.js: bun install или npm install в корне проекта
  2. Для Python: pip install -r requirements.txt в активированном virtualenv
  3. Проверьте наличие файла зависимостей: package.json (Node.js) или requirements.txt (Python)
  4. Для Docker: пересоберите образ docker compose build --no-cache
  5. Убедитесь, что версия Node.js/Python совместима (проверьте engines в package.json)

Symptoms

  • Запуск локальной ноды (Hardhat, Anvil, Ganache) падает с EADDRINUSE
  • Порт 8545 уже занят другим процессом
  • Предыдущая нода не была корректно остановлена

Cause

Порт 8545 (стандартный для Ethereum JSON-RPC) уже используется другим процессом. Это может быть предыдущий запуск Hardhat node, Anvil, Ganache, или другой сервис.

Solution

  1. Найдите процесс на порту: lsof -i :8545 (macOS/Linux) или netstat -ano | findstr 8545 (Windows)
  2. Завершите процесс: kill -9 (macOS/Linux)
  3. Или используйте другой порт: npx hardhat node --port 8546
  4. Для Docker: проверьте port mappings в docker-compose.yml и остановите конфликтующие контейнеры

Error not found? Use Cmd+K (Ctrl+K) to search for error text in the knowledge base. Also refer to module lessons for deeper understanding of mechanisms and problem diagnosis.