Конвертация между hex, Base64, Base58 и Uint8Array

Uint8Array является основным типом данных, с которым работает TweetNaCl.js. Все криптографические операции — подпись, шифрование, генерация ключей — выполняются над массивами байтов фиксированного формата. Любые внешние представления ключей, сообщений или nonce неизбежно требуют преобразования в Uint8Array и обратно.


В JavaScript бинарные данные чаще всего представлены через Uint8Array, где каждый элемент — это число от 0 до 255.

Пример создания:

const bytes = new Uint8Array([104, 101, 108, 108, 111]);

Это строка "hello" в байтовом виде.

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

function stringToUint8Array(str) {
  return new TextEncoder().encode(str);
}

function uint8ArrayToString(bytes) {
  return new TextDecoder().decode(bytes);
}

Hex-кодирование и декодирование

Hex (шестнадцатеричное представление) часто используется для отображения ключей и хэшей. Каждый байт кодируется двумя символами.

Преобразование Uint8Array → hex

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

Пример:

uint8ToHex(new Uint8Array([255, 16, 0]));
// "ff1000"

Преобразование hex → Uint8Array

function hexToUint8(hex) {
  if (hex.length % 2 !== 0) throw new Error("Invalid hex length");

  const arr = new Uint8Array(hex.length / 2);

  for (let i = 0; i < arr.length; i++) {
    arr[i] = parseInt(hex.substr(i * 2, 2), 16);
  }

  return arr;
}

Особенности:

  • hex должен содержать только символы [0-9a-fA-F]
  • пробелы и префиксы (0x) должны удаляться заранее

Base64: стандарт для передачи бинарных данных

Base64 широко используется в Web API и при обмене криптографическими данными.

Uint8Array → Base64

function uint8ToBase64(uint8) {
  let binary = '';
  for (let i = 0; i < uint8.length; i++) {
    binary += String.fromCharCode(uint8[i]);
  }
  return btoa(binary);
}

Base64 → Uint8Array

function base64ToUint8(base64) {
  const binary = atob(base64);
  const arr = new Uint8Array(binary.length);

  for (let i = 0; i < binary.length; i++) {
    arr[i] = binary.charCodeAt(i);
  }

  return arr;
}

URL-safe Base64

В криптографических протоколах часто применяется модифицированный Base64:

  • + заменяется на -
  • / заменяется на _
  • = убирается
function base64UrlEncode(uint8) {
  return uint8ToBase64(uint8)
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}

function base64UrlDecode(str) {
  str = str.replace(/-/g, '+').replace(/_/g, '/');
  while (str.length % 4) str += '=';
  return base64ToUint8(str);
}

Base58: формат Bitcoin и криптографических ключей

Base58 используется там, где важна человекочитаемость и отсутствие неоднозначных символов.

Алфавит:

123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz

Исключены:

  • 0 (ноль)
  • O (заглавная o)
  • I (i)
  • l (L)

Особенности Base58

Base58 не является побайтовым кодированием, как hex или base64. Это представление большого числа в системе счисления 58.


Реализация Uint8Array → Base58

const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

function uint8ToBase58(bytes) {
  let num = BigInt('0x' + Array.from(bytes)
    .map(b => b.toString(16).padStart(2, '0'))
    .join(''));

  let result = '';

  while (num > 0) {
    const mod = num % 58n;
    result = ALPHABET[Number(mod)] + result;
    num = num / 58n;
  }

  // обработка ведущих нулей
  for (let i = 0; i < bytes.length && bytes[i] === 0; i++) {
    result = '1' + result;
  }

  return result;
}

Base58 → Uint8Array

function base58ToUint8(str) {
  let num = 0n;

  for (let i = 0; i < str.length; i++) {
    num = num * 58n + BigInt(ALPHABET.indexOf(str[i]));
  }

  let hex = num.toString(16);
  if (hex.length % 2) hex = '0' + hex;

  let bytes = hexToUint8(hex);

  // восстановление ведущих нулей
  let leadingZeros = 0;
  for (let i = 0; i < str.length && str[i] === '1'; i++) {
    leadingZeros++;
  }

  return new Uint8Array([...new Uint8Array(leadingZeros), ...bytes]);
}

Почему TweetNaCl.js требует Uint8Array

Библиотека ориентирована на строгость и минимализм:

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

Например:

nacl.sign(messageUint8, secretKeyUint8)

никаких строковых входов не допускается.


Типичные ошибки при конвертации

1. Потеря ведущих нулей

Hex и Base58 часто теряют ведущие нули при преобразовании числа.

Это критично для ключей:

00ab... != ab...

2. Использование UTF-8 вместо байтов

new TextEncoder().encode(hexString)

Это ошибка: hex — это представление, а не данные.


3. Неверное декодирование Base64

В браузере и Node.js используются разные API (atob, Buffer).


Практическая связка с TweetNaCl.js

Пример ключа:

const keyPair = nacl.sign.keyPair();

const publicKeyHex = uint8ToHex(keyPair.publicKey);
const secretKeyBase64 = uint8ToBase64(keyPair.secretKey);

Дальнейшее восстановление:

const publicKey = hexToUint8(publicKeyHex);
const secretKey = base64ToUint8(secretKeyBase64);

Унификация представлений

В криптографических системах обычно выделяют три слоя:

  • Uint8Array — внутренний слой (всегда используется в TweetNaCl.js)
  • Base64/Base58 — транспортный слой (API, сети, ключи)
  • Hex — визуальный и отладочный слой

Сравнение форматов

Формат Размер Читаемость Использование
Hex высокая отладка
Base64 ~1.33× средняя API, Web
Base58 ~1.37× высокая криптокошельки
Uint8Array отсутствует внутренняя работа

Обратимость преобразований

Все корректные реализации должны обеспечивать строгую обратимость:

Uint8Array → Base64 → Uint8Array
Uint8Array → Hex → Uint8Array
Uint8Array → Base58 → Uint8Array

Любая потеря информации указывает на ошибку реализации, а не на ограничения формата.


Интеграция в криптографический поток

Типичный поток работы с TweetNaCl.js:

строка → Uint8Array → криптография → Uint8Array → Base64/Hex/Base58

Каждый внешний формат существует только на границах системы: ввод, хранение, передача.

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