# 🔐 Rosette Messenger - Криптографическая Архитектура

## Обзор

Rosette Messenger использует гибридную систему шифрования с двумя уровнями:

1. **XChaCha20-Poly1305** — для шифрования содержимого сообщений
2. **ECDH + AES-256-CBC** — для безопасной передачи ключей между пользователями

---

## 📨 Архитектура Шифрования Сообщения

### Уровень 1: Шифрование Содержимого (XChaCha20-Poly1305)

#### Параметры

- **Алгоритм**: XChaCha20-Poly1305 (AEAD - Authenticated Encryption with Associated Data)
- **Размер ключа**: 32 байта (256 бит)
- **Размер nonce**: 24 байта (192 бита) — расширенный nonce для XChaCha
- **Размер тега аутентификации**: 16 байт (128 бит, Poly1305 MAC)

#### Процесс Шифрования

```kotlin
fun encryptMessage(plaintext: String): EncryptedMessage {
    // 1. Генерация случайных параметров
    val key = SecureRandom.nextBytes(32)      // 256-bit ключ
    val nonce = SecureRandom.nextBytes(24)    // 192-bit nonce

    // 2. Шифрование с XChaCha20-Poly1305
    val ciphertext = xchacha20Poly1305Encrypt(
        plaintext.toByteArray(Charsets.UTF_8),
        key,
        nonce
    )

    return EncryptedMessage(
        ciphertext = ciphertext.toHex(),
        key = key.toHex(),
        nonce = nonce.toHex()
    )
}
```

---

## 🔑 XChaCha20-Poly1305: Детальная Реализация

### Этап 1: HChaCha20 Subkey Derivation

XChaCha20 использует расширенный 192-битный nonce. Чтобы уместить его в стандартный ChaCha20 (96-битный nonce), используется **HChaCha20** для получения производного ключа.

```kotlin
// Вход: 256-bit ключ + первые 128 бит (16 байт) nonce
// Выход: 256-bit subkey
val subkey = hchacha20(key, nonce[0..15])
```

**HChaCha20** — это модифицированная версия ChaCha20, которая:

- Берёт 256-битный ключ и 128 бит из nonce
- Выполняет 20 раундов ChaCha quarter rounds
- Возвращает первые и последние 4 слова состояния (32 байта)

### Этап 2: Формирование ChaCha20 Nonce

```kotlin
// ChaCha20 использует 96-битный nonce (12 байт)
val chacha20Nonce = ByteArray(12)
// [0,0,0,0] + последние 8 байт оригинального nonce
System.arraycopy(nonce, 16, chacha20Nonce, 4, 8)
// Структура: [counter: 4 bytes][nonce: 8 bytes]
```

### Этап 3: Инициализация ChaCha20 Engine

**КРИТИЧНО**: Используется **ОДИН** engine для всего процесса!

```kotlin
val engine = ChaCha7539Engine()
engine.init(true, ParametersWithIV(KeyParameter(subkey), chacha20Nonce))
```

### Этап 4: Генерация Poly1305 Ключа

**КРИТИЧЕСКАЯ ДЕТАЛЬ**: Poly1305 ключ — это первые 32 байта первого блока ChaCha20 keystream (counter = 0).

```kotlin
// Генерируем первый 64-байтный блок keystream (counter = 0)
val poly1305KeyBlock = ByteArray(64)
engine.processBytes(ByteArray(64), 0, 64, poly1305KeyBlock, 0)

// Poly1305 ключ = первые 32 байта
val poly1305Key = poly1305KeyBlock.copyOfRange(0, 32)
```

**Почему 64 байта?**

- ChaCha20 генерирует keystream блоками по 64 байта
- Первые 32 байта → Poly1305 ключ
- Остальные 32 байта → не используются
- После этого counter автоматически увеличивается до 1

### Этап 5: Шифрование Plaintext

```kotlin
// Engine продолжает с counter = 1
val ciphertext = ByteArray(plaintext.size)
engine.processBytes(plaintext, 0, plaintext.size, ciphertext, 0)
```

**Keystream Layout**:

```
Block 0 (counter=0): [Poly1305 key: 32 bytes][unused: 32 bytes]
Block 1 (counter=1): [Plaintext byte 0...63] ⊕ [Keystream]
Block 2 (counter=2): [Plaintext byte 64...127] ⊕ [Keystream]
...
```

### Этап 6: Вычисление Poly1305 MAC

Poly1305 — это MAC (Message Authentication Code) для аутентификации ciphertext.

```kotlin
val mac = Poly1305()
mac.init(KeyParameter(poly1305Key))

// Обновляем MAC с ciphertext
mac.update(ciphertext, 0, ciphertext.size)

// Padding до 16 байт (AEAD требование)
val padding = (16 - (ciphertext.size % 16)) % 16
if (padding > 0) {
    mac.update(ByteArray(padding), 0, padding)
}

// Length fields (little-endian)
mac.update(ByteArray(8), 0, 8)  // AAD length (0 в нашем случае)
mac.update(longToLittleEndian(ciphertext.size), 0, 8)

val tag = ByteArray(16)
mac.doFinal(tag, 0)
```

### Этап 7: Финальный Формат

```kotlin
return ciphertext + tag  // [ciphertext][16-byte Poly1305 tag]
```

---

## 🔓 Расшифровка XChaCha20-Poly1305

Процесс зеркальный, но с проверкой аутентификации **перед** расшифровкой:

```kotlin
fun xchacha20Poly1305Decrypt(
    ciphertextWithTag: ByteArray,
    key: ByteArray,
    nonce: ByteArray
): ByteArray {
    // 1. Разделение ciphertext и tag
    val ciphertext = ciphertextWithTag[0..-17]
    val tag = ciphertextWithTag[-16..-1]

    // 2. HChaCha20 subkey derivation (как при шифровании)
    val subkey = hchacha20(key, nonce[0..15])

    // 3. ChaCha20 nonce
    val chacha20Nonce = [0,0,0,0] + nonce[16..23]

    // 4. Инициализация engine
    val engine = ChaCha7539Engine()
    engine.init(true, ParametersWithIV(KeyParameter(subkey), chacha20Nonce))

    // 5. Генерация Poly1305 ключа (тот же процесс)
    val poly1305KeyBlock = ByteArray(64)
    engine.processBytes(ByteArray(64), 0, 64, poly1305KeyBlock, 0)

    // 6. Проверка Poly1305 tag
    val mac = Poly1305()
    mac.init(KeyParameter(poly1305KeyBlock[0..31]))
    mac.update(ciphertext)
    // ... padding и length fields ...
    val computedTag = mac.doFinal()

    if (!tag.contentEquals(computedTag)) {
        throw SecurityException("Authentication failed")
    }

    // 7. Расшифровка (только после проверки!)
    // Создаём новый engine для расшифровки
    val decryptEngine = ChaCha7539Engine()
    decryptEngine.init(false, ParametersWithIV(KeyParameter(subkey), chacha20Nonce))

    // Пропускаем первые 64 байта (Poly1305 key block)
    decryptEngine.processBytes(ByteArray(64), 0, 64, ByteArray(64), 0)

    // Расшифровываем
    val plaintext = ByteArray(ciphertext.size)
    decryptEngine.processBytes(ciphertext, 0, ciphertext.size, plaintext, 0)

    return plaintext
}
```

---

## 🔐 Уровень 2: Шифрование Ключей (ECDH + AES-256-CBC)

После шифрования сообщения XChaCha20, нужно безопасно передать ключ и nonce получателю.

### Схема: Elliptic Curve Diffie-Hellman + AES

#### Параметры

- **Эллиптическая кривая**: secp256k1 (та же что в Bitcoin)
- **AES режим**: CBC (Cipher Block Chaining)
- **Размер ключа AES**: 256 бит
- **Размер IV**: 16 байт (128 бит)
- **Padding**: PKCS5Padding

#### Процесс Шифрования Ключа

```kotlin
fun encryptKeyForRecipient(
    keyAndNonce: ByteArray,      // 56 байт: 32 (key) + 24 (nonce)
    recipientPublicKeyHex: String
): String {
    // 1. Генерация эфемерной пары ключей
    val ephemeralPrivateKey = SecureRandom.nextBytes(32)
    val ephemeralPublicKey = secp256k1.G × ephemeralPrivateKey

    // 2. ECDH: Вычисление shared secret
    val recipientPublicKey = parsePublicKey(recipientPublicKeyHex)
    val sharedPoint = recipientPublicKey × ephemeralPrivateKey
    val sharedSecret = sharedPoint.x.toHex()  // X-координата точки

    // 3. Генерация IV для AES
    val iv = SecureRandom.nextBytes(16)

    // 4. КРИТИЧНО: Latin1 → UTF-8 encoding
    // Эмуляция поведения crypto-js в JavaScript
    val latin1String = String(keyAndNonce, Charsets.ISO_8859_1)
    val utf8Bytes = latin1String.toByteArray(Charsets.UTF_8)

    // 5. AES-256-CBC шифрование
    val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
    cipher.init(
        Cipher.ENCRYPT_MODE,
        SecretKeySpec(sharedSecret.hexToBytes(), "AES"),
        IvParameterSpec(iv)
    )
    val encryptedKey = cipher.doFinal(utf8Bytes)

    // 6. Формат: iv:ciphertext:ephemeralPrivateKey
    val combined = "${iv.toHex()}:${encryptedKey.toHex()}:${ephemeralPrivateKey.toHex()}"

    // 7. Base64 encoding
    return Base64.encodeToString(combined.toByteArray(), Base64.NO_WRAP)
}
```

#### Почему Latin1 → UTF-8?

**JavaScript (crypto-js) поведение**:

```javascript
// React Native
const key = Buffer.concat([keyBytes, nonceBytes]);  // 56 байт
const keyString = key.toString('binary');  // Latin1 строка (56 символов)
const encrypted = crypto.AES.encrypt(keyString, ...);
// crypto-js внутри делает: Utf8.parse(keyString) → конвертирует в UTF-8
```

**Kotlin эмуляция**:

```kotlin
// Байты → Latin1 строка (символы с кодами 0-255)
val latin1String = String(keyAndNonce, Charsets.ISO_8859_1)
// Latin1 строка → UTF-8 байты (байты > 127 становятся multi-byte)
val utf8Bytes = latin1String.toByteArray(Charsets.UTF_8)
```

**Пример трансформации**:

```
Байт:         [0xCC] (204)
↓
Latin1 char:  'Ì' (charCode = 204)
↓
UTF-8 bytes:  [0xC3, 0x8C] (2 байта)
```

Результат: 56 байт → 88 байт UTF-8 (байты > 127 занимают 2 байта в UTF-8)

#### Формат Зашифрованного Ключа

```
Base64( ivHex : ciphertextHex : ephemeralPrivateKeyHex )

Пример:
MjdmZTMzYTIyYjczYjNiNDhmOTIzYmY3YmJjNDhmMzE6MzIwNDc4NzMzNzM2NTQ1MzYy...
```

Декодированная строка:

```
27fe33a22b73b3b48f923bf7bbc48f31:32047873373654536225d8b1....:6ed2d3c3391fcccd...
[        IV (32 hex)        ]:[  Ciphertext (192 hex)    ]:[Ephemeral Key (64 hex)]
```

---

## 🔓 Расшифровка Ключа

```kotlin
fun decryptKeyFromSender(
    encryptedKeyBase64: String,
    myPrivateKeyHex: String
): ByteArray {
    // 1. Декодирование Base64
    val decoded = String(Base64.decode(encryptedKeyBase64, Base64.DEFAULT))
    val parts = decoded.split(":")
    val ivHex = parts[0]
    val ciphertextHex = parts[1]
    val ephemeralPrivateKeyHex = parts[2]

    // 2. Парсинг ключей
    val ephemeralPrivateKey = ephemeralPrivateKeyHex.hexToBytes()
    val myPrivateKey = myPrivateKeyHex.hexToBytes()

    // 3. ECDH: Вычисление того же shared secret
    val myPublicKey = secp256k1.G × myPrivateKey
    val ephemeralPublicKey = secp256k1.G × ephemeralPrivateKey
    val sharedPoint = myPublicKey × ephemeralPrivateKey
    val sharedSecret = sharedPoint.x.toHex()

    // 4. AES-256-CBC расшифровка
    val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
    cipher.init(
        Cipher.DECRYPT_MODE,
        SecretKeySpec(sharedSecret.hexToBytes(), "AES"),
        IvParameterSpec(ivHex.hexToBytes())
    )
    val decryptedUtf8Bytes = cipher.doFinal(ciphertextHex.hexToBytes())

    // 5. UTF-8 → Latin1 (обратная конвертация)
    val utf8String = String(decryptedUtf8Bytes, Charsets.UTF_8)
    val originalBytes = utf8String.toByteArray(Charsets.ISO_8859_1)

    return originalBytes  // 56 байт: 32 (key) + 24 (nonce)
}
```

---

## 📦 Полный Цикл: Отправка Сообщения

```kotlin
fun encryptForSending(
    plaintext: String,
    recipientPublicKey: String
): Pair<String, String> {
    // 1. Шифрование сообщения XChaCha20-Poly1305
    val encrypted = encryptMessage(plaintext)
    // encrypted.ciphertext = зашифрованное сообщение (hex)
    // encrypted.key = 32-byte ключ (hex)
    // encrypted.nonce = 24-byte nonce (hex)

    // 2. Объединение key + nonce
    val keyAndNonce = encrypted.key.hexToBytes() + encrypted.nonce.hexToBytes()
    // keyAndNonce = 56 байт

    // 3. Шифрование ключа для получателя (ECDH + AES)
    val encryptedKey = encryptKeyForRecipient(keyAndNonce, recipientPublicKey)
    // encryptedKey = Base64 строка

    return Pair(encrypted.ciphertext, encryptedKey)
}
```

### Формат Пакета Сообщения

```kotlin
data class PacketMessage(
    val from: String,           // Публичный ключ отправителя (hex)
    val to: String,             // Публичный ключ получателя (hex)
    val content: String,        // Зашифрованное сообщение (hex)
    val chachaKey: String,      // Зашифрованный ключ (Base64)
    val timestamp: Long,
    val messageId: String
)
```

---

## 📬 Полный Цикл: Получение Сообщения

```kotlin
fun decryptReceived(
    encryptedContent: String,
    encryptedKey: String,
    myPrivateKey: String
): String {
    // 1. Расшифровка ключа (ECDH + AES)
    val keyAndNonce = decryptKeyFromSender(encryptedKey, myPrivateKey)

    // 2. Разделение на key и nonce
    val key = keyAndNonce.copyOfRange(0, 32)
    val nonce = keyAndNonce.copyOfRange(32, 56)

    // 3. Расшифровка сообщения (XChaCha20-Poly1305)
    val plaintext = decryptMessage(
        encryptedContent.hexToBytes(),
        key,
        nonce
    )

    return String(plaintext, Charsets.UTF_8)
}
```

---

## 🔧 Критические Детали Реализации

### 1. Counter Management в XChaCha20

**ПРОБЛЕМА**: Если создать два отдельных ChaCha20 engine, оба начнут с counter=0.

❌ **Неправильно**:

```kotlin
// Engine 1: Poly1305 ключ
val engine1 = ChaCha7539Engine()
engine1.processBytes(zeros, ...) // counter = 0

// Engine 2: Шифрование
val engine2 = ChaCha7539Engine()  // ❌ Снова counter = 0!
engine2.processBytes(plaintext, ...)
```

✅ **Правильно**:

```kotlin
// Один engine для всего процесса
val engine = ChaCha7539Engine()
engine.init(true, ParametersWithIV(key, nonce))

// Poly1305 ключ (counter = 0)
engine.processBytes(zeros[64], ..., poly1305KeyBlock, ...)

// Шифрование (counter автоматически = 1)
engine.processBytes(plaintext, ..., ciphertext, ...)
```

### 2. JavaScript/Kotlin Совместимость

**crypto-js особенности**:

```javascript
crypto.AES.encrypt(string, key, { iv });
// Внутри: crypto.enc.Utf8.parse(string)
// Это берёт charCode каждого символа как байт
```

**Kotlin эмуляция**:

```kotlin
// Байты → Latin1 (каждый байт = один символ)
val latin1 = String(bytes, Charsets.ISO_8859_1)
// Latin1 → UTF-8 (символы > 127 становятся multi-byte)
val utf8 = latin1.toByteArray(Charsets.UTF_8)
```

### 3. ECDH Shared Secret

**JavaScript (elliptic.js)**:

```javascript
const shared = ephemeralKey.derive(publicKey).toString(16);
// .toString(16) НЕ добавляет ведущие нули!
```

**Kotlin эмуляция**:

```kotlin
val xCoord = sharedPoint.normalize().xCoord.toBigInteger()
var hex = xCoord.toString(16)  // Может быть без ведущих нулей

// Если нечётная длина, добавить ведущий 0 для парсинга
if (hex.length % 2 != 0) {
    hex = "0$hex"
}
```

### 4. Little-Endian в Poly1305

Длины (AAD length, ciphertext length) должны быть в **little-endian** формате:

```kotlin
fun longToLittleEndian(n: Long): ByteArray {
    val bs = ByteArray(8)
    bs[0] = n.toByte()
    bs[1] = (n ushr 8).toByte()
    bs[2] = (n ushr 16).toByte()
    bs[3] = (n ushr 24).toByte()
    bs[4] = (n ushr 32).toByte()
    bs[5] = (n ushr 40).toByte()
    bs[6] = (n ushr 48).toByte()
    bs[7] = (n ushr 56).toByte()
    return bs
}
```

---

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

### Сильные Стороны

1. **Forward Secrecy**: Каждое сообщение использует уникальный эфемерный ключ
2. **Authenticated Encryption**: Poly1305 MAC защищает от модификации
3. **Extended Nonce**: XChaCha20 позволяет безопасно использовать случайные nonce
4. **ECDH**: Асимметричное шифрование ключей без необходимости предварительного обмена

### Важные Замечания

⚠️ **Передача Ephemeral Private Key**:

```kotlin
// Формат: iv:ciphertext:ephemeralPrivateKey
```

В текущей реализации ephemeral **private** key передаётся вместе с ciphertext. Это работает, но не является стандартной практикой. Обычно передаётся ephemeral **public** key, и получатель использует свой private key для ECDH.

**Текущая схема**:

- Отправитель: `ephemeralPrivate × recipientPublic = sharedSecret`
- Получатель: `ephemeralPrivate × myPublic = sharedSecret`

**Стандартная схема**:

- Отправитель: `ephemeralPrivate × recipientPublic = sharedSecret`
- Получатель: `myPrivate × ephemeralPublic = sharedSecret`

Текущая схема работает корректно, но передача private key нестандартна.

---

## 📊 Размеры Данных

### Одно Сообщение

```
Plaintext:     N байт
XChaCha20:     N + 16 байт (ciphertext + Poly1305 tag)
Key+Nonce:     56 байт (32 + 24)
Latin1→UTF-8:  ~88 байт (зависит от содержимого)
AES+padding:   96 байт (после PKCS5 padding)
Зашифр. ключ: ~388 символов Base64

Итого overhead: ~452 символа (независимо от размера сообщения)
```

### Пример

```
Сообщение: "Hello" (5 байт)
↓
XChaCha20: 21 байт (5 + 16 tag)
Key: 32 байт, Nonce: 24 байт → 56 байт
↓
AES: 96 байт (после padding)
↓
Base64: 388 символов

Передаётся:
- content: 42 символа (21 байт hex)
- chachaKey: 388 символов (Base64)
Итого: ~430 символов для передачи "Hello"
```

---

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

### Unit Test для XChaCha20

```kotlin
@Test
fun testXChaCha20Compatibility() {
    val key = "ccd8617e4e328baee60fc2d5de0cca9aea7ac382330aa1daafb188bc875baa68"
    val nonce = "3cc45a27fe3910913bd429f6a814039fb2c7c564d6656727"
    val plaintext = "kdkdkdkd"

    val encrypted = xchacha20Poly1305Encrypt(
        plaintext.toByteArray(),
        key.hexToBytes(),
        nonce.hexToBytes()
    )

    // Должно совпадать с @noble/ciphers
    val decrypted = xchacha20Poly1305Decrypt(
        encrypted,
        key.hexToBytes(),
        nonce.hexToBytes()
    )

    assertEquals(plaintext, String(decrypted, Charsets.UTF_8))
}
```

### Integration Test

```kotlin
@Test
fun testFullEncryptionCycle() {
    val alicePrivate = generatePrivateKey()
    val alicePublic = derivePublicKey(alicePrivate)

    val bobPrivate = generatePrivateKey()
    val bobPublic = derivePublicKey(bobPrivate)

    // Alice → Bob
    val message = "Secret message"
    val (ciphertext, encryptedKey) = encryptForSending(message, bobPublic)

    // Bob получает и расшифровывает
    val decrypted = decryptReceived(ciphertext, encryptedKey, bobPrivate)

    assertEquals(message, decrypted)
}
```

---

## 🔗 Библиотеки

### Kotlin (Android)

- **BouncyCastle** 1.77 — secp256k1, AES, ChaCha20, Poly1305
- **Android Crypto** — SecureRandom

### JavaScript (React Native/Desktop)

- **@noble/ciphers** — XChaCha20-Poly1305
- **crypto-js** — AES, PBKDF2, кодировки
- **elliptic** — secp256k1, ECDH

---

## 📝 Changelog

### Исправления 2026-01-11

#### Проблема: XChaCha20 Counter Management

**Симптом**: Desktop показывал кракозябры при получении сообщений от Kotlin Android app.

**Причина**: Kotlin создавал два отдельных `ChaCha7539Engine`:

1. Первый для генерации Poly1305 ключа (counter = 0)
2. Второй для шифрования plaintext (counter = 0 снова!)

Это приводило к тому что plaintext шифровался тем же keystream что и Poly1305 ключ.

**Решение**: Использовать **один** engine для всего процесса:

```kotlin
// 1. Генерация Poly1305 ключа (counter = 0, байты 0-63)
engine.processBytes(ByteArray(64), ..., poly1305KeyBlock, ...)

// 2. Шифрование (counter автоматически = 1, байты 64+)
engine.processBytes(plaintext, ..., ciphertext, ...)
```

---

## 🎓 Дополнительные Ресурсы

- [XChaCha20-Poly1305 RFC Draft](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha)
- [ChaCha20 и Poly1305 для IETF](https://datatracker.ietf.org/doc/html/rfc8439)
- [secp256k1 на SEC2](https://www.secg.org/sec2-v2.pdf)
- [Elliptic Curve Diffie-Hellman](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman)
