Troubleshooting криптографии и блокчейна
База знаний типичных ошибок при разработке с криптографией и блокчейном — от ключей и транзакций до смарт-контрактов и ZK. Используйте фильтры или Cmd+K для поиска.
Фильтр по модулю
Фильтр по категории
Симптомы
- RSA шифрование падает с ValueError при попытке зашифровать данные
- Размер открытого текста превышает размер ключа RSA
- Ошибка возникает при прямом шифровании файлов или длинных строк через RSA
Причина
RSA может шифровать только блоки данных размером не больше длины ключа минус размер padding. Для RSA-2048 это максимум 245 байт (OAEP) или 214 байт (PKCS1v15). Прямое шифрование файлов или длинных строк через RSA невозможно — используйте гибридное шифрование (AES + RSA).
Решение
- Проверьте размер данных:
len(plaintext)должен быть меньшеkey_size_bytes - padding_overhead - Для RSA-2048 с OAEP: максимум 190 байт (с SHA-256 hash), для PKCS1v15: 245 байт
- Используйте гибридное шифрование: сгенерируйте случайный AES-ключ, зашифруйте данные AES, затем зашифруйте AES-ключ через RSA
- Пример:
aes_key = os.urandom(32)-> шифруем данные AES-GCM -> шифруемaes_keyчерез RSA
Связанные уроки:
Симптомы
- Расшифровка AES падает с ошибкой wrong final block length
- Зашифрованные данные повреждены или обрезаны
- Несовпадение режима шифрования при шифровании и расшифровке
Причина
Ошибка padding при AES в режиме CBC/ECB. Типичные причины: неправильный ключ, неправильный IV, повреждённые данные, несовпадение режима (зашифровано в CBC, расшифровывается в ECB), или данные не выровнены по размеру блока (16 байт).
Решение
- Убедитесь, что используете тот же ключ и IV для расшифровки: ключ и IV должны быть идентичны тем, что были при шифровании
- Проверьте режим шифрования:
AES-256-CBCпри шифровании =AES-256-CBCпри расшифровке - Убедитесь, что данные не повреждены при передаче (base64 encode/decode корректен)
- Используйте AES-GCM вместо CBC — он автоматически проверяет целостность и не требует ручного padding
Связанные уроки:
Симптомы
- Генерация или импорт приватного ключа ECDSA падает с ошибкой
- Приватный ключ = 0 или >= порядка кривой n
- Ключ выглядит валидным (32 байта), но отклоняется библиотекой
Причина
Приватный ключ secp256k1 должен быть целым числом в диапазоне [1, n-1], где n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141. Значение 0 или >= n недопустимо. Это может произойти при ручной генерации ключей без проверки диапазона.
Решение
- Используйте криптографически безопасный генератор:
from ecdsa import SigningKey, SECP256k1; sk = SigningKey.generate(curve=SECP256k1) - При ручном создании проверяйте диапазон:
1 <= private_key_int < curve_order - Для ethers.js:
const wallet = ethers.Wallet.createRandom()вместо ручного ввода - Никогда не используйте слабые источники энтропии (
random.randint,Math.random()) для генерации ключей
Связанные уроки:
Симптомы
- Python `hashlib.sha256()` падает при передаче строки вместо bytes
- Ошибка TypeError: Unicode-objects must be encoded before hashing
- Хеш-функция отказывается принимать текстовые данные
Причина
Хеш-функции в Python работают только с байтами (bytes), а не со строками (str). Строку нужно предварительно закодировать в байты через `.encode()`. Это частая ошибка при первом знакомстве с криптографией в Python.
Решение
- Используйте
.encode():hashlib.sha256('hello'.encode('utf-8')).hexdigest() - Или передавайте bytes литерал:
hashlib.sha256(b'hello').hexdigest() - Для бинарных данных (файлы, ключи) используйте
rbрежим чтения:open('file', 'rb').read() - Помните: один и тот же текст в разных кодировках даёт разные хеши (UTF-8 vs Latin-1)
Связанные уроки:
Симптомы
- Bitcoin Core (bitcoind) не отвечает на RPC-запросы
- Команды bitcoin-cli возвращают ошибку -28
- Нода запущена, но ещё не готова к работе
Причина
Bitcoin Core загружает и проверяет блокчейн при запуске. Этот процесс может занять от нескольких минут (pruned node) до нескольких дней (full node, первая синхронизация). Ошибка -28 означает, что нода ещё не завершила инициализацию.
Решение
- Дождитесь завершения синхронизации:
bitcoin-cli getblockchaininfoпокажет прогресс в полеverificationprogress - Для ускорения используйте pruned режим:
prune=550в bitcoin.conf - Для лабораторных работ используйте regtest:
bitcoind -regtest— моментальный запуск без синхронизации - Проверьте прогресс:
bitcoin-cli -getinfoдолжен показатьblocksиheaders
Связанные уроки:
Симптомы
- Создание Bitcoin-транзакции падает с ошибкой insufficient funds
- Баланс кошелька показывает средства, но отправка не работает
- UTXO недостаточны для суммы + комиссия
Причина
В модели UTXO баланс — это сумма неизрасходованных выходов. Ошибка возникает когда сумма доступных UTXO меньше суммы отправки + комиссия (fee). Также возможно: UTXO ещё не подтверждены (0 confirmations) или заблокированы другой транзакцией.
Решение
- Проверьте доступные UTXO:
bitcoin-cli listunspent - Учтите комиссию: сумма входов >= сумма выходов + fee
- Включите неподтверждённые UTXO:
bitcoin-cli listunspent 0(минимум 0 подтверждений) - В regtest сгенерируйте блоки для подтверждения:
bitcoin-cli -regtest generatetoaddress 1 - Используйте
fundrawtransactionдля автоматического выбора UTXO и расчёта change
Связанные уроки:
Симптомы
- Bitcoin транзакция отклоняется при broadcast
- Скрипт валидации возвращает false
- P2PKH или P2SH скрипт не проходит верификацию
Причина
ScriptSig (unlocking script) не удовлетворяет условиям ScriptPubKey (locking script). Типичные причины: неправильная подпись (подписан другой ключ или другие данные), неправильный порядок элементов в стеке, или попытка потратить output с неправильным ключом.
Решение
- Проверьте, что подпись соответствует правильному приватному ключу для данного P2PKH адреса
- Убедитесь, что sighash type совпадает:
SIGHASH_ALL(0x01) — самый частый - Для P2SH мультисиг: проверьте порядок подписей — они должны идти в том же порядке, что и публичные ключи
- Используйте
bitcoin-cli decoderawtransactionдля анализа структуры транзакции - Для отладки:
bitcoin-cli testmempoolaccept '["hex"]'покажет причину отклонения
Связанные уроки:
Симптомы
- Транзакция отклоняется нодой при broadcast
- Ошибка min relay fee not met
- Транзакция с нулевой или слишком низкой комиссией
Причина
Комиссия транзакции ниже минимального порога relay fee ноды. Bitcoin ноды имеют параметр `minrelaytxfee` (по умолчанию 0.00001 BTC/kB), и отклоняют транзакции с комиссией ниже этого порога для защиты от спама.
Решение
- Увеличьте комиссию: fee = размер транзакции (в vbytes) * fee rate (sat/vB)
- Проверьте текущий fee rate:
bitcoin-cli estimatesmartfee 6(для подтверждения в ~6 блоках) - Для regtest:
bitcoin-cli -regtest settxfee 0.0001 - При ручном создании: используйте
fundrawtransactionс параметромfeeRate - Помните: SegWit транзакции легче (меньше vbytes), значит дешевле
Связанные уроки:
Симптомы
- Отправка транзакции в Ethereum падает с ошибкой insufficient funds
- Баланс аккаунта меньше суммы перевода + gas * gasPrice
- Деплой контракта падает из-за нехватки ETH на газ
Причина
Баланс отправителя меньше, чем value + gasLimit * gasPrice (для legacy tx) или value + gasLimit * maxFeePerGas (для EIP-1559). Минимальная стоимость газа для простого перевода: 21000 * gasPrice.
Решение
- Проверьте баланс:
await provider.getBalance(address)(ethers.js) илиeth_getBalanceRPC - Для локальной ноды (Hardhat/Anvil): аккаунты уже имеют 10000 ETH
- Для тестнета: используйте faucet для получения тестовых ETH (Sepolia, Holesky)
- Уменьшите gasLimit или используйте
estimateGas()для точного расчёта - Для EIP-1559: проверьте
maxFeePerGasиmaxPriorityFeePerGas— они влияют на итоговую стоимость
Связанные уроки:
Симптомы
- Транзакция отклоняется с ошибкой nonce too low
- Повторная отправка транзакции не проходит
- replacement transaction underpriced при попытке заменить pending tx
Причина
Nonce — это порядковый номер транзакции аккаунта. Ошибка nonce too low: транзакция с таким nonce уже подтверждена. Ошибка underpriced: попытка заменить pending транзакцию с тем же nonce, но gasPrice ниже предыдущей (нужно минимум +10%).
Решение
- Получите актуальный nonce:
await provider.getTransactionCount(address, 'pending') - Для замены pending tx: увеличьте gasPrice минимум на 10%:
newGasPrice = oldGasPrice * 1.1 - Для отмены tx: отправьте tx с тем же nonce, value=0, to=свой адрес, повышенный gasPrice
- В Hardhat:
await network.provider.send('hardhat_setNonce', [address, '0x0'])для сброса
Связанные уроки:
Симптомы
- Вызов функции контракта падает с execution reverted
- Solidity require() или revert() сработал
- Транзакция потребляет весь газ и откатывается
Причина
Контракт явно отклонил транзакцию через require(), revert() или assert(). Reason string содержит описание ошибки (если указано). Типичные причины: не выполнено условие require, вызов от неавторизованного адреса, недостаточный баланс токенов.
Решение
- Прочитайте reason string — он объясняет причину:
Error: execution reverted: Ownable: caller is not the owner - Используйте
staticCall()(ethers v6) илиcallStatic(ethers v5) для тестирования без отправки транзакции - Для custom errors (Solidity 0.8.4+): декодируйте через
contract.interface.parseError(data) - Проверьте порядок вызовов:
approve()передtransferFrom(),deposit()передwithdraw()
Связанные уроки:
Симптомы
- Компиляция Solidity падает с Stack too deep
- Функция имеет слишком много локальных переменных
- Ошибка возникает при сложных функциях с >16 переменными
Причина
EVM имеет ограничение стека: максимум 16 слотов доступны для операций. Когда функция использует больше 16 локальных переменных (включая параметры и return values), компилятор не может разместить их все в стеке.
Решение
- Разбейте функцию на несколько меньших: выделите логические блоки в internal функции
- Используйте struct для группировки связанных переменных:
struct Params { uint a; uint b; uint c; } - Используйте scoping блоки
{ }для ограничения области видимости временных переменных - Включите via-ir optimizer в solc:
settings: { viaIR: true }— он обходит ограничение стека
Связанные уроки:
Симптомы
- Отправка транзакции через remote provider (Infura, Alchemy) падает
- Ошибка eth_sendTransaction does not exist
- Код работает на Hardhat/Anvil, но не работает с внешним провайдером
Причина
Remote RPC провайдеры (Infura, Alchemy) не хранят приватные ключи и не поддерживают eth_sendTransaction. Для отправки транзакций нужно подписывать их локально (eth_sendRawTransaction) с помощью Wallet/Signer.
Решение
- Используйте Wallet с провайдером:
const wallet = new ethers.Wallet(privateKey, provider) - Или через viem:
const walletClient = createWalletClient({ account, chain, transport: http(rpcUrl) }) - Для Hardhat: переключитесь на
hre.network.providerвместо внешнего провайдера для тестов - Никогда не храните приватные ключи в коде — используйте переменные окружения:
process.env.PRIVATE_KEY
Связанные уроки:
Симптомы
- Hardhat не может подключиться к локальной или удалённой ноде
- Ошибка HH108 при запуске тестов или деплое
- localhost:8545 не отвечает
Причина
Hardhat пытается подключиться к JSON-RPC ноде, но она не запущена или недоступна. Возможно: забыли запустить `npx hardhat node`, Docker контейнер с нодой не запущен, или URL ноды указан неверно в hardhat.config.
Решение
- Запустите локальную ноду:
npx hardhat node(в отдельном терминале) - Для Docker:
docker compose up -d hardhatи проверьтеdocker compose ps - Проверьте URL в hardhat.config:
networks: { localhost: { url: 'http://127.0.0.1:8545' } } - Для тестов без ноды: используйте встроенную сеть Hardhat (
npx hardhat testбез --network)
Связанные уроки:
Симптомы
- Создание аккаунта Solana падает с ошибкой allocation failed
- Программа не может записать данные в аккаунт
- AccountInfo data слишком мал для хранимых данных
Причина
В Solana размер аккаунта фиксируется при создании и не может быть увеличен позже. Если при создании указан слишком маленький space, данные не поместятся. Также ошибка возникает при попытке записать данные в аккаунт, размер которого уже полностью использован.
Решение
- Рассчитайте правильный размер:
space = 8 (discriminator) + serialized_data_size - Для Anchor: размер вычисляется автоматически через
#[account(init, space = 8 + MyStruct::INIT_SPACE)] - Используйте
#[derive(InitSpace)]на структуре для автоматического расчёта - Для динамических данных (String, Vec): добавьте 4 байта prefix + максимальный размер данных
Связанные уроки:
Симптомы
- Транзакция Solana падает при симуляции
- Аккаунт не имеет SOL для оплаты транзакции
- Новый кошелёк на devnet/localnet без средств
Причина
Аккаунт отправителя не имеет SOL для оплаты rent и transaction fee. На devnet/localnet нужно предварительно получить SOL через airdrop. На mainnet нужно перевести SOL на аккаунт.
Решение
- На localnet:
solana airdrop 2 --url localhost - На devnet:
solana airdrop 2 --url devnet(лимит ~2 SOL за запрос) - В тестах Anchor:
await provider.connection.requestAirdrop(wallet.publicKey, 2 * LAMPORTS_PER_SOL) - Проверьте баланс:
solana balance --url localhost
Связанные уроки:
Симптомы
- Вызов Anchor программы падает с AccountNotInitialized
- PDA аккаунт существует, но не инициализирован
- Попытка читать данные из неинициализированного аккаунта
Причина
Anchor ожидает, что аккаунт инициализирован (содержит discriminator и данные). Если аккаунт ещё не создан через `init` constraint, или был создан вручную без правильного discriminator, Anchor не может десериализовать его данные.
Решение
- Убедитесь, что аккаунт создан через
#[account(init, ...)]в отдельной инструкции инициализации - Порядок вызова: сначала
initialize(), потомupdate()/read() - Для проверки существования: используйте
#[account(init_if_needed)](требуетreallocfeature) - Проверьте seeds PDA:
#[account(seeds = [b"seed", user.key().as_ref()], bump)]должны совпадать
Связанные уроки:
Симптомы
- Anchor транзакция падает с custom program error
- Hex код ошибки (0x1, 0x7d1, и т.д.) вместо читаемого сообщения
- Constraint violation в Anchor программе
Причина
Anchor преобразует constraint violations в числовые коды ошибок. 0x1 = AccountConstraintViolation (has_one, seeds mismatch), 0x7d1 = AccountNotInitialized, 0xbc4 = ConstraintMut. Для кастомных ошибок: 0x1770 + index в #[error_code] enum.
Решение
- Декодируйте ошибку:
anchor idl errorsпокажет все ошибки программы - Частые коды: 0x0 = InstructionMissing, 0x1 = InstructionFallbackNotFound, 0x7d0 = AccountDiscriminatorAlreadySet
- Проверьте constraints:
has_one = authorityтребует, чтобы полеauthorityаккаунта совпадало с переданным - Включите
anchor test --provider.cluster localnetдля полных логов ошибок
Связанные уроки:
Симптомы
- Swap на Uniswap/DEX падает с UNPREDICTABLE_GAS_LIMIT
- DeFi контракт reverts при вызове
- estimateGas() падает до отправки транзакции
Причина
DeFi транзакция откатывается в процессе симуляции. Типичные причины: slippage превышен (цена изменилась), deadline истёк, insufficient allowance (не сделан approve), insufficient liquidity, или обращение к паре которая не существует.
Решение
- Проверьте approve:
await token.approve(routerAddress, amount)перед swap/deposit - Увеличьте slippage tolerance:
amountOutMin = amountOut * 0.95(5% slippage) - Увеличьте deadline:
Math.floor(Date.now() / 1000) + 60 * 20(20 минут) - Для fork тестирования: используйте актуальный блок
--fork-block-numberдля консистентного state
Связанные уроки:
Симптомы
- transferFrom() или DeFi операция падает с insufficient allowance
- Контракт не может списать токены от имени пользователя
- Approve был сделан, но на другой адрес или сумму
Причина
ERC-20 стандарт требует двухшаговый процесс: сначала owner вызывает approve(spender, amount), затем spender может вызвать transferFrom(). Ошибка возникает когда allowance = 0 или меньше запрашиваемой суммы.
Решение
- Перед DeFi операцией вызовите approve:
await token.approve(protocolAddress, ethers.MaxUint256) - Проверьте текущий allowance:
await token.allowance(owner, spender) - Используйте
MaxUint256для одноразового безлимитного approve (удобно для тестов) - Для продакшена: approve точную сумму, не MaxUint256 (безопасность)
Связанные уроки:
Симптомы
- Lending позиция ликвидирована
- Health factor упал ниже 1.0
- Невозможно занять больше средств — позиция undercollateralized
Причина
Health Factor = (collateral * liquidation_threshold) / debt. Когда HF < 1, позиция может быть ликвидирована. Причины: цена залога упала, долг вырос (проценты), или liquidation threshold для актива изменился.
Решение
- Мониторьте Health Factor:
aave.getUserAccountData(address)возвращаетhealthFactor - Увеличьте collateral: добавьте залог через
supply()для повышения HF - Уменьшите долг: частично погасите через
repay()для повышения HF - Установите алерт: HF < 1.5 — предупреждение, HF < 1.2 — критическое
- Используйте стабильные залоги (USDC, DAI) для предсказуемого HF
Связанные уроки:
Симптомы
- Slither выдаёт High severity finding: reentrancy
- Функция делает внешний вызов до обновления состояния
- Паттерн: external call -> state change
Причина
Функция выполняет внешний вызов (transfer, call) до обновления внутреннего состояния контракта. Атакующий может вызвать функцию повторно через fallback/receive до того, как состояние обновится, и вывести средства многократно.
Решение
- Применяйте паттерн Checks-Effects-Interactions (CEI): 1) проверки, 2) обновление состояния, 3) внешние вызовы
- Используйте
ReentrancyGuardот OpenZeppelin:contract MyContract is ReentrancyGuard { function withdraw() external nonReentrant { ... } } - Замените
call{value: amount}("")наAddress.sendValue()с проверкой - Для pull-payment: используйте
PullPaymentpattern (баланс хранится, пользователь забирает)
Связанные уроки:
Симптомы
- Вызов контракта от другого контракта отклоняется
- require(msg.sender == tx.origin) блокирует вызов
- Мультисиг или контрактный кошелёк не может взаимодействовать с протоколом
Причина
Контракт использует tx.origin проверку или require(msg.sender.code.length == 0) для ограничения вызовов только от EOA. Это антипаттерн: он блокирует контрактные кошельки (Gnosis Safe, Account Abstraction) и не защищает от атак (tx.origin может быть обманут через phishing).
Решение
- Не используйте
tx.originдля авторизации — это уязвимость (phishing attack) - Для защиты от flash loan атак: используйте time-lock или multi-block delay вместо onlyEOA
- Для Account Abstraction совместимости: проверяйте
msg.senderчерез access control, не через code.length - Паттерн:
modifier onlyAuthorized() { require(hasRole(ROLE, msg.sender)); }
Связанные уроки:
Симптомы
- Компиляция Circom цепи падает с ошибкой T3001
- Constraint содержит умножение трёх и более сигналов
- Выражение вида `a * b * c === d` отклоняется компилятором
Причина
R1CS (Rank-1 Constraint System) допускает только квадратичные constraints вида A * B = C, где A, B, C — линейные комбинации сигналов. Выражения с тремя и более перемножениями сигналов не квадратичны и должны быть разбиты на промежуточные шаги.
Решение
- Разбейте на промежуточные сигналы:
signal tmp <-- a * b; tmp * c === d; - Добавьте constraint для промежуточного:
signal tmp; tmp <== a * b; tmp * c === d; - Используйте
<==(assign + constrain) вместо<--+ отдельный constraint - Для деления:
signal inv <-- 1 / b; inv * b === 1; a * inv === result;
Связанные уроки:
Симптомы
- Верификация ZK доказательства возвращает false
- snarkjs verify выводит INVALID_PROOF
- On-chain verifier отклоняет proof
Причина
Доказательство не соответствует verification key или public inputs. Причины: witness изменился после генерации proof, public inputs переданы в неправильном порядке, proof сгенерирован для другой цепи (другой .zkey файл), или proof испорчен при передаче.
Решение
- Убедитесь, что public inputs идентичны при генерации proof и верификации
- Проверьте порядок public inputs:
snarkjs zkey export verificationkeyпоказывает ожидаемый порядок - Перегенерируйте proof с актуальным witness:
snarkjs groth16 prove circuit.zkey witness.wtns proof.json public.json - Для on-chain: убедитесь, что Verifier.sol сгенерирован из того же .zkey, что и proof
Связанные уроки:
Симптомы
- Генерация witness падает с ошибкой
- Входные данные не удовлетворяют constraints цепи
- assert внутри template Circom не выполняется
Причина
Входные данные (input.json) не удовлетворяют математическим ограничениям цепи. Например: для цепи доказательства знания прообраза хеша, если указан неправильный прообраз, hash(input) != expected_hash, и constraint не выполняется.
Решение
- Проверьте входные данные в
input.json: все значения должны быть строками чисел - Для hash preimage: убедитесь, что hash(preimage) действительно равен ожидаемому хешу
- Используйте
circom --inspectдля анализа constraints перед генерацией witness - Проверьте типы: Circom работает с полем Fr (модуль p), отрицательные числа = p - |n|
Связанные уроки:
Симптомы
- Docker контейнер не запускается из-за конфликта имён
- Ошибка Conflict при docker run или docker compose up
- Предыдущий контейнер с тем же именем не удалён
Причина
Docker контейнер с указанным именем уже существует (возможно остановлен, но не удалён). Docker не позволяет создать два контейнера с одинаковым именем.
Решение
- Удалите существующий контейнер:
docker rm -f - Или используйте docker compose:
docker compose down && docker compose up -d - Для полной очистки:
docker compose down -v(удалит и volumes) - Используйте
docker compose up -d --force-recreateдля пересоздания контейнеров
Связанные уроки:
Симптомы
- Import модуля падает с Cannot find module (Node.js) или No module named (Python)
- Зависимости не установлены
- Пакет установлен глобально, но не в проекте
Причина
Необходимый пакет не установлен в текущем окружении. Для Node.js: пакет отсутствует в node_modules. Для Python: пакет не установлен в текущем virtualenv. Часто происходит после клонирования репозитория без установки зависимостей.
Решение
- Для Node.js:
bun installилиnpm installв корне проекта - Для Python:
pip install -r requirements.txtв активированном virtualenv - Проверьте наличие файла зависимостей:
package.json(Node.js) илиrequirements.txt(Python) - Для Docker: пересоберите образ
docker compose build --no-cache - Убедитесь, что версия Node.js/Python совместима (проверьте
enginesв package.json)
Связанные уроки:
Симптомы
- Запуск локальной ноды (Hardhat, Anvil, Ganache) падает с EADDRINUSE
- Порт 8545 уже занят другим процессом
- Предыдущая нода не была корректно остановлена
Причина
Порт 8545 (стандартный для Ethereum JSON-RPC) уже используется другим процессом. Это может быть предыдущий запуск Hardhat node, Anvil, Ganache, или другой сервис.
Решение
- Найдите процесс на порту:
lsof -i :8545(macOS/Linux) илиnetstat -ano | findstr 8545(Windows) - Завершите процесс:
kill -9(macOS/Linux) - Или используйте другой порт:
npx hardhat node --port 8546 - Для Docker: проверьте port mappings в docker-compose.yml и остановите конфликтующие контейнеры
Связанные уроки:
Не нашли свою ошибку? Используйте Cmd+K (Ctrl+K) для поиска по тексту ошибки во всей базе знаний. Также обратитесь к урокам модулей для более глубокого понимания механизмов и диагностики проблем.