В библиотеке TweetNaCl.js (и её обёртке nacl.js) функция
nacl.hash реализует криптографическую хеш-функцию SHA-512,
возвращающую фиксированный 64-байтовый (512-битный) результат. Именно
этот детерминированный и необратимый механизм лежит в основе построения
деревьев Меркла, где каждый узел представляет собой хеш своих дочерних
элементов.
const hash = nacl.hash(messageUint8Array);
Важная особенность nacl.hash — отсутствие необходимости
вручную управлять внутренними состояниями алгоритма. На вход подаётся
массив Uint8Array, на выходе всегда получается новый
Uint8Array фиксированной длины.
Дерево Меркла представляет собой бинарную структуру, где:
Ключевой принцип:
изменение любого элемента данных приводит к изменению корневого хеша
Перед построением дерева данные необходимо привести к байтовому виду.
В JavaScript это обычно Uint8Array.
function toUint8Array(str) {
return new TextEncoder().encode(str);
}
Каждый элемент данных становится листом дерева после хеширования:
const leafHash = nacl.hash(toUint8Array("data"));
Листья формируются как индивидуальные хеши элементов:
function createLeaves(dataArray) {
return dataArray.map(item => nacl.hash(toUint8Array(item)));
}
Каждый элемент массива проходит через SHA-512, что обеспечивает:
Основной принцип построения:
nacl.hashПри объединении двух узлов важно использовать строгое преобразование в байты:
function concatUint8Arrays(a, b) {
const result = new Uint8Array(a.length + b.length);
result.set(a, 0);
result.set(b, a.length);
return result;
}
Хеширование пары:
function hashPair(left, right) {
const combined = concatUint8Arrays(left, right);
return nacl.hash(combined);
}
Один уровень дерева формируется так:
function buildLevel(nodes) {
const level = [];
for (let i = 0; i < nodes.length; i += 2) {
const left = nodes[i];
const right = nodes[i + 1] || nodes[i]; // дублирование при нечётном количестве
level.push(hashPair(left, right));
}
return level;
}
Дублирование последнего узла при нечётном количестве элементов — стандартная практика для сохранения бинарной структуры.
function buildMerkleTree(dataArray) {
let level = createLeaves(dataArray);
const tree = [level];
while (level.length > 1) {
level = buildLevel(level);
tree.push(level);
}
return {
root: level[0],
tree
};
}
Использование SHA-512 через nacl.hash накладывает ряд
особенностей:
Это критично для Merkle-деревьев, так как:
структура должна быть полностью воспроизводимой на разных системах
Для отладки часто требуется перевод байтов в hex:
function toHex(bytes) {
return Array.from(bytes)
.map(b => b.toString(16).padStart(2, "0"))
.join("");
}
Merkle-доказательство строится на пути от листа к корню. Каждый шаг включает:
function verifyPath(leaf, path) {
let hash = nacl.hash(toUint8Array(leaf));
for (const step of path) {
const combined = step.direction === "left"
? concatUint8Arrays(step.hash, hash)
: concatUint8Arrays(hash, step.hash);
hash = nacl.hash(combined);
}
return hash;
}
Если результат совпадает с известным корнем дерева — элемент подтверждён.
Merkle-дерево чувствительно к порядку входных данных. Два массива с одинаковыми элементами, но разным порядком, дадут разные корни:
["A", "B"] ≠ ["B", "A"]Это свойство используется для:
При работе с большими наборами данных важны:
Uint8ArrayУзким местом обычно становится не nacl.hash, а операции
конкатенации массивов.
const data = ["a", "b", "c", "d"];
const { root } = buildMerkleTree(data);
console.log(toHex(root));
Корневой хеш становится единственной точкой доверия для всей структуры данных.
SHA-512, используемый в TweetNaCl.js, обеспечивает:
Эти свойства делают возможным использование Merkle-деревьев в системах, где требуется строгая проверка целостности данных без хранения самих данных.
Merkle-деревья на базе nacl.hash используются в:
Их ключевая ценность заключается в возможности сравнивать большие объёмы данных через один фиксированный хеш-идентификатор.