Files
mobile-android/CRYPTO_NEW_AUTH_UPDATE.md

9.8 KiB
Raw Blame History

Обновление авторизации для совместимости с crypto_new

Дата: 16 января 2026

Статус: Реализовано

Обзор изменений

Обновлена логика авторизации в Android приложении для полной совместимости с новым методом шифрования из crypto_new (TypeScript/JavaScript). Основные изменения касаются генерации ключевых пар и формата публичных ключей.

Основные изменения

1. Метод генерации приватного ключа

Старый метод (BIP39):

fun seedPhraseToPrivateKey(seedPhrase: List<String>): String {
    val mnemonicCode = MnemonicCode.INSTANCE
    val seed = MnemonicCode.toSeed(seedPhrase, "")  // 64 bytes
    return seed.joinToString("") { "%02x".format(it) }  // 128 hex chars
}

Новый метод (crypto_new):

fun seedPhraseToPrivateKey(seedPhrase: List<String>): String {
    val seedString = seedPhrase.joinToString(" ")
    val digest = MessageDigest.getInstance("SHA-256")
    val hash = digest.digest(seedString.toByteArray(Charsets.UTF_8))  // 32 bytes
    return hash.joinToString("") { "%02x".format(it) }  // 64 hex chars
}

JavaScript эквивалент (crypto_new/crypto.ts):

const privateKey = sha256.create().update(seed).digest().toHex().toString();

2. Формат публичного ключа

Старый метод (несжатый):

val publicKeyPoint = ecSpec.g.multiply(privateKeyBigInt)
val publicKeyHex = publicKeyPoint.getEncoded(false)  // 65 bytes (04 + x + y)

Новый метод (сжатый):

val publicKeyPoint = ecSpec.g.multiply(privateKeyBigInt)
val publicKeyHex = publicKeyPoint.getEncoded(true)  // 33 bytes (02/03 + x)

JavaScript эквивалент (crypto_new/crypto.ts):

const publicKey = secp256k1.getPublicKey(Buffer.from(privateKey, "hex"), true);

3. Метод генерации privateKeyHash

Этот метод НЕ ИЗМЕНИЛСЯ и уже был совместим с crypto_new:

fun generatePrivateKeyHash(privateKey: String): String {
    val data = (privateKey + "rosetta").toByteArray()
    val digest = MessageDigest.getInstance("SHA-256")
    val hash = digest.digest(data)
    return hash.joinToString("") { "%02x".format(it) }
}

JavaScript эквивалент (crypto_new/crypto.ts):

export const generateHashFromPrivateKey = async (privateKey: string) => {
  return sha256
    .create()
    .update(privateKey + "rosetta")
    .digest()
    .toHex()
    .toString();
};

Изменённые файлы

CryptoManager.kt

Файл: /app/src/main/java/com/rosetta/messenger/crypto/CryptoManager.kt

Изменённые функции:

  1. seedPhraseToPrivateKey() - теперь использует SHA256 вместо BIP39
  2. generateKeyPairFromSeed() - генерирует сжатый публичный ключ (33 байта)

Влияние на авторизацию

Процесс авторизации

  1. Создание аккаунта (AuthState.kt -> createAccount()):

    val keyPair = CryptoManager.generateKeyPairFromSeed(seedPhrase)
    // keyPair.privateKey - 32 bytes (64 hex chars) через SHA256
    // keyPair.publicKey - 33 bytes (66 hex chars) сжатый формат
    
    val privateKeyHash = CryptoManager.generatePrivateKeyHash(keyPair.privateKey)
    // SHA256(privateKey + "rosetta")
    
  2. Подключение к серверу (Protocol.kt -> startHandshake()):

    val handshake = PacketHandshake().apply {
        this.publicKey = keyPair.publicKey      // 33 bytes сжатый
        this.privateKey = privateKeyHash        // SHA256 hash
    }
    
  3. Сервер проверяет:

    • Публичный ключ в сжатом формате (33 байта)
    • privateKeyHash = SHA256(privateKey + "rosetta")

Совместимость

Совместимо с crypto_new

  • Генерация ключей: SHA256(seedPhrase) → privateKey
  • Публичный ключ: Сжатый формат (33 байта)
  • privateKeyHash: SHA256(privateKey + "rosetta")
  • ECDH: Поддерживает сжатые ключи через decodePoint()

⚠️ Несовместимость со старыми аккаунтами

ВАЖНО: Аккаунты, созданные со старым методом (BIP39 + несжатый ключ), больше НЕ СМОГУТ авторизоваться!

Причины:

  1. Другой приватный ключ (BIP39 vs SHA256)
  2. Другой публичный ключ (65 байт vs 33 байта)
  3. Другой privateKeyHash (из-за другого privateKey)

Миграция

Для поддержки старых аккаунтов потребуется:

  1. Определить формат ключа при загрузке:

    fun isOldFormat(publicKey: String): Boolean {
        return publicKey.length == 130  // 65 bytes = 130 hex chars
    }
    
  2. Использовать соответствующий метод генерации

НО: Рекомендуется создать новые аккаунты с новым методом шифрования!

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

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

  1. Создать тестовый seed phrase:

    test seed phrase for crypto new compatibility check here now
    
  2. JavaScript (crypto_new):

    const keyPair = await generateKeyPairFromSeed(
      "test seed phrase for crypto new compatibility check here now"
    );
    console.log("Private:", keyPair.privateKey);
    console.log("Public:", keyPair.publicKey);
    console.log("Hash:", await generateHashFromPrivateKey(keyPair.privateKey));
    
  3. Kotlin (Android):

    val seedPhrase = listOf("test", "seed", "phrase", "for", "crypto", "new", "compatibility", "check", "here", "now")
    val keyPair = CryptoManager.generateKeyPairFromSeed(seedPhrase)
    val hash = CryptoManager.generatePrivateKeyHash(keyPair.privateKey)
    
    Log.d("Test", "Private: ${keyPair.privateKey}")
    Log.d("Test", "Public: ${keyPair.publicKey}")
    Log.d("Test", "Hash: $hash")
    
  4. Результаты должны совпадать на 100%!

Проверка авторизации

  1. Создать новый аккаунт в Android приложении
  2. Сохранить seed phrase
  3. Импортировать тот же seed phrase в React Native версии
  4. Убедиться что оба приложения:
    • Генерируют одинаковые ключи
    • Успешно авторизуются на сервере
    • Могут отправлять/получать сообщения друг другу

Преимущества нового метода

1. Простота

  • Один SHA256 вместо сложной BIP39 генерации
  • Меньше зависимостей

2. Совместимость

  • 100% совместимость с JavaScript crypto_new
  • Одинаковые ключи на всех платформах

3. Размер

  • Сжатые публичные ключи: 33 байта вместо 65
  • Экономия трафика и места в БД

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

  • SHA256 быстрее чем BIP39 derivation
  • Кэширование результатов

Потенциальные проблемы

1. Миграция существующих пользователей

Решение: Требуется создание новых аккаунтов с новым seed phrase.

2. Обратная совместимость

Решение: Можно добавить проверку формата ключа и поддержку обоих методов:

fun generateKeyPairFromSeed(
    seedPhrase: List<String>,
    useNewMethod: Boolean = true
): KeyPairData {
    return if (useNewMethod) {
        // Новый метод: SHA256 + compressed
        generateKeyPairFromSeedNew(seedPhrase)
    } else {
        // Старый метод: BIP39 + uncompressed
        generateKeyPairFromSeedLegacy(seedPhrase)
    }
}

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

SHA256(seedPhrase) vs BIP39:

  • BIP39 использует PBKDF2 с 2048 итераций
  • SHA256 - один проход

Рекомендация: Для production рассмотреть использование PBKDF2 в crypto_new:

// Более безопасная версия
const privateKey = crypto
  .PBKDF2(seed, "rosetta", {
    keySize: 256 / 32,
    iterations: 2048,
  })
  .toString();

Заключение

Авторизация полностью обновлена для совместимости с crypto_new. Все ключи генерируются одинаково на Android и JavaScript платформах, что обеспечивает:

  • Единую базу кода для криптографии
  • Совместимость между платформами
  • Уменьшение размера ключей
  • Улучшение производительности

Следующие шаги:

  1. Тестирование авторизации на реальном сервере
  2. Проверка обмена сообщениями между Android и React Native
  3. Документирование процесса миграции для существующих пользователей