159 lines
5.4 KiB
TypeScript
159 lines
5.4 KiB
TypeScript
/**
|
|
* Benchmark: AES-256-GCM (Web Crypto) vs NaCl SecretBox
|
|
* Tests encryption/decryption performance across payload sizes
|
|
*/
|
|
|
|
import {
|
|
aesEncrypt,
|
|
aesDecrypt,
|
|
generateSymmetricKey,
|
|
generateKeyPair,
|
|
encryptForRecipient,
|
|
decryptFromSender,
|
|
shareDocKey,
|
|
receiveDocKey,
|
|
} from './mit-crypto.js'
|
|
function generateTestData(size: number): Uint8Array {
|
|
const data = new Uint8Array(size)
|
|
// getRandomValues has 64KB limit, fill in chunks
|
|
for (let offset = 0; offset < size; offset += 65536) {
|
|
const chunk = Math.min(65536, size - offset)
|
|
crypto.getRandomValues(data.subarray(offset, offset + chunk))
|
|
}
|
|
return data
|
|
}
|
|
|
|
const encoder = new TextEncoder()
|
|
const decoder = new TextDecoder()
|
|
|
|
async function benchmark(name: string, fn: () => Promise<void>, iterations: number = 1000) {
|
|
// Warmup
|
|
for (let i = 0; i < 10; i++) await fn()
|
|
|
|
const start = performance.now()
|
|
for (let i = 0; i < iterations; i++) await fn()
|
|
const elapsed = performance.now() - start
|
|
|
|
console.log(`${name}: ${(elapsed / iterations).toFixed(3)}ms avg (${iterations} iterations)`)
|
|
}
|
|
|
|
async function main() {
|
|
console.log('=== rStack Crypto Primitives Benchmark ===\n')
|
|
|
|
// ─── Symmetric Encryption ───
|
|
console.log('--- Symmetric Encryption (AES-256-GCM) ---')
|
|
const key = generateSymmetricKey()
|
|
|
|
for (const size of [100, 1_000, 10_000, 100_000]) {
|
|
const data = generateTestData(size)
|
|
|
|
await benchmark(` Encrypt ${size.toLocaleString()}B`, async () => {
|
|
await aesEncrypt(key, data)
|
|
})
|
|
|
|
const encrypted = await aesEncrypt(key, data)
|
|
await benchmark(` Decrypt ${size.toLocaleString()}B`, async () => {
|
|
await aesDecrypt(key, encrypted)
|
|
})
|
|
}
|
|
|
|
// ─── Key Exchange ───
|
|
console.log('\n--- Key Exchange (x25519 ECDH) ---')
|
|
const alice = generateKeyPair()
|
|
const bob = generateKeyPair()
|
|
|
|
await benchmark(' Generate key pair', async () => {
|
|
generateKeyPair()
|
|
})
|
|
|
|
// ─── ECIES Encryption ───
|
|
console.log('\n--- ECIES Encryption (x25519 + AES-256-GCM) ---')
|
|
const message = encoder.encode('This is a secret document encryption key - 32 bytes!')
|
|
|
|
await benchmark(' Encrypt for recipient', async () => {
|
|
await encryptForRecipient(message, bob.publicKey, alice.privateKey)
|
|
})
|
|
|
|
const { ciphertext, ephemeralPublicKey } = await encryptForRecipient(
|
|
message, bob.publicKey, alice.privateKey
|
|
)
|
|
await benchmark(' Decrypt from sender', async () => {
|
|
await decryptFromSender(ciphertext, ephemeralPublicKey, bob.privateKey)
|
|
})
|
|
|
|
// ─── Document Key Sharing ───
|
|
console.log('\n--- Document Key Sharing ---')
|
|
const docKey = generateSymmetricKey()
|
|
|
|
await benchmark(' Share doc key', async () => {
|
|
await shareDocKey(docKey, bob.publicKey, alice.privateKey)
|
|
})
|
|
|
|
const shared = await shareDocKey(docKey, bob.publicKey, alice.privateKey)
|
|
await benchmark(' Receive doc key', async () => {
|
|
await receiveDocKey(shared.encryptedKey, shared.ephemeralPublicKey, bob.privateKey)
|
|
})
|
|
|
|
// ─── End-to-End Roundtrip ───
|
|
console.log('\n--- Full Roundtrip: Note Encryption + Key Sharing ---')
|
|
const noteContent = encoder.encode(JSON.stringify({
|
|
title: 'Secret Meeting Notes',
|
|
content: '<p>Discussion about token allocation...</p>'.repeat(100),
|
|
tags: ['dao', 'treasury', 'private'],
|
|
}))
|
|
|
|
await benchmark(' Full encrypt + share flow', async () => {
|
|
// 1. Generate doc key
|
|
const dk = generateSymmetricKey()
|
|
// 2. Encrypt content
|
|
const enc = await aesEncrypt(dk, noteContent)
|
|
// 3. Share key with collaborator
|
|
await shareDocKey(dk, bob.publicKey, alice.privateKey)
|
|
})
|
|
|
|
await benchmark(' Full receive + decrypt flow', async () => {
|
|
// Simulated received data
|
|
const dk = generateSymmetricKey()
|
|
const enc = await aesEncrypt(dk, noteContent)
|
|
const sk = await shareDocKey(dk, bob.publicKey, alice.privateKey)
|
|
// Actual measured flow
|
|
const receivedKey = await receiveDocKey(sk.encryptedKey, sk.ephemeralPublicKey, bob.privateKey)
|
|
await aesDecrypt(receivedKey, enc)
|
|
})
|
|
|
|
// ─── Correctness Check ───
|
|
console.log('\n--- Correctness Verification ---')
|
|
|
|
// Symmetric roundtrip
|
|
const testData = encoder.encode('Hello, encrypted world!')
|
|
const testKey = generateSymmetricKey()
|
|
const testEncrypted = await aesEncrypt(testKey, testData)
|
|
const testDecrypted = await aesDecrypt(testKey, testEncrypted)
|
|
const symOk = decoder.decode(testDecrypted) === 'Hello, encrypted world!'
|
|
console.log(` Symmetric roundtrip: ${symOk ? 'PASS' : 'FAIL'}`)
|
|
|
|
// ECIES roundtrip
|
|
const kp1 = generateKeyPair()
|
|
const kp2 = generateKeyPair()
|
|
const plaintext = encoder.encode('Secret message for key pair 2')
|
|
const { ciphertext: ct, ephemeralPublicKey: epk } = await encryptForRecipient(
|
|
plaintext, kp2.publicKey, kp1.privateKey
|
|
)
|
|
const decrypted = await decryptFromSender(ct, epk, kp2.privateKey)
|
|
const eciesOk = decoder.decode(decrypted) === 'Secret message for key pair 2'
|
|
console.log(` ECIES roundtrip: ${eciesOk ? 'PASS' : 'FAIL'}`)
|
|
|
|
// Doc key sharing roundtrip
|
|
const origKey = generateSymmetricKey()
|
|
const shareResult = await shareDocKey(origKey, kp2.publicKey, kp1.privateKey)
|
|
const recoveredKey = await receiveDocKey(
|
|
shareResult.encryptedKey, shareResult.ephemeralPublicKey, kp2.privateKey
|
|
)
|
|
const keyOk = origKey.every((b, i) => b === recoveredKey[i])
|
|
console.log(` Doc key sharing roundtrip: ${keyOk ? 'PASS' : 'FAIL'}`)
|
|
|
|
console.log('\nDone.')
|
|
}
|
|
|
|
main().catch(console.error)
|