nacl.box.keyPair.fromSecretKey: восстановление пары из секрета

Метод nacl.box.keyPair.fromSecretKey предназначен для восстановления полной пары ключей (публичного и секретного) на основе уже существующего секретного ключа. Это критически важный механизм в сценариях, где приватный ключ хранится или передаётся отдельно, а публичный должен быть детерминированно получен без повторной генерации.

В основе лежит криптографическая схема Curve25519, где публичный ключ является функцией от секретного, и может быть однозначно вычислен.


Сигнатура метода

nacl.box.keyPair.fromSecretKey(secretKey)

Параметры:

  • secretKey (Uint8Array, длина 32 байта) — приватный ключ

Возвращает:

{
  publicKey: Uint8Array(32),
  secretKey: Uint8Array(32)
}

Криптографическая основа

В библиотеке используется алгоритм X25519 (вариант Curve25519 для обмена ключами). Публичный ключ вычисляется как:

publicKey = scalarMult.base(secretKey)

Это означает:

  • секретный ключ интерпретируется как скаляр
  • выполняется умножение на базовую точку эллиптической кривой
  • результат — публичный ключ

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


Практическое применение

1. Восстановление ключей после хранения

Секретный ключ может быть сохранён в:

  • базе данных
  • файле
  • защищённом хранилище (например, localStorage с шифрованием)

При загрузке приложения требуется восстановить публичный ключ:

const savedSecretKey = ... // Uint8Array(32)

const keyPair = nacl.box.keyPair.fromSecretKey(savedSecretKey)

console.log(keyPair.publicKey)

2. Детерминированные ключи

Если секретный ключ получен из:

  • seed-фразы
  • хэша пароля
  • генератора случайных чисел с фиксированным seed

то fromSecretKey позволяет всегда получать одну и ту же пару ключей:

const seed = nacl.hash(new TextEncoder().encode("password"))
const secretKey = seed.slice(0, 32)

const keyPair = nacl.box.keyPair.fromSecretKey(secretKey)

3. Совместимость с другими системами

Метод полезен при интеграции с:

  • backend-сервисами
  • другими реализациями NaCl (например, libsodium)
  • криптографическими протоколами

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


Отличие от nacl.box.keyPair()

Метод Назначение
nacl.box.keyPair() Генерация новой случайной пары
fromSecretKey() Восстановление из существующего секрета

keyPair() использует криптографически стойкий генератор случайных чисел, тогда как fromSecretKey() полностью детерминирован.


Проверка корректности ключа

Метод не валидирует секретный ключ в строгом смысле:

  • ожидается длина ровно 32 байта
  • любые 32 байта будут интерпретированы как допустимый скаляр

Пример ошибки:

const badKey = new Uint8Array(16)
nacl.box.keyPair.fromSecretKey(badKey) // ошибка

Формат и типы данных

Все ключи представлены как:

Uint8Array

Для сериализации используются:

  • Base64
  • Hex

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

function toHex(uint8arr) {
  return Array.from(uint8arr)
    .map(b => b.toString(16).padStart(2, '0'))
    .join('')
}

Внутренние особенности реализации

Внутри TweetNaCl:

  1. Секретный ключ копируется

  2. Выполняется clamp (маскирование битов):

    • обнуляются младшие 3 бита
    • устанавливаются старшие биты
  3. Вычисляется публичный ключ через scalar multiplication

Clamp необходим для:

  • предотвращения слабых ключей
  • соблюдения требований Curve25519

Безопасность

Хранение секретного ключа

Секретный ключ — критически чувствительные данные:

  • нельзя хранить в открытом виде

  • желательно использовать:

    • Web Crypto API (если возможно)
    • аппаратные хранилища
    • зашифрованные контейнеры

Повторное использование ключей

fromSecretKey поощряет повторное использование одного и того же ключа, что допустимо в:

  • системах идентификации
  • обмене ключами

но нежелательно в:

  • одноразовых сессиях
  • протоколах с высокой анонимностью

Производительность

Операция:

  • очень быстрая (одна scalar multiplication)
  • не требует случайности
  • подходит для выполнения на клиенте

Частые ошибки

1. Неверная длина ключа

if (secretKey.length !== 32) {
  throw new Error("Invalid secret key length")
}

2. Использование строки вместо Uint8Array

// Неправильно
const key = "mysecret"

// Правильно
const key = new TextEncoder().encode("mysecret")

3. Повторное вычисление без необходимости

Если пара уже известна, нет смысла вызывать метод повторно.


Связь с другими методами

  • nacl.box.before(publicKey, secretKey)

    • использует ту же криптографическую основу
  • nacl.scalarMult.base(secretKey)

    • низкоуровневая версия вычисления публичного ключа
  • nacl.box.keyPair()

    • генерация новых ключей

Минимальный пример

import nacl from "tweetnacl"

// исходный секретный ключ
const secretKey = nacl.randomBytes(32)

// восстановление пары
const keyPair = nacl.box.keyPair.fromSecretKey(secretKey)

console.log("Public:", keyPair.publicKey)
console.log("Secret:", keyPair.secretKey)

Когда использовать

  • восстановление пользователя по ключу
  • криптографическая идентификация
  • синхронизация ключей между устройствами
  • deterministic wallet / identity systems

Когда не использовать

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

Ключевая идея

nacl.box.keyPair.fromSecretKey — это чистая функция преобразования: секретный ключ → пара ключей, где публичный всегда однозначно определяется.