nacl.sign.keyPair.fromSeed: детерминированная генерация ключей

Функция nacl.sign.keyPair.fromSeed в TweetNaCl.js / nacl.js используется для построения пары ключей подписи Ed25519 на основе заранее заданного 32-байтного значения — seed. В отличие от генерации случайной пары ключей, этот механизм полностью детерминирован: одинаковый seed всегда приводит к одинаковой паре ключей.


Принцип работы детерминированной генерации

В основе лежит алгоритм Ed25519, который требует два ключевых компонента:

  • секретный ключ (secret key / private key)
  • публичный ключ (public key)

При использовании fromSeed секретный ключ не генерируется случайно, а выводится из seed. Далее из него вычисляется публичный ключ.

Seed выступает как единственный источник энтропии. Его длина фиксирована — 32 байта. Любое изменение хотя бы одного байта seed полностью меняет результирующую пару ключей.


Формат seed и требования к нему

Seed в TweetNaCl.js всегда представлен как:

  • Uint8Array(32)

Он должен содержать криптографически стойкие данные, если цель — безопасность. Однако ключевая особенность метода заключается в том, что seed может быть:

  • случайно сгенерирован
  • получен из пароля (через KDF)
  • восстановлен из хранилища
  • зафиксирован для воспроизводимости

Поведение функции keyPair.fromSeed

Функция возвращает объект:

  • publicKey: Uint8Array(32)
  • secretKey: Uint8Array(64)

Важно, что secretKey в TweetNaCl.js не является просто seed. Он включает:

  • первые 32 байта: расширенный секрет (derived secret scalar)
  • последние 32 байта: публичный ключ

Пример использования

import nacl from 'tweetnacl';

// seed строго 32 байта
const seed = new Uint8Array([
  1, 2, 3, 4, 5, 6, 7, 8,
  9, 10, 11, 12, 13, 14, 15, 16,
  17, 18, 19, 20, 21, 22, 23, 24,
  25, 26, 27, 28, 29, 30, 31, 32
]);

const keyPair = nacl.sign.keyPair.fromSeed(seed);

console.log(keyPair.publicKey);
console.log(keyPair.secretKey);

Детерминированность результата

Одно из ключевых свойств:

  • одинаковый seed → одинаковый keyPair

Это делает метод полезным в сценариях, где необходимо восстановление ключей без хранения приватного ключа.

Пример:

const seedA = new Uint8Array(32).fill(7);

const kp1 = nacl.sign.keyPair.fromSeed(seedA);
const kp2 = nacl.sign.keyPair.fromSeed(seedA);

kp1.publicKey === kp2.publicKey; // всегда true (по содержимому)

Сравнение происходит по байтам, а не по ссылке.


Отличие от keyPair()

В библиотеке также существует:

nacl.sign.keyPair()

Различие принципиальное:

Метод Источник энтропии Поведение
keyPair() криптографически случайный генератор каждый вызов новый ключ
keyPair.fromSeed() фиксированный seed воспроизводимый результат

Внутренние преобразования Ed25519

При вызове fromSeed выполняются этапы:

  1. Хэширование seed (SHA-512 внутри реализации Ed25519)
  2. Получение расширенного секретного значения
  3. Клэмпинг (bit clamping) для соответствия кривой Ed25519
  4. Вычисление публичного ключа через scalar multiplication base point

Клэмпинг секретного ключа

Секретная часть подвергается строгим битовым ограничениям:

  • очищаются младшие 3 бита первого байта
  • устанавливаются/сбрасываются определённые старшие биты последнего байта

Это необходимо для предотвращения слабых ключей и соблюдения свойств группы Ed25519.


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

Восстановление ключей

Seed может храниться отдельно:

  • в виде мнемоники (через дополнительный слой)
  • в зашифрованном хранилище
  • в базе данных

И затем восстанавливать ключевую пару при необходимости.


Идентичность пользователя

Один seed может стабильно представлять пользователя в системе:

  • одинаковый публичный ключ при каждом запуске
  • отсутствие необходимости хранить secretKey

Тестирование криптографических систем

Детерминированность позволяет:

  • воспроизводить тестовые сценарии
  • сравнивать подписи
  • фиксировать результаты

Код: генерация подписи с ключом из seed

import nacl from 'tweetnacl';

const seed = new Uint8Array(32).fill(42);
const keyPair = nacl.sign.keyPair.fromSeed(seed);

const message = new TextEncoder().encode("hello");

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

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

Особенности безопасности

Использование fromSeed накладывает важные ограничения:

  • безопасность полностью зависит от качества seed
  • слабый seed = полностью предсказуемые ключи
  • повторное использование seed в разных системах создаёт корреляции

Критически важно избегать:

  • коротких или человеко-читаемых seed без KDF
  • хранения seed в открытом виде
  • генерации seed через Math.random()

Генерация seed

Для создания seed обычно применяют:

const seed = nacl.randomBytes(32);

Это обеспечивает криптографически стойкую энтропию.


Представление ключей в других форматах

TweetNaCl.js работает с Uint8Array, но часто требуется преобразование:

  • Base64
  • Hex
  • Buffer (Node.js)

Пример преобразования в hex:

const toHex = (bytes) =>
  Array.from(bytes)
    .map(b => b.toString(16).padStart(2, '0'))
    .join('');

Поведение при одинаковых seed в разных средах

Результат стабилен:

  • браузер
  • Node.js
  • различные версии TweetNaCl.js

Алгоритм Ed25519 полностью детерминирован, что исключает различия между платформами.


Ограничения метода

Несмотря на удобство, подход имеет особенности:

  • невозможность «обновить» ключ без смены seed
  • зависимость всей криптосистемы от одного значения
  • необходимость защищённого хранения seed

Использование в архитектуре приложений

Типичные схемы:

  • seed хранится в зашифрованном localStorage
  • seed генерируется при первом запуске
  • ключи пересоздаются на лету при каждом входе

Связь с Ed25519 и nacl.sign

nacl.sign.keyPair.fromSeed является прямым интерфейсом к Ed25519:

  • подписи основаны на кривой Curve25519
  • совместимость с большинством криптографических систем
  • высокая скорость вычислений по сравнению с RSA

Поведение secretKey

secretKey в результате:

  • 64 байта
  • содержит расширенный секрет и публичный ключ
  • используется напрямую в nacl.sign.* функциях

Прямое извлечение seed из secretKey невозможно без знания структуры и алгоритма.


Итоговая модель генерации

seed (32 bytes)
   ↓
SHA-512 / derivation
   ↓
clamp + scalar derivation
   ↓
secret scalar
   ↓
public key generation
   ↓
keyPair

Типичные ошибки при использовании

  • передача seed длиной не 32 байта
  • попытка использовать строку вместо Uint8Array без кодирования
  • генерация seed через нестойкие источники
  • сравнение ключей как объектов вместо байтов

Работа с бинарными данными

Все операции в fromSeed требуют строгого соблюдения бинарного формата:

  • любые преобразования строк → только через TextEncoder / Buffer
  • никакой неявной конверсии типов
const encoder = new TextEncoder();
const seed = encoder.encode("example-seed-32-bytes-minimum!!");

Повторяемость и воспроизводимость

Главная особенность метода:

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