Геометрический forensic-watermark в моделях конструктора — спайк
Статус: спайк закрыт, решение принято — НЕ ВНЕДРЯЕМ (основатель, 2026-07-31;
docs/adr/ad-1136-geometric-watermark-not-implemented.md). Причина — § 4.3: перенос детали на столе слайсера, штатное действие перед печатью, сбивает чтение метки до случайного угадывания, поэтому схема даёт ложное чувство защищённости. Документ остаётся как результат замеров, а не как открытый вопрос: подключать описанное здесь к экспорту нельзя, и повторный спайк на ту же тему не нужен — условия пересмотра названы в § 5 ADR. Родитель — эпик AD-1133 (защита моделей: provenance + юридическая, не DRM); соседи по эпику —LICENSE.txtв архиве (AD-1134) и метка происхождения в заголовке STEP / имени тела STL (AD-1265) — они остаются в силе.
1. Вопрос, на который отвечает спайк
Можно ли спрятать идентификатор пользователя в самой геометрии детали так, чтобы одновременно выполнялось всё:
- не ломает печать — деталь печатается как прежде;
- не меняет аэродинамику — обводы остаются теми же;
- не виден глазом на напечатанной детали;
- извлекается из файла — по файлу можно назвать владельца.
Тикет прямо допускает отрицательный ответ: если незаметность и извлекаемость на наших размерах несовместимы — это честный результат. Они совместимы. Несовместимо другое, и об этом § 5.
2. Что померили и на чём
Стенд — обычный экспорт v2-редактора (борт размахом 1 м, фюзеляж 0.8 м): ASCII-STL 32.5 МБ, 385 812 вершин; STEP 623 КБ, 6 995 контрольных точек.
Разрешающая способность самих форматов (замер, не оценка):
| Формат | Как пишутся координаты | Наименьший различимый шаг |
|---|---|---|
| ASCII-STL (OCCT) | %.6e — 7 значащих цифр (4.259878e+02) |
~10⁻⁴ мм на габарите 0.4 м, ~10⁻³ мм на 1.5 м |
| STEP (OCCT) | полная двойная точность (425.98775446637) |
~10⁻¹¹ мм |
То есть формат не является ограничением: в него влезает шаг модуляции на порядки мельче того, что различает печать.
3. Схема PoC
Слепая (blind) QIM-модуляция по значению координаты. Для каждой вершины:
индекс бита = hash(round(X/Δ), round(Y/Δ)) mod N
сам бит = чётность round(Z/Δ) → Z «дотягивается» до нужной ячейки
Три свойства, ради которых схема выбрана именно такой:
- ключ по X/Y, а меняется Z — одинаковые вершины соседних фасетов сдвигаются одинаково, и меш остаётся водонепроницаемым без разбора смежности (в ASCII-STL вершина дублируется в каждом фасете). Замер: вырожденных треугольников после встраивания — 0 (до встраивания тоже 0);
- извлечение слепое — исходный файл не нужен;
- адресация по позиции, а не по порядку вершин — отрезанный кусок детали несёт метку целиком. Для нашего экспорта по печатным деталям (AD-1128, борт режется на сегменты) это не бонус, а необходимое условие.
4. Результаты замеров
Прогон: node scripts/ad1136_watermark_poc.mjs <файл.stl> [--step=мм] [--bits=N]
4.1 Ёмкость и стоимость (Δ = 0.01 мм, 128 бит)
| Величина | Значение |
|---|---|
| вершин обработано | 385 812 |
| покрыто бит | 128 / 128 |
| голосов на самый «бедный» бит | 2 744 |
| прочитано из нетронутого файла | 100 % |
| сдвиг поверхности | max 10 мкм, средний 5 мкм |
| вырожденных треугольников | 0 |
| прирост размера файла | −1.2 % (перезапись чисел, не рост) |
| время | встраивание 0.54 с, извлечение 0.16–0.29 с |
Ёмкость не является ограничением: 512 бит читаются так же на 100 %, самый бедный бит собирает 545 голосов.
4.2 Незаметность — сравнение с физикой печати
Сдвиг 10 мкм (Δ = 0.01 мм) против того, что умеет FDM:
| Величина | Порядок | Отношение к метке |
|---|---|---|
| высота слоя | 100–200 мкм | в 10–20 раз крупнее |
| ширина экструзии (сопло 0.4) | 400–450 мкм | в 40 раз крупнее |
| шероховатость поверхности Ra | 10–20 мкм | того же порядка или крупнее |
| шаг позиционирования XY | ~50 мкм | в 5 раз крупнее |
| цена деления штангенциркуля | 10–20 мкм | того же порядка |
Ключевая строка — шероховатость: метка мельче собственного микрорельефа печати, то есть на напечатанной детали её физически нечего увидеть и нечем измерить. Аэродинамика: 10 мкм на хорде 200 мм — это 5·10⁻⁵ относительной деформации обвода, на два порядка ниже шероховатости той же поверхности; вклад в поляру неотличим от нуля любым доступным нам методом (ни XFOIL, ни VLM таких величин не различают в принципе).
Пункты 1–4 из § 1 выполняются одновременно. Ответ на вопрос спайка — положительный.
4.3 Устойчивость — где схема ломается (главный результат)
Доля верно прочитанных бит после операции над файлом. Идентификатор читается только при 100 %; ~50 % — это случайное угадывание, то есть метки нет.
| Операция над файлом | Δ = 0.002 мм | Δ = 0.01 мм | Δ = 0.05 мм |
|---|---|---|---|
| копия файла | 100 % | 100 % | 100 % |
| сдвиг по Z на целое число ячеек | 100 % | 100 % | 100 % |
| перенос детали на (0.137, 0.041, 0.213) мм | 44 % | 48 % | 53 % |
| шум ±2 мкм (пересчёт координат) | 48 % | 100 % | 100 % |
| шум ±20 мкм (грубый пересчёт/ремеш) | 53 % | 50 % | 100 % |
| масштаб ×1.001 (подгонка размера) | 99 % | 95 % | 74 % |
Читается это так:
- Δ задаёт компромисс, а не качество. Мельче — незаметнее, но ломается от любого пересчёта координат; крупнее — переживает шум, но подбирается к разрешению печати и всё равно не переживает масштабирование.
- Ни одно значение Δ не спасает от переноса и масштабирования. Оба — один клик в любом mesh-редакторе или слайсере (и, что важнее, штатная операция: человек двигает деталь на столе принтера).
- Настоящий ремеш/децимация (не эмулированные здесь шумом, а перестроение сетки) убивает метку тем более: она живёт в координатах ИСХОДНЫХ вершин.
⚠ Методическая заметка: первый прогон PoC «доказал» устойчивость к переносу на 7.3 мм — потому что 7.3 мм это ровно 730 ячеек сетки, и чётность сохранилась. Ложно-успешный замер, атака переписана на некратное смещение. Строка «сдвиг на целое число ячеек» оставлена в таблице как контроль: она показывает, что схема инвариантна к сдвигу ПО СЕТКЕ и ни к какому другому.
5. Выводы
- Незаметность и извлекаемость на наших размерах совместимы — с запасом в один-два порядка по каждому из четырёх требований § 1. Это НЕ то место, где идея упирается.
- Упирается она в устойчивость. Метка переживает то, что делает недобросовестный пользователь ЛЕНИВО (скачал → выложил как своё, сконвертировал формат, разрезал на печатные детали), и не переживает того, что делает пользователь ОБЫЧНО (подвинул деталь на столе, подогнал масштаб) — не говоря о том, что сделал бы намеренно (ремеш в 3 клика). То есть это forensic-метка против ленивой перепубликации, а не защита.
- Секретность схемы — не защита. Универсальная атака у этого класса схем одна и тривиальна: пересчитать координаты (шум/переквантование/ремеш). Стойкость не в том, что метку нельзя снять, а в том, что снимать её никто не станет, пока не знает, что она есть.
- Это НЕ замена AD-1265, а другой слой. Заголовок STEP / имя тела STL — ЯВНАЯ метка: её видно, её легко удалить, но она честная и работает как provenance. Геометрическая метка — наоборот: снять её случайно нельзя, а найти без исходного ключа тоже нельзя. Слои дополняют друг друга и не конкурируют.
6. Чего внедрение будет стоить (если решат внедрять)
Это оценка, а не план: тикет — спайк, продовой части в нём нет.
- Только STL. PoC работает по вершинам сетки. Для STEP та же схема ложится на контрольные точки (точности формата хватает с запасом), но любой пересчёт B-rep в CAD её снимает; отдельный замер по STEP не делался.
- Память. Метку приходится ставить ПОСТ-ФАКТУМ, текстом поверх готового блоба: сетку пишет OCCT, до неё не дотянуться. Для 32 МБ STL это ~65 МБ JS-строки в воркере, который и без того упирается в порог перезапуска по WASM-heap (AD-1140, 512 МБ). Это прямо противоречит конструкции AD-1265, где метка происхождения читает только КРАЯ блоба именно ради независимости цены от размера модели. Потоковая (по-чанковая) реализация — отдельная работа.
- Секрет. В проде хэш обязан солиться секретом из
web/config.py, иначе сопоставить метку с пользователем сможет любой, кто видел код. Соль — новый секрет в контуре эксплуатации. - Юридически — решение основателя, не исполнителя. Скрытая метка в файле пользователя без раскрытия в «Условиях использования» (раздел 19, AD-1132) — это скрытое отслеживание. Раскрытие метки её forensic-ценность НЕ снижает (стойкость не зависит от секретности факта, § 5.3), но снижает репутационный риск до нуля. Внедрять без явного решения по этому пункту нельзя.
7. Решение основателя (2026-07-31): не внедряем
Спайк спрашивал три вещи — внедряем ли вообще, раскрывать ли метку в «Условиях использования» и как резать работу на тикеты. Ответ на первый вопрос отрицательный, поэтому два остальных сняты: ни правки раздела 19, ни потоковой реализации, ни соли в конфиге не будет.
Обоснование — § 4.3: перенос детали на некратное смещение оставляет 48 % верно прочитанных бит, то есть метки не остаётся. Двигают деталь на столе слайсера все и всегда, это первое действие перед печатью, а не атака. Схема, которая исчезает у того, кто файл просто напечатал, и работает только против того, кто переслал его байт-в-байт, даёт ложное чувство защищённости при настоящей цене внедрения (§ 6).
Полная формулировка решения, что остаётся в силе из эпика AD-1133 и при каких
условиях к теме уместно вернуться —
docs/adr/ad-1136-geometric-watermark-not-implemented.md. Отдельного тикета
реализации не заводить.