В криптографических системах на основе TweetNaCl.js и nacl.js каждая операция подписи обычно рассматривается как независимое действие: сообщение → подпись → проверка. При увеличении количества сообщений такой подход становится узким местом из-за повторяющихся затрат на сериализацию данных, вызовы криптографических функций и работу с буферами.
Батчинг операций подписи и верификации решает задачу групповой обработки, минимизируя накладные расходы JavaScript-уровня и позволяя более эффективно использовать криптографическое ядро библиотеки.
В TweetNaCl.js используется Ed25519 для цифровых подписей. Основной API выглядит следующим образом:
const nacl = require('tweetnacl');
nacl.util = require('tweetnacl-util');
const keyPair = nacl.sign.keyPair();
const message = nacl.util.decodeUTF8('Hello world');
const signature = nacl.sign.detached(message, keyPair.secretKey);
const isValid = nacl.sign.detached.verify(
message,
signature,
keyPair.publicKey
);
Каждая операция detached создаёт независимую подпись,
которая затем может быть проверена отдельно. При большом количестве
сообщений (например, журналы событий, блоки транзакций, телеметрия)
такой подход приводит к избыточным вызовам verify.
Если имеется массив сообщений:
const messages = [
'event:login:user1',
'event:transfer:500',
'event:logout:user1',
'event:login:user2'
];
и каждое сообщение подписано отдельно:
const signatures = messages.map(m =>
nacl.sign.detached(nacl.util.decodeUTF8(m), keyPair.secretKey)
);
то на этапе проверки выполняется:
messages.forEach((m, i) => {
const valid = nacl.sign.detached.verify(
nacl.util.decodeUTF8(m),
signatures[i],
keyPair.publicKey
);
});
Основная проблема — повторяющееся декодирование, обращение к криптографической функции и проверка каждой подписи отдельно.
Батчинг в контексте TweetNaCl.js не реализован как встроенная функция, поэтому он строится поверх существующего API.
Ключевая идея — объединить операции на уровне JavaScript-логики:
decodeUTF8Эффективный батчинг начинается с нормализации входных данных:
function prepareBatch(messages) {
const encoded = new Array(messages.length);
for (let i = 0; i < messages.length; i++) {
encoded[i] = nacl.util.decodeUTF8(messages[i]);
}
return encoded;
}
Эта стадия убирает повторяющееся преобразование UTF-8 при каждой верификации.
Хотя криптографически подписи остаются индивидуальными, их генерация может быть оптимизирована через единый цикл без промежуточных структур:
function signBatch(encodedMessages, secretKey) {
const signatures = new Array(encodedMessages.length);
for (let i = 0; i < encodedMessages.length; i++) {
signatures[i] = nacl.sign.detached(encodedMessages[i], secretKey);
}
return signatures;
}
Оптимизация достигается за счёт:
Основной выигрыш даёт оптимизация проверки:
function verifyBatch(encodedMessages, signatures, publicKey) {
const results = new Array(encodedMessages.length);
for (let i = 0; i < encodedMessages.length; i++) {
results[i] = nacl.sign.detached.verify(
encodedMessages[i],
signatures[i],
publicKey
);
}
return results;
}
В результате вместо множества разрозненных операций формируется единый линейный процесс обработки.
В V8 и других движках основная стоимость таких операций складывается из:
Использование классического for-цикла вместо
функциональных методов снижает давление на GC и уменьшает количество
скрытых классовых переходов.
Хотя TweetNaCl.js не поддерживает SIMD или нативную пакетную криптографию, возможна имитация батча через группировку операций:
function batchProcess(items, secretKey, publicKey) {
const encoded = prepareBatch(items);
const signatures = signBatch(encoded, secretKey);
const results = verifyBatch(encoded, signatures, publicKey);
return {
signatures,
results
};
}
Такой подход объединяет весь pipeline в одну последовательность, уменьшая промежуточные состояния.
Серьёзная оптимизация достигается при отказе от постоянного создания новых массивов:
const bufferCache = {
encoded: [],
signatures: [],
results: []
};
function ensureCapacity(arr, size) {
if (arr.length < size) {
arr.length = size;
}
}
Далее буферы переиспользуются:
function processBatch(messages, keyPair) {
ensureCapacity(bufferCache.encoded, messages.length);
ensureCapacity(bufferCache.signatures, messages.length);
ensureCapacity(bufferCache.results, messages.length);
for (let i = 0; i < messages.length; i++) {
bufferCache.encoded[i] = nacl.util.decodeUTF8(messages[i]);
}
for (let i = 0; i < messages.length; i++) {
bufferCache.signatures[i] =
nacl.sign.detached(bufferCache.encoded[i], keyPair.secretKey);
}
for (let i = 0; i < messages.length; i++) {
bufferCache.results[i] =
nacl.sign.detached.verify(
bufferCache.encoded[i],
bufferCache.signatures[i],
keyPair.publicKey
);
}
return bufferCache.results;
}
При работе с потоками событий батчинг приобретает дополнительный смысл: сообщения поступают непрерывно и группируются по окну времени или размеру.
class BatchProcessor {
constructor(size, keyPair) {
this.size = size;
this.keyPair = keyPair;
this.batch = [];
}
add(message) {
this.batch.push(message);
if (this.batch.length >= this.size) {
return this.flush();
}
return null;
}
flush() {
const encoded = prepareBatch(this.batch);
const signatures = signBatch(encoded, this.keyPair.secretKey);
const results = verifyBatch(encoded, signatures, this.keyPair.publicKey);
this.batch.length = 0;
return { encoded, signatures, results };
}
}
Такой подход используется в системах логирования и телеметрии, где важно удерживать стабильную нагрузку.
При групповой обработке важно учитывать, что нарушение одного элемента не должно влиять на остальные. Поэтому результаты верификации возвращаются массивом:
[
true,
false,
true,
true
]
Каждый элемент батча остаётся независимым с точки зрения криптографической проверки, несмотря на общую обработку.
Батчинг в TweetNaCl.js имеет архитектурные ограничения:
Поэтому основная цель батчинга — снижение overhead, а не ускорение самой криптографии.
Типичная схема использования батчинга выглядит как конвейер:
const batch = processBatch(messages, keyPair);
Такой подход особенно эффективен в системах, где: