Модульные тесты криптографических операций требуют особого подхода: обычные стратегии проверки бизнес-логики здесь недостаточны, поскольку корректность определяется не только поведением кода, но и строгим соответствием математическим свойствам алгоритмов и заранее известным тестовым векторам. В случае TweetNaCl.js / nacl.js это особенно важно, так как библиотека реализует низкоуровневые примитивы (Curve25519, XSalsa20-Poly1305, Ed25519), где любая регрессия может привести к несовместимости или уязвимости.
Классические юнит-тесты часто проверяют вход → ожидаемый выход. В криптографии этого недостаточно без дополнительных условий:
В TweetNaCl.js ключевой принцип — работа с Uint8Array.
Это накладывает обязательство тестировать не строки и абстрактные типы,
а именно бинарные представления данных.
Для модульных тестов обычно используют Jest или Mocha. Важно обеспечить воспроизводимость:
При тестировании криптографии генератор случайных чисел часто заменяется на стабильный источник:
import nacl from 'tweetnacl';
// фиктивный RNG для тестов
nacl.setPRNG((x, n) => {
for (let i = 0; i < n; i++) {
x[i] = 1; // детерминированное заполнение
}
});
Такой подход позволяет избежать флаки-тестов, но используется только для unit-тестирования, не для production.
Операции nacl.secretbox и
nacl.secretbox.open проверяются через:
import nacl from 'tweetnacl';
test('secretbox encrypt/decrypt roundtrip', () => {
const key = new Uint8Array(32).fill(2);
const nonce = new Uint8Array(24).fill(3);
const message = new TextEncoder().encode('hello crypto');
const encrypted = nacl.secretbox(message, nonce, key);
const decrypted = nacl.secretbox.open(encrypted, nonce, key);
expect(decrypted).not.toBeNull();
expect(new TextDecoder().decode(decrypted)).toBe('hello crypto');
});
test('secretbox detects tampering', () => {
const key = new Uint8Array(32).fill(2);
const nonce = new Uint8Array(24).fill(3);
const message = new Uint8Array([1, 2, 3, 4]);
const encrypted = nacl.secretbox(message, nonce, key);
encrypted[0] ^= 1;
const result = nacl.secretbox.open(encrypted, nonce, key);
expect(result).toBeNull();
});
Ключевой момент: криптографическая функция должна возвращать
null, а не выбрасывать исключение.
nacl.box требует пары ключей (public/private). Здесь
тестирование усложняется:
test('box key exchange encryption', () => {
const alice = nacl.box.keyPair();
const bob = nacl.box.keyPair();
const nonce = new Uint8Array(24).fill(7);
const message = new TextEncoder().encode('secure message');
const encrypted = nacl.box(message, nonce, bob.publicKey, alice.secretKey);
const decrypted = nacl.box.open(encrypted, nonce, alice.publicKey, bob.secretKey);
expect(decrypted).not.toBeNull();
expect(new TextDecoder().decode(decrypted)).toBe('secure message');
});
Здесь важно тестировать обе стороны: отправитель и получатель должны быть симметрично валидны.
test('box fails with wrong keypair', () => {
const alice = nacl.box.keyPair();
const bob = nacl.box.keyPair();
const eve = nacl.box.keyPair();
const nonce = new Uint8Array(24).fill(1);
const message = new Uint8Array([10, 20, 30]);
const encrypted = nacl.box(message, nonce, bob.publicKey, alice.secretKey);
const result = nacl.box.open(encrypted, nonce, alice.publicKey, eve.secretKey);
expect(result).toBeNull();
});
Подписи в TweetNaCl.js основаны на Ed25519 и требуют строгой проверки:
test('sign and verify detached', () => {
const keyPair = nacl.sign.keyPair();
const message = new TextEncoder().encode('signed data');
const signature = nacl.sign.detached(message, keyPair.secretKey);
const valid = nacl.sign.detached.verify(
message,
signature,
keyPair.publicKey
);
expect(valid).toBe(true);
});
test('signature fails on modified message', () => {
const keyPair = nacl.sign.keyPair();
const message = new Uint8Array([1, 2, 3]);
const signature = nacl.sign.detached(message, keyPair.secretKey);
message[0] = 9;
const valid = nacl.sign.detached.verify(
message,
signature,
keyPair.publicKey
);
expect(valid).toBe(false);
});
Самый надёжный способ проверки криптографической библиотеки — использование официальных test vectors (например, из NaCl reference implementation или libsodium).
Типичный подход:
test('secretbox matches known vector', () => {
const key = hexToUint8Array('1c9240a5eb55d38af333888604f6b5f0...');
const nonce = hexToUint8Array('0000000000000000000000000000000000000000');
const message = hexToUint8Array('48656c6c6f');
const encrypted = nacl.secretbox(message, nonce, key);
expect(encrypted).toEqual(hexToUint8Array('expectedcipherhex...'));
});
Любое расхождение означает либо ошибку реализации, либо неправильную интерпретацию байтов.
Nonce в NaCl не обязан быть секретным, но обязан быть уникальным. Это создаёт важную категорию тестов:
test('nonce must be 24 bytes', () => {
const key = new Uint8Array(32).fill(1);
const badNonce = new Uint8Array(12);
const message = new Uint8Array([1, 2, 3]);
expect(() => {
nacl.secretbox(message, badNonce, key);
}).toThrow();
});
Хотя TweetNaCl.js часто не бросает исключения, а ведёт себя undefined-safe, тесты должны фиксировать фактическое поведение библиотеки.
Помимо фиксированных кейсов полезно проверять свойства:
decrypt(encrypt(m)) = mПример:
test('roundtrip property', () => {
const key = nacl.randomBytes(32);
const nonce = nacl.randomBytes(24);
const message = nacl.randomBytes(64);
const encrypted = nacl.secretbox(message, nonce, key);
const decrypted = nacl.secretbox.open(encrypted, nonce, key);
expect(decrypted).toEqual(message);
});
Криптографические функции чувствительны к размеру:
test('empty message handling', () => {
const key = new Uint8Array(32).fill(1);
const nonce = new Uint8Array(24).fill(1);
const message = new Uint8Array([]);
const encrypted = nacl.secretbox(message, nonce, key);
const decrypted = nacl.secretbox.open(encrypted, nonce, key);
expect(decrypted).toEqual(new Uint8Array([]));
});
Отдельный класс тестов — совместимость с другими реализациями:
Такие тесты обычно используют заранее подготовленные данные и проверяют:
Хорошо построенный набор тестов в TweetNaCl.js обычно ловит:
Важное правило — криптографические тесты не должны зависеть от:
Поэтому почти всегда данные переводятся в Uint8Array
вручную, без строковых промежуточных представлений, кроме строго
контролируемых участков теста.
Модульные тесты в TweetNaCl.js становятся не просто проверкой корректности функций, а формальной системой верификации криптографических свойств, где каждая операция рассматривается как строго определённое преобразование над битовыми структурами с фиксированными инвариантами и заранее известными результатами.