Design by Contract в эпоху AI: как контракты Мейера защищают криптографию там, где тесты молчат
В марте 2026 года в сеть утекли внутренние материалы Anthropic о модели Claude Mythos - и почти сразу компания официально представила Mythos публично. Самое громкое в этой истории не название и не маркетинг, а результат: модель нашла 0-day в OpenBSD, который спокойно прожил в кодовой базе двадцать семь лет. Почти одновременно всплыла уязвимость схожего масштаба в FFmpeg - возрастом шестнадцать лет - а затем зацепило и Linux kernel. Ирония в том, что уязвимые участки годами проходили проверки, статический анализ и бесконечные прогоны тестов. Для OpenBSD, по рассказам, хватило менее пятидесяти долларов вычислений, чтобы добраться до результата, на который у людей не хватало ни времени, ни внимания десятилетиями.
Вопрос теперь не в том, "впечатляет ли" это, а в том, что делать с реальностью, где поиск слабых мест автоматизируется и дешевеет быстрее, чем команды успевают перестраивать практики. Запретить AI на аудит невозможно, как невозможно "закрыть" саму идею ускоренного поиска ошибок. Значит, единственный устойчивый ответ - проектировать системы так, чтобы критические логические промахи не доживали до продакшна.
Часто предлагают универсальную таблетку: "переписать на Rust". В мире memory corruption это действительно меняет правила игры: модель владения и заимствований не дает просто так собрать use-after-free, заметно сложнее пронести buffer overflow, а многие резонансные баги последних лет как раз из этой категории. Но у криптосистем и инфраструктуры доверия есть другой, не менее опасный слой - логические ошибки. Их не ловит компилятор, и они прекрасно мимикрируют под "рабочий код": выбрали неправильный padding для RSA - тишина; забыли проверку revocation у сертификата - все зеленое; приняли "сырую" энтропию в DRBG без проверки качества - тесты не ругаются, потому что никто не догадался написать тест именно на это.
Проблема тестов не в том, что они "плохие". Проблема в их природе: тест проверяет ровно то, что человек заранее сформулировал. Если сценарий не пришел в голову - он не будет проверен. Именно поэтому уязвимость может спокойно существовать десятилетиями, не проваливая ни одного прогона: формально "все работает", пока не появится тот, кто задаст правильный вопрос.
Значит, нужен подход, который фиксирует требования не на уровне "как реализовано", а на уровне "какие гарантии обязаны выполняться всегда". Реализацию можно переписать, оптимизировать, распараллелить и ускорить. А гарантия либо соблюдается, либо нарушена - независимо от того, насколько красивый код и сколько тестов проходит.
Такой подход придумали давно - еще в 1986 году. Его автор - Бертран Мейер, а идея называется Design by Contract (DbC). Суть проста и одновременно жестка: любой модуль обязан явно объявить три набора условий.
- PRE (предусловия): что должно быть истинно на входе.
- POST (постусловия): что станет истинным на выходе, если предусловия соблюдены.
- INV (инварианты): что остается истинным всегда, пока объект/модуль считается корректным.
Простейший пример - деление: предусловие "делитель не равен нулю", постусловие "результат, умноженный на делитель, дает делимое" (с учетом оговорок о типах и округлениях). Это не комментарии "на добром слове", а формальная рамка: нарушил - значит, модуль неисправен.
Почему же DbC не стал стандартом индустрии, если все выглядит столь логично? Ответ приземленный: двойная работа. В реальной разработке это означает: сначала ты формулируешь контракт, потом пишешь код, потом пишешь тесты и обвязку. Контракт - отдельный артефакт, который требует дисциплины, времени и не дает мгновенной отдачи. Когда сроки горят, выкидывают именно то, что "не обязательно для сборки" - контракты исчезают первыми.
Там, где цена ошибки чудовищна, DbC выжил в разных обличьях. В авиации и критическом софте - через Ada/SPARK и строгие методологии. В automotive - под давлением практик уровня ISO 26262. По сути, это те же "обязательные гарантии", только в терминах и процессах конкретной индустрии. В массовой разработке такого бюджета терпения нет - и поэтому Eiffel (язык Мейера с контрактами в основе) остался нишевым; Microsoft Code Contracts для C# были заброшены; Java ограничилась assert без обязательной дисциплины; D добавил контракты в синтаксис, но это не сделало подход повседневной нормой. Идея расползлась по утверждениям, аннотациям и типам, но не стала архитектурным стандартом.
И вот здесь появляется новая переменная: AI-агент. DbC десятилетиями ждал момента, когда "вторую половину работы" можно будет кому-то делегировать - рутину, оформление, перебор, поддержание формальной строгости. С приходом LLM и агентных пайплайнов этот момент стал технически возможен: агент способен помогать превращать требования в формальные PRE/POST/INV, подсказывать недостающие условия на стыках, генерировать каркас проверок и следить, чтобы контракты не разъехались с кодом при рефакторингах.
За последние годы вокруг этого возникла и исследовательская волна: от идей нейросимвольного "контрактного слоя" для надежных агентов до подходов, где модель генерирует спецификации для больших массивов C/C++ кода. Встречаются и концепции вроде "prompt contracts" и "файлы как контракты". Но у многих подходов есть общий изъян: либо это теория без демонстрации на живой системе, либо промт-практика без строгих PRE/POST/INV, где "контракт" превращается в расплывчатую договоренность.
Дальше начинается самое интересное: криптография и PKI - область, где тесты особенно часто молчат именно там, где "нельзя ошибаться". Потому что криптографические интерфейсы обычно выглядят корректно даже тогда, когда система уже обречена: ключи генерируются, подписи ставятся, рукопожатия проходят, сертификаты выдаются. Но внутренние нарушения требований безопасности не отражаются в функциональной корректности.
Почему криптографии нужны контракты сильнее, чем большинству кода
Криптография - это не про "работает/не работает", а про "сохраняет ли свойства". Конфиденциальность, неподделываемость, стойкость к повтору, корректная проверка цепочек доверия - это свойства, которые легко разрушить одним неверным условием на входе. При этом большинство библиотек честно выполняют то, что им сказали, не задавая лишних вопросов. И именно поэтому контрактный стиль становится не украшением, а способом зафиксировать безопасность как обязательство.
Proof-идея на практике: PKI с аппаратным TRNG и контрактами
В рамках практической проверки подхода можно собрать PKI-систему, где источник энтропии - аппаратный TRNG, а критические узлы оформлены контрактами. Ключевой смысл здесь не в "модной железке", а в том, что TRNG - это место, где чрезвычайно важно формализовать ожидания: энтропия должна быть не просто "какими-то байтами", а потоком, удовлетворяющим требованиям качества и проверкам здоровья.
Два стыка, где контракты дают максимальный эффект
Стык №1: TRNG → сбор энтропии → DRBG/генерация ключей.
Тут контракт должен запрещать превращать "непроверенный шум" в основу долгоживущих ключей.
Примеры того, что фиксируется контрактом:
- PRE: устройство TRNG инициализировано, тесты здоровья пройдены, источник не в деградированном режиме.
- PRE: запрошенный объем энтропии соответствует минимальному порогу для операции (например, генерации ключа заданного класса).
- POST: возвращенные данные имеют метаданные качества (или признак прохождения проверки), а не просто массив байт.
- INV: запрещено использовать энтропию, помеченную как "сомнительная/непроверенная", для операций генерации ключей и сидирования.
Стык №2: проверка сертификатов → политика доверия → принятие решения.
Здесь ломаются не алгоритмы, а процессы: пропущенная проверка revocation, неправильная обработка цепочки, неверные ограничения по назначениям ключа.
Что фиксируют контракты:
- PRE: входной сертификат соответствует ожидаемому профилю (назначение ключа, алгоритмы, допустимые параметры).
- PRE: для "решения о доверии" обязателен результат проверки статуса (не "по возможности", а как условие).
- POST: если сертификат принят, то зафиксированы причины принятия (какая цепочка, какие политики, какой статус отзыва).
- INV: решение "доверять" не может быть выдано, если отсутствуют обязательные проверки, даже если подпись корректна.
Как это выглядит в рабочем процессе (когда помогает AI-агент)
Практический пайплайн обычно сводится к трем шагам.
1) Вы формулируете требования на человеческом языке: что считается корректным состоянием, какие запреты абсолютны, какие условия допустимы только в тестовом окружении, что должно логироваться и воспроизводиться.
2) AI-агент помогает превратить это в формальные PRE/POST/INV: предлагает формулировки, выявляет "дырки" (например, забытое условие про уникальность nonce или запрет повторного использования сидов), генерирует каркас проверок и негативные сценарии.
3) Контракты становятся частью сборки и рантайма: в debug/CI их можно делать максимально строгими, а в production - выбирать режим (полный, частичный, выборочный), чтобы не убить производительность.
Ключевое отличие от "просто ассёртов" - в том, что контракт не про "поймать null", а про фиксацию архитектурного обещания. Если компонент по контракту обязан проверять revocation, это не "желательно", а "иначе компонент считается некорректным".
Где контракты сильнее тестов
- Тесты ловят регрессии по известным сценариям, контракты ловят нарушения правил даже в новых сценариях.
- Тесты не гарантируют полноту, контракт задает границы допустимого поведения.
- Тесты часто проверяют "что получилось", контракт фиксирует "что разрешено получить".
Ограничения и честные оговорки
Контракты не превращают систему в математически доказанную. Они не заменяют криптографические доказательства, не отменяют необходимость аудита и не страхуют от неверно сформулированной спецификации. Если контракт написан плохо, он будет столь же бесполезен, как и плохой тест. Более того, жесткие проверки могут конфликтовать с реальностью эксплуатации: аппаратные генераторы могут деградировать, сети - отваливаться, инфраструктура статуса сертификатов - быть недоступной. Поэтому контракты нужно проектировать вместе с политиками деградации: что делать при недоступности проверки статуса, какие режимы запрещены, какие допустимы только в ограниченном контексте.
Но ключевой выигрыш DbC в криптографии - в другом: он заставляет заранее назвать вещи своими именами. Не "потом добавим проверку", а "без этой проверки решения о доверии не существует". Не "возьмем энтропию откуда-нибудь", а "без сигналов качества энтропия не имеет права стать сидом".
AI ускоряет поиск багов и удешевляет атаки на качество кода. Следовательно, выиграют не те, кто напишет больше тестов на вчерашние сценарии, а те, кто сумеет зафиксировать обязательные гарантии на уровне архитектуры - и сделать так, чтобы система сама не позволяла нарушать их "по пути". Design by Contract задумывался именно для этого. Просто раньше он стоил слишком дорого по времени. Теперь часть этой цены можно переложить на AI-агента - и наконец использовать DbC там, где молчание тестов особенно опасно: в криптографии и инфраструктуре доверия.



