Merkle tree для идентификации

Oct 21, 2019 15:26

Криптохэши прекрасно подходят для идентификации статичных данных. Запросили из content-addressable storage (CAS) файлик (точнее blob) по криптохэшу, вот он пришёл, всё можно проверить, ничего нельзя подменить (с поправкой на стойкость криптохэш-функции). Но проверить можно только целиком. А если идентификация не совпала - можно попробовать перекачать, снова целиком, так как неизвестно, какие фрагменты скачаны с ошибками. Для мелких данных ок, для гигабайтных блобов уже нет. Стандартный приём - построить Merkle tree, на нижнем уровне считая криптохэши от фрагментов в N байт, а на каждом следующем криптохэш от списка криптохэшей предыдущего уровня.

Как идентифицировать выбранный алгоритм построения Merkle tree (учитывая разные возможные подходы)? На мой взгляд, удобно задать Merkle tree через криптохэш-функцию и параметр N как максимальный размер и фрагментов данных, и неразрывных фрагментов списков криптохэшей. Например, выбрали SHA-256 и N = 64KiB (2**16) - это фрагменты до 65536 байт данных на нижнем уровне и до 2048 полных криптохэшей SHA-256 на верхних. Тогда получившуюся функцию можно обозначить как SHA-256-P64K (или sha256p64k). При таком подходе идентификация блобов до 64KiB включительно совпадёт с SHA-256, идентификация от 64KiB+1 до 128MiB включительно потребует одного уровня косвенности, и так далее. Обращаемся к CAS, получаем по SHA-256-P64K содержимое файла или карту фрагментов следующего уровня, которые можно параллельно скачивать и проверять, хоть с этого же CAS (с любым приемлемым количеством соединений), хоть с других доверенных источников.

Людей, знакомых с рассматриваемыми технологиями, могла насторожить фраза "идентификация блобов до 64KiB включительно совпадёт с SHA-256". Каждый уровень Merkle tree принято наделять маркером, используемом при вычислении криптохэша на этом уровне. Можно, например, вставить байт 00 перед вычислением криптохэшей от фрагментов данных, байт 01 перед вычислением криптохэшей от фрагментов списков криптохэшей нижнего уровня, и так далее. Без этого идентификация через Merkle tree теряет уникальность, те же байты могут идентифицировать и сами данные, и их "карту", чем могут воспользоваться злоумышленники.

Но я вижу и другое решение. Вместо использования уровня косвенности в качестве префикса при вычислении каждого конкретного криптохэша - использовать это значение в операции xor с последним байтом получаемого криптохэша. В этом случае (для примера с SHA-256-P64K) фрагменты блобов на нижнем уровне будут идентифицироваться по SHA-256, фрагменты списков криптохэшей на следующем - по результату SHA-256 с (hash[31] ^= 1), и так далее с (hash[31] ^= indirection_level). Такая операция возвращает уникальность и обладает тем интересным свойством, что идентификация данных какого-либо уровня и их "карты" расходятся только в последнем байте. Что упрощает отладку и поддержку CAS.

Покрутил я эту модель, прикинул различные сценарии поведения (вплоть до случаев memory error, с учётом прочих метаданных в CAS), и пока выглядит оптимально. Если у вас есть свои соображения - пишите. Чуть позже напишу продолжение об идентификации не-статичных данных.
Previous post Next post
Up