Тестирование на известных векторах (test vectors)

Проверка криптографических реализаций на известных тестовых векторах является обязательным этапом верификации корректности. Для библиотек уровня TweetNaCl.js / nacl.js это особенно критично, поскольку любая ошибка в реализации алгоритмов приводит к полной компрометации безопасности: несовпадение одного байта в результате шифрования или подписи означает некорректную реализацию примитивов.

Тестовые векторы представляют собой заранее вычисленные входные данные и ожидаемые выходы для конкретных криптографических операций. Они публикуются в спецификациях алгоритмов (например, Curve25519, XSalsa20-Poly1305, Ed25519) и используются как эталон.


Источники тестовых векторов

Основные источники корректных наборов:

  • RFC 8439 — ChaCha20-Poly1305
  • RFC 8032 — Ed25519
  • Документация NaCl / libsodium
  • Оригинальные тестовые наборы Daniel J. Bernstein (djb)
  • Реализации libsodium test suite

TweetNaCl.js стремится быть совместимым с оригинальной NaCl, поэтому наиболее важны именно эталонные векторы DJB.


Структура тестового вектора

Типичный тестовый вектор включает:

  • входные данные (message)
  • ключ или nonce
  • дополнительные параметры (publicKey, secretKey)
  • ожидаемый результат (ciphertext, signature, shared key)

Пример абстрактной структуры:

message      = "..."
key          = 32 bytes
nonce        = 24 bytes
expected     = 64 bytes ciphertext

Тестирование symmetric encryption (nacl.secretbox)

TweetNaCl.js использует nacl.secretbox на основе XSalsa20 + Poly1305.

Пример тестового вектора

const nacl = require('tweetnacl');
nacl.util = require('tweetnacl-util');

const key = new Uint8Array(32).fill(1);
const nonce = new Uint8Array(24).fill(2);
const message = nacl.util.decodeUTF8("test message");

const encrypted = nacl.secretbox(message, nonce, key);

Ожидаемое поведение:

  • одинаковые входные данные всегда дают одинаковый результат
  • изменение хотя бы одного байта key/nonce полностью меняет ciphertext
  • длина ciphertext = message.length + 16 (Poly1305 MAC)

Проверка обратимости (open / box.open)

Для корректного тестирования используется обратное преобразование:

const decrypted = nacl.secretbox.open(encrypted, nonce, key);
const result = nacl.util.encodeUTF8(decrypted);

Ключевые свойства:

  • box.open(box(m)) == m
  • при неверном ключе результат = null
  • при изменённом nonce результат = null

Curve25519: nacl.box

Ключевая часть тестирования — обмен ключами через Curve25519.

Эталонный сценарий

const alice = nacl.box.keyPair();
const bob = nacl.box.keyPair();

const nonce = new Uint8Array(24).fill(5);
const msg = nacl.util.decodeUTF8("hello");

const boxed = nacl.box(msg, nonce, bob.publicKey, alice.secretKey);
const opened = nacl.box.open(boxed, nonce, alice.publicKey, bob.secretKey);

Проверки:

  • корректная взаимная расшифровка
  • идентичность результата исходному сообщению
  • несовместимость ключей приводит к null

Ed25519: nacl.sign

Подписи являются детерминированными, что критично для тестовых векторов.

Проверка подписи

const keyPair = nacl.sign.keyPair();
const message = nacl.util.decodeUTF8("message");

const signed = nacl.sign(message, keyPair.secretKey);
const opened = nacl.sign.open(signed, keyPair.publicKey);

Ожидания:

  • sign.open(sign(m)) == m
  • изменение одного байта подписи приводит к null
  • подпись имеет фиксированную длину (64 байта + message)

Сравнение с эталонными векторами RFC 8032

Для Ed25519 используется набор известных тестов:

  • фиксированный seed
  • фиксированный publicKey
  • фиксированный message
  • фиксированная signature

Проверка выглядит как жесткое сравнение:

if (!equalBytes(signature, expectedSignature)) {
    throw new Error("Ed25519 mismatch");
}

Byte-level сравнение результатов

Криптографические тесты не допускают частичных совпадений. Проверка всегда бинарная:

function equalBytes(a, b) {
  if (a.length !== b.length) return false;
  for (let i = 0; i < a.length; i++) {
    if (a[i] !== b[i]) return false;
  }
  return true;
}

Любое расхождение считается критической ошибкой реализации.


Тестирование nonce sensitivity

Nonce в NaCl не должен повторяться. Тестовые векторы проверяют:

  • изменение nonce → полностью другой ciphertext
  • повтор nonce + key → повтор результата (детерминизм)
const c1 = nacl.secretbox(msg, nonce1, key);
const c2 = nacl.secretbox(msg, nonce2, key);

c1 !== c2

Тестирование устойчивости к повреждениям

Важный класс тестов — проверка реакции на битовые ошибки.

Инверсия одного бита ciphertext

const corrupted = encrypted.slice();
corrupted[0] ^= 1;

const opened = nacl.secretbox.open(corrupted, nonce, key);

Ожидание:

  • результат строго null
  • никакой частичной расшифровки

Тестирование согласованности между платформами

TweetNaCl.js должен совпадать с:

  • TweetNaCl (C)
  • libsodium
  • NaCl reference implementation

Сравнение производится через фиксированные векторы:

assert.equal(
  nacl.util.encodeBase64(result),
  expectedBase64
);

Типичные ошибки, выявляемые тестовыми векторами

1. Ошибки порядка байтов

Little-endian / big-endian несоответствия приводят к расхождению ключей Curve25519.

2. Неправильная длина nonce

XSalsa20 требует строго 24 байта.

3. Ошибки Poly1305

Любое отклонение ломает MAC-верификацию.

4. Неверная обработка Uint8Array

JavaScript-реализация часто ломается на:

  • копировании буферов
  • изменяемости массива
  • shared references

Организация тестового набора

В реальных проектах тестовые векторы оформляются как JSON:

{
  "secretbox": [
    {
      "key": "...",
      "nonce": "...",
      "message": "...",
      "ciphertext": "..."
    }
  ]
}

И прогоняются автоматически:

vectors.secretbox.forEach(v => {
  const out = nacl.secretbox(v.msg, v.nonce, v.key);
  assert(equalBytes(out, v.ciphertext));
});

Автоматизация регрессионного тестирования

При изменении реализации обязательно прогоняются:

  • full vector suite
  • cross-version compatibility checks
  • fuzzing + vector validation

Типичная схема CI:

  1. загрузка тестовых векторов
  2. выполнение всех primitive tests
  3. сравнение с эталоном
  4. остановка при первом несовпадении

Значение тестовых векторов для безопасности

Криптография не допускает статистической корректности. Только строгая детерминированная эквивалентность с эталоном считается допустимой.

Тестовые векторы выполняют роль формальной спецификации поведения библиотеки, фиксируя:

  • корректность математических операций
  • отсутствие побочных эффектов реализации JavaScript
  • совместимость с экосистемой NaCl

Любое отклонение от векторов означает не просто баг, а потенциальную уязвимость протокола, построенного поверх библиотеки.