test: audit AES-256-GCM crypto with 7 test cases

Verify random IV (same plaintext encrypts differently), ciphertext
format (iv:authTag:encrypted), auth tag tamper detection, encrypted
data tamper detection, empty string handling, and unicode support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-13 09:33:08 +00:00
co-authored by Claude Opus 4.6
parent c5744f5984
commit b1e86843a7
+64
View File
@@ -0,0 +1,64 @@
import { describe, it, expect } from 'vitest'
import { encrypt, decrypt } from './crypto.js'
describe('crypto — AES-256-GCM', () => {
it('encrypt then decrypt returns original plaintext', () => {
const plaintext = 'nostr+walletconnect://relay.example.com?secret=abc123'
const ciphertext = encrypt(plaintext)
const decrypted = decrypt(ciphertext)
expect(decrypted).toBe(plaintext)
})
it('encrypting same plaintext twice produces different ciphertexts (random IV)', () => {
const plaintext = 'same-text-every-time'
const c1 = encrypt(plaintext)
const c2 = encrypt(plaintext)
expect(c1).not.toBe(c2)
// Both should still decrypt to the same value
expect(decrypt(c1)).toBe(plaintext)
expect(decrypt(c2)).toBe(plaintext)
})
it('ciphertext format is iv:authTag:encrypted (3 hex parts)', () => {
const ciphertext = encrypt('test data')
const parts = ciphertext.split(':')
expect(parts.length).toBe(3)
// IV should be 16 bytes = 32 hex chars
expect(parts[0].length).toBe(32)
expect(parts[0]).toMatch(/^[0-9a-f]+$/)
// Auth tag should be 16 bytes = 32 hex chars
expect(parts[1].length).toBe(32)
expect(parts[1]).toMatch(/^[0-9a-f]+$/)
// Encrypted data is hex
expect(parts[2]).toMatch(/^[0-9a-f]+$/)
})
it('decrypt throws on tampered ciphertext (auth tag verification)', () => {
const ciphertext = encrypt('sensitive wallet data')
const parts = ciphertext.split(':')
// Tamper with the encrypted data
const tampered = parts[0] + ':' + parts[1] + ':' + 'ff'.repeat(parts[2].length / 2)
expect(() => decrypt(tampered)).toThrow()
})
it('decrypt throws on tampered auth tag', () => {
const ciphertext = encrypt('sensitive wallet data')
const parts = ciphertext.split(':')
// Tamper with the auth tag
const tampered = parts[0] + ':' + '00'.repeat(16) + ':' + parts[2]
expect(() => decrypt(tampered)).toThrow()
})
it('handles empty string encryption', () => {
const ciphertext = encrypt('')
const decrypted = decrypt(ciphertext)
expect(decrypted).toBe('')
})
it('handles unicode content', () => {
const plaintext = '₿⚡ Bitcoin Lightning 🟠'
const ciphertext = encrypt(plaintext)
const decrypted = decrypt(ciphertext)
expect(decrypted).toBe(plaintext)
})
})