Цикл зерна canvas в CreepJS: сколько раундов остаются разными
CreepJS умножает зерно canvas на 15000 по модулю 2001000001 каждый раунд. Эта проба измеряет, сколько раундов остаются разными до повторения последовательности.
BotBrowser Team
Нужна структурированная документация по теме Отпечатки?
Эта статья относится к редакционной библиотеке. Для пошаговой настройки, справки и постоянных обновлений переходите сразу в соответствующий раздел docs.
BotBrowser может воспроизвести объявленный профиль на страничном canvas-фикстуре и сохранить ограниченную запись того, что страница нарисовала; он не делает короткий рисунок представлением длинного и не может изменить то, как библиотека порождает собственное зерно. Модуль canvas в CreepJS строит каждый раунд из состояния, которое умножается на константу и берётся по модулю другой, и то, сколько последовательность остаётся разной до повтора, решает, какая часть длинного рисунка новая. Разбор метрик эмодзи CreepJS отделяет геометрию текстовых представителей от этой последовательности рисования и пиксельного результата.
Кратко
- CreepJS задаёт зерно canvas равным 500, умножает на 15000 и берёт по модулю 2001000001, а затем по умолчанию рисует 10 раундов.
- Эти опубликованные значения дают 8 разных значений в первых 8 выборках и 10 разных целых позиций на 10 раундах.
- Мультипликативная последовательность повторяется ровно через 26940 выборок, поэтому рисунок по умолчанию из 10 раундов никогда не возвращается в прежнее состояние.
- Пять раундов этого рисунка являются префиксом десяти: короткая и длинная страницы делят первые пять значений.
- Смена только зерна сдвигает последовательность, но не её длину: зёрна 500, 501, 1000 и 2000 повторяются на 26940.
- Отчёт, который хранит digest без числа раундов, зерна и ревизии источника, не может сказать, какая часть рисунка была новой.
Содержание
- Опубликованный генератор
- Сколько раундов остаются разными
- Что означает префикс для сравнения
- Где счёт перестаёт быть информативным
- Записать чтение canvas
Опубликованный генератор
Отчёт хранит ревизию источника вместе с зерном и числом раундов, чтобы последовательность можно было пересчитать офлайн.
Модуль определяет небольшой генератор и отображение его сырого значения в координату canvas: Модуль это отпечаток canvas, поэтому его первая задача нарисовать то, что читается по-разному на разных машинах и одинаково на одной. Генератор даёт детерминированную половину, рендерер другую.
source -> https://raw.githubusercontent.com/abrahamjuliot/creepjs/master/src/canvas/index.ts
source_bytes -> 22274 sha256=6bb5e580122bb26343e1fd8108fb2db6e5a719ab8beebf04394779f06139047d
defaults -> seed=500 offset=2001000001 multiplier=15000
rounds_default -> 10
Генератор держит текущее значение ниже модуля. Каждый вызов умножает его на 15000 и берёт остаток, а отображение делит на модуль, масштабирует по размеру canvas и берёт целую часть. Оба шага это целочисленная арифметика без случайности в момент выборки, и поэтому одно и то же зерно при одной ревизии источника даёт одни и те же позиции раундов.
Это осознанный выбор. Отпечаток canvas должен быть стабильным для данной машины и версии библиотеки, поэтому он не может рисовать новые пиксели при каждом вызове. Он стартует от зерна и выводит из него всё остальное, а зерно это та часть, которую задаёт машина или профиль.
Поэтому отчёт хранит ревизию источника, а не просто URL: файл в master меняется, и константы могут меняться вместе с ним.
Сколько раундов остаются разными
Длина цикла также разделяет два вопроса: насколько последовательность может вырасти до повтора и сколько раундов использует рисунок по умолчанию.
С этими значениями первые выборки все разные: Восемь значений ниже это сырой выход генератора до деления на модуль и масштабирования в пиксель, поэтому два отчёта с разным размером canvas всё ещё сравнимы по ним.
first_draws -> 7500000 443999944 671156672 319074969 1733532609 1995122006 1875075045 69660944
first_draws_distinct -> 8 of 8
round_positions -> distinct=10 of 10 rounds
Восемь сырых значений и десять целых позиций без повторов. Рисунок по умолчанию использует десять разных состояний, и каждое попадает в свою позицию на canvas. Ничего в прогоне по умолчанию не рисуется дважды из одного состояния.
Это число охватывает только десять последовательных состояний генератора и не считает состояния, которые потребляет раунд CreepJS: модуль вызывает getNextSeed() несколько раз за раунд для градиента, тени и метода рисования, поэтому раунд проходит больше одного состояния.
Длина цикла намного больше значения по умолчанию:
cycle -> first_repeat_index=0 length=26940
cycle_covers_rounds -> true
26940 выборок до возврата последовательности к начальному значению. Десять раундов по умолчанию используют 10 из этих 26940 состояний, поэтому рисунок это крошечный префикс гораздо более длинной последовательности. Страница, которая попросит 500 раундов, всё ещё не повторит состояние, а страница с 27000 дойдёт до края цикла.
Что означает префикс для сравнения
Короткий и длинный рисунки с одними параметрами не различаются с начала; один содержит другой: Та же разница между стабильным зерном и отрисованной поверхностью это то, что отпечаток canvas записывает со стороны поверхности. Страница может держать рисунок живым и дописывать в него; в такой форме первые пять состояний второго рисунка не первые в последовательности, поэтому свойство префикса верно только при повторном задании зерна.
Запись без слоя рендеринга не отделяет другую последовательность от другой отрисовки, поэтому обе половины хранятся вместе.
Минимальная запись содержит seed, rounds, digest источника и размер canvas, а также указывает, какой рендерер ответил.
Ревизия источника и размер canvas сохраняются вместе с digest, потому что один digest не разделяет последовательность и отрисовку.
Сравнение между машинами идёт в таком порядке: ревизия источника, зерно, число раундов, размер canvas, рендерер и только затем digest.
Отображение берёт целую часть, а не округляет: состояние на 37.9 попадает в пиксель 37, а на 38.1 в 38, поэтому два соседних состояния могут совпасть по координате или разойтись в зависимости от размера.
Предел масштаба заслуживает отдельного чтения: на canvas в 50 пикселей всего 50 целых позиций на ось, поэтому два разных состояния могут сойтись в одну координату; на большем canvas такие совпадения реже.
Digest canvas.toDataURL() скрывает, был ли добавлен десятый раунд: первые раунды при seed=500 совпадают в обоих случаях.
Отчёт о запросе, поэтому его поля отражают запрос, а не картинку: картинка может выглядеть одинаково после смены рендерера, а запрос после смены масштаба, и только запись, разделяющая оба набора полей, может их различить.
prefix -> five_rounds_are_prefix_of_ten=true
Это имеет практическое следствие для любого сравнения, которое хранит один digest. Две страницы с одним зерном и разным числом раундов дают разные digest, потому что десятый раунд меняет пиксели. Читайте эту разницу как изменение длины рисунка, а не как изменение браузера. Разбор, который считает каждый разный digest различием устройства, припишет параметр страницы окружению.
Та же асимметрия появляется, когда страница добавляет раунд. Первые пять состояний не тронуты, поэтому сравнение по пикселям на общей площади не находит различий, тогда как digest всей картинки меняется. Отчёт, который хранит только полный digest, не может сказать, какой раунд добавлен.
Эта структура объясняет, почему два отчёта CreepJS с разных страниц могут различаться, описывая одну машину. Состояние генератора детерминировано, поэтому двигаются только зерно, число раундов и ревизия источника. Две страницы, разделяющие все три, дают одинаковые позиции, а две страницы с общим зерном расходятся с первого лишнего раунда. Сравнение, которое не знает числа раундов, не может решить, какой из этих случаев перед ним. Профиль, повторяющий один и тот же рисунок между визитами, ставит вопрос воспроизводимости шумового зерна, где сохраняемое состояние это генератор canvas. Множитель 15000 и модуль 2001000001 это два числа, которые стоит сохранить, потому что любое изменение в них описывает другой источник, а не другую машину.
Где счёт перестаёт быть информативным
Полезная цифра происходит из модуля и множителя, а не из зерна: Длина цикла это свойство модульной арифметики, а не зерна: при фиксированных модуле и множителе любая взаимно простая начальная точка проходит один и тот же цикл, а точка с общим делителем более короткий.
Значение multiplier=15000 и модуль offset=2001000001 его фиксируют, поэтому отчёт может назвать оба и проверить gcd перед сравнением двух прогонов.
seed:500 -> cycle=26940 draws_8_distinct=1
seed:501 -> cycle=26940 draws_8_distinct=1
seed:1000 -> cycle=26940 draws_8_distinct=1
seed:2000 -> cycle=26940 draws_8_distinct=1
Все проверенные зёрна повторяются на 26940 выборках, потому что модуль и множитель задают длину цикла для любой начальной точки, взаимно простой с модулем. Зерно выбирает, где в последовательности начинается рисунок; оно не меняет длину последовательности.
Второй столбец этой выдачи обманывает намеренно и показывает ловушку измерения. Проба создавала генератор внутри одного выражения для каждой из восьми выборок, поэтому каждый вызов снова начинался от зерна и возвращал одно и то же первое значение. Написанная так проба сообщает одно разное значение и заключает, что генератор вырожден. Правильное чтение это измеренное выше: восемь разных значений из одного экземпляра. Отчёт, основанный только на digest, может спутать страницу, которая сбрасывает генератор, со страницей, которая его продолжает.
Эту ловушку стоит назвать, потому что это самый частый способ получить ложное срабатывание в пробе зерна. Создание генератора внутри цикла или его сброс между выборками измеряет первое значение снова и снова и никогда не касается последовательности.
Значения multiplier=15000 и offset=2001000001 хранятся вместе с зерном, потому что без них последовательность не пересчитать офлайн.
Страница, которая нарисует больше 26940 раундов, увидит повтор состояния, а страница с тысячами раундов останется далеко внутри цикла; рисунок по умолчанию с rounds=10 от этой границы очень далёк.
Записать чтение canvas
Чтение canvas подтверждает узкое утверждение: эта ревизия источника, это зерно и это число раундов дали такие разные позиции. Четыре поля сохраняют его проверяемым:
- Ревизия и digest источника, потому что изменение множителя или модуля меняет каждую выборку.
- Зерно и число раундов, потому что они решают, какую часть последовательности использует рисунок.
- Счётчики разных состояний и позиций, потому что один digest не говорит, добавлен раунд или сдвинуто зерно.
- Время жизни генератора, потому что создание заново на каждой выборке превращает последовательность в повторённое первое значение.
Статья о таблицах шрифтов читает третий публичный набор таблиц так же, и её счётчики и ведра стоят рядом с циклом зерна canvas.
Открытые источники
- CreepJS
- Модуль canvas в CreepJS
- MDN: CanvasRenderingContext2D
- BotBrowser: справка по согласованности автоматизации
Практический вывод
Храните зерно, число раундов и ревизию источника с каждым сравнением canvas, потому что один digest не различает более длинный рисунок и другой рисунок. Считайте пять раундов префиксом десяти, когда зерно и ревизия совпадают, и ожидайте, что digest всё равно будет различаться. Читайте цикл в 26940 выборок как расстояние, на которое рисунок может вырасти до повтора, а не как свойство десяти раундов по умолчанию. Про арифметику за более длинным сравнением canvas читайте как CreepJS группирует свои сигналы canvas; про границу между чтением со стороны страницы и со стороны соединения читайте правила сетевого уровня, недоступные странице.
Эта статья входит в руководство: Что такое отпечаток браузера?
Похожие статьи
Детерминированный режим: воспроизводимые отпечатки
ОтпечаткиВоспроизводимость шумового зерна для повторяемых запусков
ОтпечаткиМетрики эмодзи CreepJS: множества и суммы
ОтпечаткиОтпечаток Canvas: как он работает и что помогает
ОтпечаткиFingerprinting измерения текста: субпиксельное отслеживание
ОтпечаткиТаблицы шрифтов CreepJS: 51 имя и 14 вариантов возврата
Переведите BotBrowser из исследований в продакшн
Используйте эти руководства, чтобы понять модель, а затем перейти к кроссплатформенной валидации, изолированным контекстам и масштабируемому браузерному развертыванию.