import { describe, expect, it } from 'vitest' import { x25519 } from '@noble/curves/ed25519' import { deriveSharedSecret, generateEphemeralKeyPair } from '../src/x25519.js' import { E2EError } from '../src/errors.js' import { getWebCrypto } from '../src/crypto-provider.js' import { bytesToHex } from './helpers.js' describe('T5 x25519 ECDH', () => { it('classic agreement: crossed pubkeys derive the same shared secret', async () => { const a = await generateEphemeralKeyPair() const b = await generateEphemeralKeyPair() const ab = await deriveSharedSecret(a.privateKey, b.publicKey) const ba = await deriveSharedSecret(b.privateKey, a.publicKey) expect(ab.length).toBe(32) expect(bytesToHex(ab)).toBe(bytesToHex(ba)) }) it('INV5: private key is a non-extractable CryptoKey (exportKey rejects)', async () => { const a = await generateEphemeralKeyPair() expect(a.privateKey.extractable).toBe(false) await expect(getWebCrypto().exportKey('raw', a.privateKey)).rejects.toBeTruthy() }) it('negative: an invalid peer public key is rejected with a typed error', async () => { const a = await generateEphemeralKeyPair() await expect(deriveSharedSecret(a.privateKey, new Uint8Array(8))).rejects.toBeInstanceOf( E2EError, ) }) it('parity: WebCrypto secret equals @noble/curves x25519 for the same key material', async () => { // Generate a noble scalar, import it into WebCrypto as pkcs8, and check both agree. const noblePriv = x25519.utils.randomPrivateKey() const noblePub = x25519.getPublicKey(noblePriv) const peer = await generateEphemeralKeyPair() const nobleSecret = x25519.getSharedSecret(noblePriv, peer.publicKey).slice(-32) const wcSecret = await deriveSharedSecret(peer.privateKey, noblePub) expect(bytesToHex(wcSecret)).toBe(bytesToHex(nobleSecret)) }) })