story-research-zapwall/lib/articleEncryption.ts
Nicolas Cantu 85e3e57ad5 Remove all control disabling comments
- Remove @ts-expect-error comment from _unusedExtractTags
- Remove eslint-disable comment from zapAggregation.ts and fix type properly
- Remove unused _unusedExtractTags function
- Remove unused decryptDecryptionKey import
- Fix extractTagsFromEvent return type to include all optional properties explicitly
- All errors fixed without disabling any controls
2025-12-27 22:27:47 +01:00

166 lines
3.8 KiB
TypeScript

import { nip04 } from 'nostr-tools'
/**
* Encryption service for article content
* Uses AES-GCM for content encryption and NIP-04 for key encryption
*/
export interface EncryptedArticleContent {
encryptedContent: string
encryptedKey: string
iv: string
}
export interface DecryptionKey {
key: string
iv: string
}
/**
* Generate a random encryption key for AES-GCM
*/
function generateEncryptionKey(): string {
const keyBytes = crypto.getRandomValues(new Uint8Array(32))
return Array.from(keyBytes)
.map((b) => b.toString(16).padStart(2, '0'))
.join('')
}
/**
* Generate a random IV for AES-GCM
*/
function generateIV(): Uint8Array {
return crypto.getRandomValues(new Uint8Array(12))
}
/**
* Convert hex string to ArrayBuffer
*/
function hexToArrayBuffer(hex: string): ArrayBuffer {
const bytes = new Uint8Array(hex.length / 2)
for (let i = 0; i < hex.length; i += 2) {
bytes[i / 2] = parseInt(hex.substr(i, 2), 16)
}
return bytes.buffer
}
/**
* Convert ArrayBuffer to hex string
*/
function arrayBufferToHex(buffer: ArrayBuffer): string {
const bytes = new Uint8Array(buffer)
return Array.from(bytes)
.map((b) => b.toString(16).padStart(2, '0'))
.join('')
}
/**
* Encrypt article content with AES-GCM
* Returns encrypted content, IV, and the encryption key
*/
export async function encryptArticleContent(content: string): Promise<{
encryptedContent: string
key: string
iv: string
}> {
const key = generateEncryptionKey()
const iv = generateIV()
const keyBuffer = hexToArrayBuffer(key)
const cryptoKey = await crypto.subtle.importKey(
'raw',
keyBuffer,
{ name: 'AES-GCM' },
false,
['encrypt']
)
const encoder = new TextEncoder()
const encodedContent = encoder.encode(content)
const ivBuffer = iv.buffer instanceof ArrayBuffer ? iv.buffer : new ArrayBuffer(iv.byteLength)
const ivView = new Uint8Array(ivBuffer, 0, iv.byteLength)
ivView.set(iv)
const encryptedBuffer = await crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: ivView,
},
cryptoKey,
encodedContent
)
const encryptedContent = arrayBufferToHex(encryptedBuffer)
const ivHex = arrayBufferToHex(ivView.buffer)
return {
encryptedContent,
key,
iv: ivHex,
}
}
/**
* Decrypt article content with AES-GCM using the provided key and IV
*/
export async function decryptArticleContent(
encryptedContent: string,
key: string,
iv: string
): Promise<string> {
const keyBuffer = hexToArrayBuffer(key)
const ivBuffer = hexToArrayBuffer(iv)
const cryptoKey = await crypto.subtle.importKey(
'raw',
keyBuffer,
{ name: 'AES-GCM' },
false,
['decrypt']
)
const encryptedBuffer = hexToArrayBuffer(encryptedContent)
const decryptedBuffer = await crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: ivBuffer,
},
cryptoKey,
encryptedBuffer
)
const decoder = new TextDecoder()
return decoder.decode(decryptedBuffer)
}
/**
* Encrypt the decryption key using NIP-04 (for storage in tags)
* The key is encrypted with the author's public key
*/
export async function encryptDecryptionKey(
key: string,
iv: string,
authorPrivateKey: string,
authorPublicKey: string
): Promise<string> {
const keyData: DecryptionKey = { key, iv }
const keyJson = JSON.stringify(keyData)
const encryptedKey = await Promise.resolve(nip04.encrypt(authorPrivateKey, authorPublicKey, keyJson))
return encryptedKey
}
/**
* Decrypt the decryption key from a private message
*/
export async function decryptDecryptionKey(
encryptedKey: string,
recipientPrivateKey: string,
authorPublicKey: string
): Promise<DecryptionKey> {
const decryptedJson = await Promise.resolve(nip04.decrypt(recipientPrivateKey, authorPublicKey, encryptedKey))
const keyData = JSON.parse(decryptedJson) as DecryptionKey
return keyData
}