Files
bitchat/bitchatTests/XChaCha20Poly1305CompatTests.swift
T
b31a63ce37 Burn down SwiftLint advisory violations from 109 to 4 (#1362)
Mechanical style fixes across the enabled rule set, mostly via
swiftlint --fix (trailing_comma, comma, colon, trailing_newline,
comment_spacing, unused_closure_parameter, unneeded_break_in_switch,
opening_brace) plus hand fixes:

- non_optional_string_data_conversion (45): .data(using: .utf8)! and
  ?? Data() fallbacks replaced with the non-optional Data(_.utf8),
  including two production sites (NIP-44 HKDF info constant and the
  announce canonicalization context/nickname bytes — byte-identical
  output, only the impossible-nil handling is gone).
- switch_case_alignment: LocationChannel had a misindented closing
  brace; also repaired an --fix artifact in BLEService's .none case.
- redundant_string_enum_value: TrustLevel raw values equal to the case
  names (encoded form unchanged).
- unused_optional_binding: let _ = binds replaced with != nil / is Bool.
- static_over_final_class: PreviewView.layerClass.
- Resolved the BinaryProtocolTests TODO by documenting that 8-byte
  recipient ID truncation is the fixed wire-field size, not a bug.

The 4 remaining violations are all todo markers for a shared
test-helpers module (tracked in #1088) and one Reuse note.

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 14:09:13 +02:00

223 lines
7.0 KiB
Swift

//
// XChaCha20Poly1305CompatTests.swift
// bitchatTests
//
// Tests for XChaCha20-Poly1305 encryption with proper error handling.
// This is free and unencumbered software released into the public domain.
//
import Testing
import struct Foundation.Data
@testable import bitchat
struct XChaCha20Poly1305CompatTests {
@Test func sealAndOpenRoundtrip() throws {
let plaintext = Data("Hello, XChaCha20-Poly1305!".utf8)
let key = Data(repeating: 0x42, count: 32)
let nonce = Data(repeating: 0x24, count: 24)
let sealed = try XChaCha20Poly1305Compat.seal(plaintext: plaintext, key: key, nonce24: nonce)
let decrypted = try XChaCha20Poly1305Compat.open(
ciphertext: sealed.ciphertext,
tag: sealed.tag,
key: key,
nonce24: nonce
)
#expect(decrypted == plaintext)
}
@Test func sealAndOpenWithAAD() throws {
let plaintext = Data("Secret message".utf8)
let key = Data(repeating: 0xAB, count: 32)
let nonce = Data(repeating: 0xCD, count: 24)
let aad = Data("additional authenticated data".utf8)
let sealed = try XChaCha20Poly1305Compat.seal(plaintext: plaintext, key: key, nonce24: nonce, aad: aad)
let decrypted = try XChaCha20Poly1305Compat.open(
ciphertext: sealed.ciphertext,
tag: sealed.tag,
key: key,
nonce24: nonce,
aad: aad
)
#expect(decrypted == plaintext)
}
@Test func sealProducesDifferentCiphertextWithDifferentNonces() throws {
let plaintext = Data("Same plaintext".utf8)
let key = Data(repeating: 0x42, count: 32)
let nonce1 = Data(repeating: 0x01, count: 24)
let nonce2 = Data(repeating: 0x02, count: 24)
let sealed1 = try XChaCha20Poly1305Compat.seal(plaintext: plaintext, key: key, nonce24: nonce1)
let sealed2 = try XChaCha20Poly1305Compat.seal(plaintext: plaintext, key: key, nonce24: nonce2)
#expect(sealed1.ciphertext != sealed2.ciphertext)
}
@Test func sealThrowsOnShortKey() {
let plaintext = Data("Test".utf8)
let shortKey = Data(repeating: 0x42, count: 16)
let nonce = Data(repeating: 0x24, count: 24)
var didThrow = false
do {
_ = try XChaCha20Poly1305Compat.seal(plaintext: plaintext, key: shortKey, nonce24: nonce)
} catch {
didThrow = true
}
#expect(didThrow)
}
@Test func sealThrowsOnLongKey() {
let plaintext = Data("Test".utf8)
let longKey = Data(repeating: 0x42, count: 64)
let nonce = Data(repeating: 0x24, count: 24)
var didThrow = false
do {
_ = try XChaCha20Poly1305Compat.seal(plaintext: plaintext, key: longKey, nonce24: nonce)
} catch {
didThrow = true
}
#expect(didThrow)
}
@Test func sealThrowsOnEmptyKey() {
let plaintext = Data("Test".utf8)
let emptyKey = Data()
let nonce = Data(repeating: 0x24, count: 24)
var didThrow = false
do {
_ = try XChaCha20Poly1305Compat.seal(plaintext: plaintext, key: emptyKey, nonce24: nonce)
} catch {
didThrow = true
}
#expect(didThrow)
}
@Test func openThrowsOnInvalidKeyLength() {
let ciphertext = Data(repeating: 0x00, count: 16)
let tag = Data(repeating: 0x00, count: 16)
let shortKey = Data(repeating: 0x42, count: 31)
let nonce = Data(repeating: 0x24, count: 24)
var didThrow = false
do {
_ = try XChaCha20Poly1305Compat.open(ciphertext: ciphertext, tag: tag, key: shortKey, nonce24: nonce)
} catch {
didThrow = true
}
#expect(didThrow)
}
@Test func sealThrowsOnShortNonce() {
let plaintext = Data("Test".utf8)
let key = Data(repeating: 0x42, count: 32)
let shortNonce = Data(repeating: 0x24, count: 12)
var didThrow = false
do {
_ = try XChaCha20Poly1305Compat.seal(plaintext: plaintext, key: key, nonce24: shortNonce)
} catch {
didThrow = true
}
#expect(didThrow)
}
@Test func sealThrowsOnLongNonce() {
let plaintext = Data("Test".utf8)
let key = Data(repeating: 0x42, count: 32)
let longNonce = Data(repeating: 0x24, count: 32)
var didThrow = false
do {
_ = try XChaCha20Poly1305Compat.seal(plaintext: plaintext, key: key, nonce24: longNonce)
} catch {
didThrow = true
}
#expect(didThrow)
}
@Test func sealThrowsOnEmptyNonce() {
let plaintext = Data("Test".utf8)
let key = Data(repeating: 0x42, count: 32)
let emptyNonce = Data()
var didThrow = false
do {
_ = try XChaCha20Poly1305Compat.seal(plaintext: plaintext, key: key, nonce24: emptyNonce)
} catch {
didThrow = true
}
#expect(didThrow)
}
@Test func openThrowsOnInvalidNonceLength() {
let ciphertext = Data(repeating: 0x00, count: 16)
let tag = Data(repeating: 0x00, count: 16)
let key = Data(repeating: 0x42, count: 32)
let shortNonce = Data(repeating: 0x24, count: 23)
var didThrow = false
do {
_ = try XChaCha20Poly1305Compat.open(ciphertext: ciphertext, tag: tag, key: key, nonce24: shortNonce)
} catch {
didThrow = true
}
#expect(didThrow)
}
@Test func openFailsWithWrongKey() throws {
let plaintext = Data("Secret".utf8)
let correctKey = Data(repeating: 0x42, count: 32)
let wrongKey = Data(repeating: 0x43, count: 32)
let nonce = Data(repeating: 0x24, count: 24)
let sealed = try XChaCha20Poly1305Compat.seal(plaintext: plaintext, key: correctKey, nonce24: nonce)
var didThrow = false
do {
_ = try XChaCha20Poly1305Compat.open(
ciphertext: sealed.ciphertext,
tag: sealed.tag,
key: wrongKey,
nonce24: nonce
)
} catch {
didThrow = true
}
#expect(didThrow)
}
@Test func openFailsWithTamperedCiphertext() throws {
let plaintext = Data("Secret".utf8)
let key = Data(repeating: 0x42, count: 32)
let nonce = Data(repeating: 0x24, count: 24)
let sealed = try XChaCha20Poly1305Compat.seal(plaintext: plaintext, key: key, nonce24: nonce)
// Create tampered ciphertext by changing first byte
var tamperedBytes = [UInt8](sealed.ciphertext)
tamperedBytes[0] = tamperedBytes[0] ^ 0xFF
let tampered = Data(tamperedBytes)
var didThrow = false
do {
_ = try XChaCha20Poly1305Compat.open(
ciphertext: tampered,
tag: sealed.tag,
key: key,
nonce24: nonce
)
} catch {
didThrow = true
}
#expect(didThrow)
}
}