nacl.sign: подпись сообщения с вложением

Механизм nacl.sign реализует цифровую подпись на основе алгоритма Ed25519 и обеспечивает одновременно аутентификацию и целостность сообщения. В отличие от симметричных схем, здесь используется пара ключей: закрытый ключ для подписи и открытый для проверки.

В библиотеке TweetNaCl.js подпись реализована через пространство имён nacl.sign, которое включает как работу с «склеенными» (signed message), так и с раздельными (detached signature) подписями.

Генерация пары ключей выполняется через:

const nacl = require('tweetnacl');

const keyPair = nacl.sign.keyPair();

Результат содержит:

  • keyPair.publicKey — открытый ключ (32 байта)
  • keyPair.secretKey — закрытый ключ (64 байта, включает публичную часть)

Особенность реализации заключается в том, что секретный ключ уже содержит публичную часть, что упрощает внутренние операции библиотеки.

Подписанное сообщение (attached signature)

Функция nacl.sign() создаёт сообщение, в которое встроена подпись. Это называется подписанным сообщением (signed message).

const message = new TextEncoder().encode("секретные данные");

const signedMessage = nacl.sign(message, keyPair.secretKey);

На выходе получается бинарный массив:

[sig || message]

где:

  • sig — 64-байтовая подпись
  • message — исходное сообщение

Особенности формата signed message

  • Подпись встроена в начало массива
  • Размер результата = 64 + длина сообщения
  • Не требуется хранить подпись отдельно
  • Проверка автоматически извлекает сообщение

Проверка и извлечение сообщения

Для проверки используется nacl.sign.open():

const opened = nacl.sign.open(signedMessage, keyPair.publicKey);

Если подпись корректна:

  • возвращается исходное сообщение
  • иначе возвращается null

Внутренне выполняется:

  1. Проверка подписи Ed25519
  2. Извлечение исходных данных
  3. Возврат «очищенного» сообщения

Detached signature: раздельная подпись

В большинстве протоколов предпочтительнее использовать отделённую подпись.

Создание подписи

const message = new TextEncoder().encode("сообщение");

const signature = nacl.sign.detached(message, keyPair.secretKey);

Результат:

  • signature — 64 байта
  • не содержит данных сообщения

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

const isValid = nacl.sign.detached.verify(
  message,
  signature,
  keyPair.publicKey
);

Возвращает:

  • true — подпись корректна
  • false — подпись недействительна

Сравнение signed message и detached signature

Подписанное сообщение (nacl.sign)

Преимущества:

  • автоматическое объединение данных и подписи
  • удобно для хранения и передачи как единого блока

Недостатки:

  • увеличенный размер (дублирование структуры)
  • менее гибко при протокольной интеграции

Detached signature (nacl.sign.detached)

Преимущества:

  • разделение данных и подписи
  • используется в сетевых протоколах и API
  • легче интегрируется в JSON и бинарные форматы

Недостатки:

  • требуется хранить два объекта (данные + подпись)

Практический пример протокола передачи

Типичный сценарий:

const payload = {
  user: "alice",
  amount: 100
};

const message = new TextEncoder().encode(JSON.stringify(payload));

const signature = nacl.sign.detached(message, keyPair.secretKey);

Передача:

{
  "payload": "{...}",
  "signature": "base64..."
}

Проверка на стороне получателя:

const valid = nacl.sign.detached.verify(
  message,
  signature,
  publicKey
);

Кодирование и бинарные данные

TweetNaCl.js работает исключительно с Uint8Array. Поэтому любые строки требуют преобразования:

const encode = (str) => new TextEncoder().encode(str);
const decode = (bytes) => new TextDecoder().decode(bytes);

При работе с сетью часто используется Base64:

const toBase64 = (bytes) =>
  btoa(String.fromCharCode(...bytes));

const fromBase64 = (str) =>
  new Uint8Array(atob(str).split('').map(c => c.charCodeAt(0)));

Внутренний принцип Ed25519 в nacl.sign

Алгоритм опирается на:

  • эллиптическую кривую Curve25519
  • хэширование SHA-512
  • детерминированное формирование nonce

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

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

Ошибки при использовании nacl.sign

Повреждение данных

Любое изменение байтов приводит к:

null

при проверке.

Несовпадение ключей

Открытый ключ должен соответствовать секретному, иначе подпись всегда будет невалидной.

Потеря кодирования

Частая ошибка — проверка строки вместо Uint8Array, что приводит к неверной валидации.

Рекомендованные схемы использования

  • API подписи запросов → detached
  • локальное хранение сообщений → signed message
  • токены аутентификации → detached
  • бинарные протоколы → detached
  • простые прототипы → signed message

Взаимодействие с другими частями TweetNaCl.js

nacl.sign не является изолированным механизмом:

  • использует низкоуровневые примитивы nacl.lowlevel
  • совместим с nacl.box при комбинированных схемах
  • может применяться вместе с nacl.hash для предварительного хеширования данных

Такой подход позволяет строить гибридные криптосистемы, где подпись защищает не только сообщение, но и производные структуры данных.