From 90935387876fa2a2c5fe2e693f0435734aca1350 Mon Sep 17 00:00:00 2001 From: Muhammad Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Wed, 9 Apr 2025 19:58:53 +0200 Subject: [PATCH 1/9] port shared secret encryption from nucypher-core --- packages/shared/package.json | 2 + packages/shared/src/encryption/errors.ts | 66 ++++ .../shared/src/encryption/shared-secret.ts | 39 ++ .../test/fixtures/shared-secret-vectors.json | 46 +++ .../test/shared-secret-compatibility.test.ts | 255 +++++++++++++ packages/shared/test/shared-secret.test.ts | 178 +++++++++ pnpm-lock.yaml | 338 +++++++----------- 7 files changed, 709 insertions(+), 215 deletions(-) create mode 100644 packages/shared/src/encryption/errors.ts create mode 100644 packages/shared/src/encryption/shared-secret.ts create mode 100644 packages/shared/test/fixtures/shared-secret-vectors.json create mode 100644 packages/shared/test/shared-secret-compatibility.test.ts create mode 100644 packages/shared/test/shared-secret.test.ts diff --git a/packages/shared/package.json b/packages/shared/package.json index c25cf5867..a6283515d 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -42,6 +42,8 @@ "dependencies": { "@ethersproject/abi": "^5.8.0", "@ethersproject/providers": "^5.8.0", + "@noble/ciphers": "^1.2.1", + "@noble/hashes": "^1.7.1", "@nucypher/nucypher-contracts": "^0.23.0", "@nucypher/nucypher-core": "^0.14.5", "axios": "^1.8.4", diff --git a/packages/shared/src/encryption/errors.ts b/packages/shared/src/encryption/errors.ts new file mode 100644 index 000000000..e70123bb0 --- /dev/null +++ b/packages/shared/src/encryption/errors.ts @@ -0,0 +1,66 @@ +/** + * Errors during encryption. + */ +export class EncryptionError extends Error { + readonly code: EncryptionErrorCode; + + constructor(code: EncryptionErrorCode) { + let message: string; + switch (code) { + case EncryptionErrorCode.PlaintextTooLarge: + message = 'Plaintext is too large to encrypt'; + break; + default: + message = 'Unknown encryption error'; + } + super(message); + this.name = 'EncryptionError'; + this.code = code; + } +} + +/** + * Error codes for encryption operations. + */ +export enum EncryptionErrorCode { + PlaintextTooLarge = 'PlaintextTooLarge', +} + +/** + * Errors during decryption. + */ +export class DecryptionError extends Error { + readonly code: DecryptionErrorCode; + readonly details?: string; + + constructor(code: DecryptionErrorCode, details?: string) { + let message: string; + switch (code) { + case DecryptionErrorCode.CiphertextTooShort: + message = 'The ciphertext must include the nonce'; + break; + case DecryptionErrorCode.AuthenticationFailed: + message = 'Decryption of ciphertext failed: ' + + 'either someone tampered with the ciphertext or ' + + 'you are using an incorrect decryption key.'; + break; + case DecryptionErrorCode.DeserializationFailed: + message = details ? `deserialization failed: ${details}` : 'deserialization failed'; + break; + default: + message = 'Unknown decryption error'; + } + super(message); + this.name = 'DecryptionError'; + this.code = code; + } +} + +/** + * Error codes for decryption operations. + */ +export enum DecryptionErrorCode { + CiphertextTooShort = 'CiphertextTooShort', + AuthenticationFailed = 'AuthenticationFailed', + DeserializationFailed = 'DeserializationFailed', +} diff --git a/packages/shared/src/encryption/shared-secret.ts b/packages/shared/src/encryption/shared-secret.ts new file mode 100644 index 000000000..7100dab53 --- /dev/null +++ b/packages/shared/src/encryption/shared-secret.ts @@ -0,0 +1,39 @@ +import { chacha20poly1305 } from '@noble/ciphers/chacha'; +import { randomBytes } from '@noble/hashes/utils'; + +import { DecryptionError, DecryptionErrorCode, EncryptionError, EncryptionErrorCode } from './errors'; + +export function encryptWithSharedSecret( + sharedSecret: Uint8Array, + plaintext: Uint8Array, +): Uint8Array { + const nonce = randomBytes(12); // Generate a 12-byte nonce + const cipher = chacha20poly1305(sharedSecret, nonce); // Use an object with key + try { + const ciphertext = cipher.encrypt(plaintext); + const result = new Uint8Array(nonce.length + ciphertext.length); + result.set(nonce); + result.set(ciphertext, nonce.length); + return result; + } catch (error) { + throw new EncryptionError(EncryptionErrorCode.PlaintextTooLarge); + } +} + +export function decryptWithSharedSecret( + sharedSecret: Uint8Array, + ciphertext: Uint8Array, +): Uint8Array { + const nonceLength = 12; // ChaCha20Poly1305 uses a 12-byte nonce + if (ciphertext.length < nonceLength) { + throw new DecryptionError(DecryptionErrorCode.CiphertextTooShort); + } + const nonce = ciphertext.slice(0, nonceLength); + const encryptedData = ciphertext.slice(nonceLength); + const cipher = chacha20poly1305(sharedSecret, nonce); // Use an object with key + try { + return cipher.decrypt(encryptedData); + } catch (error) { + throw new DecryptionError(DecryptionErrorCode.AuthenticationFailed); + } +} diff --git a/packages/shared/test/fixtures/shared-secret-vectors.json b/packages/shared/test/fixtures/shared-secret-vectors.json new file mode 100644 index 000000000..45bd1676d --- /dev/null +++ b/packages/shared/test/fixtures/shared-secret-vectors.json @@ -0,0 +1,46 @@ +{ + "testVectors": [ + { + "id": "vector1", + "description": "Basic encryption/decryption compatibility", + "sharedSecret": [ + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31 + ], + "plaintext": "This is a fixed test message", + "fixedNonce": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + "expectedCiphertext": "0102030405060708090a0b0c30e02d19293bd79329c9232eacc4e82eca99e0ea66e4da3808bccbacb3fd2ccbce94fe0c26cbbb81be779817" + }, + { + "id": "vector2", + "description": "Empty plaintext compatibility", + "sharedSecret": [ + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63 + ], + "plaintext": "", + "fixedNonce": [16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192], + "expectedCiphertext": "102030405060708090a0b0c0d9abc79645718dc328d5c3faa129fced" + }, + { + "id": "vector3", + "description": "Placeholder for Rust-generated ciphertext", + "sharedSecret": [ + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31 + ], + "rustGeneratedCiphertext": [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, + 218, 219, 220, 221, 222, 223, 224, 225, 226, 227 + ], + "expectedPlaintext": "Replace with actual plaintext from Rust" + } + ] +} diff --git a/packages/shared/test/shared-secret-compatibility.test.ts b/packages/shared/test/shared-secret-compatibility.test.ts new file mode 100644 index 000000000..dd61ae2a4 --- /dev/null +++ b/packages/shared/test/shared-secret-compatibility.test.ts @@ -0,0 +1,255 @@ +import { describe, expect, it } from 'vitest'; +import { DecryptionError, DecryptionErrorCode } from '../src/encryption/errors'; +import { + decryptWithSharedSecret, + encryptWithSharedSecret, +} from '../src/encryption/shared-secret'; +import testVectorsFile from './fixtures/shared-secret-vectors.json'; + +/** + * This test file contains fixed test vectors to verify compatibility + * between the TypeScript implementation and the original Rust implementation. + * + * Test vectors are loaded from a JSON file which makes it easier to: + * 1. Share vectors between different implementations (Rust, TypeScript) + * 2. Update or add vectors without changing test logic + * 3. Maintain expected inputs/outputs separately from test code + */ +describe('Shared Secret Compatibility Tests', () => { + // Parse test vectors from the loaded JSON file + const { testVectors } = testVectorsFile as { testVectors: any[] }; + + // Helper to convert test vector from JSON to usable objects + const prepareTestVector = (vector: any) => { + return { + id: vector.id, + description: vector.description, + sharedSecret: new Uint8Array(vector.sharedSecret), + plaintext: vector.plaintext + ? new TextEncoder().encode(vector.plaintext) + : new Uint8Array(0), + fixedNonce: new Uint8Array(vector.fixedNonce), + expectedCiphertext: vector.expectedCiphertext + ? Buffer.from(vector.expectedCiphertext, 'hex') + : null, + rustGeneratedCiphertext: vector.rustGeneratedCiphertext + ? new Uint8Array(vector.rustGeneratedCiphertext) + : null, + expectedPlaintext: vector.expectedPlaintext, + }; + }; + + // Parse test vectors into usable formats + const testVector1 = prepareTestVector(testVectors[0]); + const testVector2 = prepareTestVector(testVectors[1]); + const rustCompatVector = prepareTestVector(testVectors[2]); + + // Mock implementation for fixed nonce tests + const setupFixedNonceMock = (fixedNonce: Uint8Array) => { + // Save the original implementation + const originalRandomBytes = globalThis.crypto.getRandomValues; + + // Override with our fixed nonce for deterministic encryption + globalThis.crypto.getRandomValues = (( + array: T, + ): T => { + if (array && array.byteLength === fixedNonce.length) { + // Safe to type assert since we're checking the byteLength matches + const typedArray = array as unknown as Uint8Array; + typedArray.set(fixedNonce); + } + return array; + }) as typeof globalThis.crypto.getRandomValues; + + return () => { + // Restore the original implementation + globalThis.crypto.getRandomValues = originalRandomBytes; + }; + }; + + describe('Round-trip fixed vector tests', () => { + // Testing each vector for round-trip compatibility + it('should correctly encrypt and decrypt test vector 1', () => { + const { sharedSecret, plaintext, fixedNonce, expectedCiphertext } = + testVector1; + + // Setup mock for deterministic output + const cleanupMock = setupFixedNonceMock(fixedNonce); + + try { + // Encrypt with fixed nonce + const ciphertext = encryptWithSharedSecret(sharedSecret, plaintext); + + // Check against expected ciphertext from the JSON file + const ciphertextHex = Buffer.from(ciphertext).toString('hex'); + if (expectedCiphertext) { + const expectedHex = Buffer.from(expectedCiphertext).toString('hex'); + expect(ciphertextHex).toEqual(expectedHex); + console.debug( + `✓ Vector ${testVector1.id} ciphertext matches expected output`, + ); + } else { + console.error( + `Generated ciphertext for vector ${testVector1.id}:`, + ciphertextHex, + ); + console.error( + 'Add this to the test vectors JSON file to enable validation', + ); + } + + // Verify decryption works correctly + const decrypted = decryptWithSharedSecret(sharedSecret, ciphertext); + expect(Buffer.from(decrypted).toString()).toEqual( + Buffer.from(plaintext).toString(), + ); + } finally { + // Always restore the original implementation + cleanupMock(); + } + }); + + it('should correctly encrypt and decrypt test vector 2 (empty plaintext)', () => { + const { sharedSecret, plaintext, fixedNonce, expectedCiphertext } = + testVector2; + + // Setup mock for deterministic output + const cleanupMock = setupFixedNonceMock(fixedNonce); + + try { + // Encrypt with fixed nonce + const ciphertext = encryptWithSharedSecret(sharedSecret, plaintext); + + // Check against expected ciphertext from the JSON file + const ciphertextHex = Buffer.from(ciphertext).toString('hex'); + if (expectedCiphertext) { + const expectedHex = Buffer.from(expectedCiphertext).toString('hex'); + expect(ciphertextHex).toEqual(expectedHex); + console.debug( + `✓ Vector ${testVector2.id} ciphertext matches expected output`, + ); + } else { + console.error( + `Generated ciphertext for vector ${testVector2.id}:`, + ciphertextHex, + ); + throw Error( + 'Add this to the test vectors JSON file to enable validation', + ); + } + + // Verify decryption works correctly + const decrypted = decryptWithSharedSecret(sharedSecret, ciphertext); + expect(decrypted.length).toEqual(plaintext.length); + expect(decrypted.byteLength).toEqual(0); // Empty plaintext + } finally { + // Always restore the original implementation + cleanupMock(); + } + }); + }); + + describe('Cross-implementation compatibility tests', () => { + // Compatibility test for decryption of pre-generated ciphertext from Rust + it('should correctly decrypt ciphertext generated by the Rust implementation', () => { + // Using test vector with Rust-generated ciphertext from the JSON file + const { sharedSecret, rustGeneratedCiphertext, expectedPlaintext } = + rustCompatVector; + + // Skip if no Rust-generated ciphertext data is available + if (!rustGeneratedCiphertext) { + throw Error('No Rust-generated ciphertext provided - skipping test'); + } + + try { + const decrypted = decryptWithSharedSecret( + sharedSecret, + rustGeneratedCiphertext, + ); + const decryptedText = Buffer.from(decrypted).toString(); + + if ( + expectedPlaintext && + expectedPlaintext !== 'Replace with actual plaintext from Rust' + ) { + expect(decryptedText).toEqual(expectedPlaintext); + console.debug('✓ Successfully decrypted Rust-generated ciphertext'); + } else { + console.error('Decrypted Rust-generated ciphertext:', decryptedText); + throw Error( + 'Update the test vector with expected plaintext to enable validation', + ); + } + } catch (error) { + if (rustGeneratedCiphertext.length <= 12) { + throw Error( + 'Ciphertext too short - update with real Rust-generated data', + ); + } else if ( + error instanceof DecryptionError && + error.code === DecryptionErrorCode.AuthenticationFailed + ) { + throw Error( + 'Authentication failed - update with real Rust-generated data', + ); + } else { + throw error; + } + } + }); + }); + + describe('Error handling compatibility tests', () => { + it('should handle ciphertext too short error in the same way as Rust', () => { + const { sharedSecret } = testVector1; + const tooShortCiphertext = new Uint8Array([0x01, 0x02, 0x03]); // Less than nonce length + + try { + decryptWithSharedSecret(sharedSecret, tooShortCiphertext); + expect.fail('Should have thrown an error'); + } catch (error) { + expect(error).toBeInstanceOf(DecryptionError); + expect((error as DecryptionError).code).toBe( + DecryptionErrorCode.CiphertextTooShort, + ); + expect((error as DecryptionError).message).toBe( + 'The ciphertext must include the nonce', + ); + } + }); + + it('should handle authentication failed error in the same way as Rust', () => { + const { sharedSecret, fixedNonce } = testVector1; + + // Setup mock for deterministic output + const cleanupMock = setupFixedNonceMock(fixedNonce); + + try { + // Generate valid ciphertext + const validCiphertext = encryptWithSharedSecret( + sharedSecret, + new TextEncoder().encode('Test message'), + ); + + // Tamper with the ciphertext + validCiphertext[validCiphertext.length - 1] = + (validCiphertext[validCiphertext.length - 1] + 1) % 256; + + // Try to decrypt tampered ciphertext + decryptWithSharedSecret(sharedSecret, validCiphertext); + expect.fail('Should have thrown an error'); + } catch (error) { + expect(error).toBeInstanceOf(DecryptionError); + expect((error as DecryptionError).code).toBe( + DecryptionErrorCode.AuthenticationFailed, + ); + expect((error as DecryptionError).message).toContain( + 'Decryption of ciphertext failed', + ); + } finally { + // Always restore the original implementation + cleanupMock(); + } + }); + }); +}); diff --git a/packages/shared/test/shared-secret.test.ts b/packages/shared/test/shared-secret.test.ts new file mode 100644 index 000000000..2c408c7ec --- /dev/null +++ b/packages/shared/test/shared-secret.test.ts @@ -0,0 +1,178 @@ +import { describe, expect, it, beforeEach, vi } from 'vitest'; +import { encryptWithSharedSecret, decryptWithSharedSecret } from '../src/encryption/shared-secret'; +import { DecryptionError, DecryptionErrorCode, EncryptionError, EncryptionErrorCode } from '../src/encryption/errors'; + +// Mock the noble/hashes/utils randomBytes function +vi.mock('@noble/hashes/utils', () => { + // Counter to ensure each call generates different values + let counter = 0; + + return { + randomBytes: (size: number) => { + const bytes = new Uint8Array(size); + // Fill with semi-random pattern based on counter + for (let i = 0; i < size; i++) { + bytes[i] = (i + counter) % 256; + } + counter++; // Increment counter for next call + return bytes; + } + }; +}); + +describe('Shared Secret Encryption/Decryption', () => { + let sharedSecret: Uint8Array; + let plaintext: Uint8Array; + + beforeEach(() => { + // Create a deterministic shared secret for testing + sharedSecret = new Uint8Array(32); // ChaCha20Poly1305 uses 32-byte keys + for (let i = 0; i < 32; i++) { + sharedSecret[i] = i; + } + + // Create a simple plaintext for testing + plaintext = new TextEncoder().encode('This is a test message for encryption and decryption.'); + }); + + describe('encryptWithSharedSecret', () => { + it('should encrypt plaintext successfully', () => { + const encrypted = encryptWithSharedSecret(sharedSecret, plaintext); + + // Ensure the encrypted result is longer than the plaintext (nonce + ciphertext + tag) + expect(encrypted.length).toBeGreaterThan(plaintext.length); + + // Ensure the encrypted result is different from the plaintext + expect(Buffer.from(encrypted).toString('hex')).not.toEqual(Buffer.from(plaintext).toString('hex')); + + // Verify the nonce is included in the result (first 12 bytes) + expect(encrypted.length).toBeGreaterThanOrEqual(12); + }); + + it('should produce different ciphertexts for the same plaintext due to random nonce', () => { + const encrypted1 = encryptWithSharedSecret(sharedSecret, plaintext); + const encrypted2 = encryptWithSharedSecret(sharedSecret, plaintext); + + // The ciphertexts should be different due to random nonces + expect(Buffer.from(encrypted1).toString('hex')).not.toEqual(Buffer.from(encrypted2).toString('hex')); + }); + + it('should handle empty plaintext', () => { + const emptyPlaintext = new Uint8Array(0); + const encrypted = encryptWithSharedSecret(sharedSecret, emptyPlaintext); + + // Result should at least contain the nonce (12 bytes) + authentication tag + expect(encrypted.length).toBeGreaterThanOrEqual(12); + }); + + it('should throw EncryptionError on encryption failure', () => { + // Mock a scenario that would cause encryption to fail + // For this test, we'll use an invalid shared secret (too short) + const invalidSecret = new Uint8Array(16); // Too short for ChaCha20Poly1305 + + expect(() => encryptWithSharedSecret(invalidSecret, plaintext)).toThrow(EncryptionError); + try { + encryptWithSharedSecret(invalidSecret, plaintext); + } catch (error) { + expect(error).toBeInstanceOf(EncryptionError); + expect((error as EncryptionError).code).toBe(EncryptionErrorCode.PlaintextTooLarge); + expect((error as EncryptionError).message).toBe('Plaintext is too large to encrypt'); + } + }); + }); + + describe('decryptWithSharedSecret', () => { + it('should decrypt ciphertext back to the original plaintext', () => { + const encrypted = encryptWithSharedSecret(sharedSecret, plaintext); + const decrypted = decryptWithSharedSecret(sharedSecret, encrypted); + + // The decrypted result should match the original plaintext + expect(Buffer.from(decrypted).toString()).toEqual(Buffer.from(plaintext).toString()); + }); + + it('should handle empty ciphertext appropriately', () => { + const emptyCiphertext = new Uint8Array(0); + + // Should throw because ciphertext is too short (doesn't contain nonce) + expect(() => decryptWithSharedSecret(sharedSecret, emptyCiphertext)).toThrow(DecryptionError); + }); + + it('should throw DecryptionError when ciphertext is too short', () => { + const shortCiphertext = new Uint8Array(8); // Less than the nonce length (12) + + expect(() => decryptWithSharedSecret(sharedSecret, shortCiphertext)).toThrow(DecryptionError); + try { + decryptWithSharedSecret(sharedSecret, shortCiphertext); + } catch (error) { + expect(error).toBeInstanceOf(DecryptionError); + expect((error as DecryptionError).code).toBe(DecryptionErrorCode.CiphertextTooShort); + expect((error as DecryptionError).message).toBe('The ciphertext must include the nonce'); + } + }); + + it('should throw DecryptionError on authentication failure', () => { + // Encrypt the plaintext + const encrypted = encryptWithSharedSecret(sharedSecret, plaintext); + + // Tamper with the ciphertext portion + encrypted[20] = (encrypted[20] + 1) % 256; // Modify a byte + + // Decryption should fail due to authentication failure + expect(() => decryptWithSharedSecret(sharedSecret, encrypted)).toThrow(DecryptionError); + try { + decryptWithSharedSecret(sharedSecret, encrypted); + } catch (error) { + expect(error).toBeInstanceOf(DecryptionError); + expect((error as DecryptionError).code).toBe(DecryptionErrorCode.AuthenticationFailed); + // Check that it contains the expected error message + expect((error as DecryptionError).message).toContain('Decryption of ciphertext failed'); + expect((error as DecryptionError).message).toContain('either someone tampered with the ciphertext or'); + expect((error as DecryptionError).message).toContain('you are using an incorrect decryption key'); + } + }); + + it('should throw DecryptionError when using wrong shared secret', () => { + // Encrypt with the correct shared secret + const encrypted = encryptWithSharedSecret(sharedSecret, plaintext); + + // Create a different shared secret + const wrongSecret = new Uint8Array(32); + for (let i = 0; i < 32; i++) { + wrongSecret[i] = 31 - i; + } + + // Decryption should fail with the wrong shared secret + expect(() => decryptWithSharedSecret(wrongSecret, encrypted)).toThrow(DecryptionError); + try { + decryptWithSharedSecret(wrongSecret, encrypted); + } catch (error) { + expect(error).toBeInstanceOf(DecryptionError); + expect((error as DecryptionError).code).toBe(DecryptionErrorCode.AuthenticationFailed); + } + }); + }); + + describe('Integration of encryption and decryption', () => { + it('should successfully roundtrip encryption/decryption with various data sizes', () => { + // Test with different plaintext sizes + const testSizes = [0, 1, 16, 64, 1024, 8192]; + + for (const size of testSizes) { + const testData = new Uint8Array(size); + // Fill with some deterministic pattern + for (let i = 0; i < size; i++) { + testData[i] = i % 256; + } + + const encrypted = encryptWithSharedSecret(sharedSecret, testData); + const decrypted = decryptWithSharedSecret(sharedSecret, encrypted); + + // Verify the decrypted data matches the original + expect(decrypted.length).toEqual(testData.length); + for (let i = 0; i < size; i++) { + expect(decrypted[i]).toEqual(testData[i]); + } + } + }); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca6912dd0..febf2dd3d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,7 +39,7 @@ importers: version: 6.21.0(eslint@8.57.1)(typescript@5.8.2) '@vitest/coverage-v8': specifier: ^3.0.9 - version: 3.1.1(vitest@3.0.9(@types/node@20.17.28)(jiti@2.4.2)(jsdom@16.7.0)(terser@5.39.0)(yaml@2.7.0)) + version: 3.1.1(vitest@3.0.9(@types/node@20.17.28)(jiti@2.4.2)(jsdom@16.7.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(terser@5.39.0)(yaml@2.7.0)) bundlemon: specifier: ^2.1.0 version: 2.1.0 @@ -105,7 +105,7 @@ importers: version: 5.8.2 vitest: specifier: ^3.0.9 - version: 3.0.9(@types/node@20.17.28)(jiti@2.4.2)(jsdom@16.7.0)(terser@5.39.0)(yaml@2.7.0) + version: 3.0.9(@types/node@20.17.28)(jiti@2.4.2)(jsdom@16.7.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(terser@5.39.0)(yaml@2.7.0) demos/taco-demo: dependencies: @@ -129,7 +129,7 @@ importers: version: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) file-loader: specifier: ^6.2.0 - version: 6.2.0(webpack@5.98.0(webpack-cli@5.1.4)) + version: 6.2.0(webpack@5.98.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -145,7 +145,7 @@ importers: devDependencies: '@pmmmwh/react-refresh-webpack-plugin': specifier: ^0.5.15 - version: 0.5.15(react-refresh@0.14.2)(type-fest@0.21.3)(webpack-dev-server@4.15.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)(webpack-cli@5.1.4)(webpack@5.98.0))(webpack@5.98.0(webpack-cli@5.1.4)) + version: 0.5.15(react-refresh@0.14.2)(type-fest@0.21.3)(webpack-dev-server@4.15.2)(webpack@5.98.0) '@types/react': specifier: ^18.3.20 version: 18.3.20 @@ -157,16 +157,16 @@ importers: version: 18.3.5(@types/react@18.3.20) copy-webpack-plugin: specifier: ^12.0.2 - version: 12.0.2(webpack@5.98.0(webpack-cli@5.1.4)) + version: 12.0.2(webpack@5.98.0) crypto-browserify: specifier: ^3.12.1 version: 3.12.1 esbuild-loader: specifier: ^2.21.0 - version: 2.21.0(webpack@5.98.0(webpack-cli@5.1.4)) + version: 2.21.0(webpack@5.98.0) html-webpack-plugin: specifier: ^5.6.3 - version: 5.6.3(webpack@5.98.0(webpack-cli@5.1.4)) + version: 5.6.3(webpack@5.98.0) process: specifier: ^0.11.10 version: 0.11.10 @@ -214,7 +214,7 @@ importers: version: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) file-loader: specifier: ^6.2.0 - version: 6.2.0(webpack@5.98.0(webpack-cli@5.1.4)) + version: 6.2.0(webpack@5.98.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -230,7 +230,7 @@ importers: devDependencies: '@pmmmwh/react-refresh-webpack-plugin': specifier: ^0.5.15 - version: 0.5.15(react-refresh@0.14.2)(type-fest@0.21.3)(webpack-dev-server@4.15.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)(webpack-cli@5.1.4)(webpack@5.98.0))(webpack@5.98.0(webpack-cli@5.1.4)) + version: 0.5.15(react-refresh@0.14.2)(type-fest@0.21.3)(webpack-dev-server@4.15.2)(webpack@5.98.0) '@types/react': specifier: ^18.3.20 version: 18.3.20 @@ -242,13 +242,13 @@ importers: version: 18.3.5(@types/react@18.3.20) copy-webpack-plugin: specifier: ^12.0.2 - version: 12.0.2(webpack@5.98.0(webpack-cli@5.1.4)) + version: 12.0.2(webpack@5.98.0) esbuild-loader: specifier: ^2.21.0 - version: 2.21.0(webpack@5.98.0(webpack-cli@5.1.4)) + version: 2.21.0(webpack@5.98.0) html-webpack-plugin: specifier: ^5.6.3 - version: 5.6.3(webpack@5.98.0(webpack-cli@5.1.4)) + version: 5.6.3(webpack@5.98.0) react-refresh: specifier: ^0.14.2 version: 0.14.2 @@ -352,10 +352,10 @@ importers: devDependencies: copy-webpack-plugin: specifier: ^12.0.2 - version: 12.0.2(webpack@5.98.0(webpack-cli@5.1.4)) + version: 12.0.2(webpack@5.98.0) esbuild-loader: specifier: ^2.21.0 - version: 2.21.0(webpack@5.98.0(webpack-cli@5.1.4)) + version: 2.21.0(webpack@5.98.0) ethers: specifier: ^5.8.0 version: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -474,10 +474,10 @@ importers: devDependencies: copy-webpack-plugin: specifier: ^12.0.2 - version: 12.0.2(webpack@5.98.0(webpack-cli@5.1.4)) + version: 12.0.2(webpack@5.98.0) esbuild-loader: specifier: ^2.21.0 - version: 2.21.0(webpack@5.98.0(webpack-cli@5.1.4)) + version: 2.21.0(webpack@5.98.0) webpack: specifier: ^5.98.0 version: 5.98.0(webpack-cli@5.1.4) @@ -512,6 +512,12 @@ importers: '@ethersproject/providers': specifier: ^5.8.0 version: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@noble/ciphers': + specifier: ^1.2.1 + version: 1.2.1 + '@noble/hashes': + specifier: ^1.7.1 + version: 1.7.1 '@nucypher/nucypher-contracts': specifier: ^0.23.0 version: 0.23.0 @@ -609,7 +615,7 @@ importers: version: link:../shared siwe: specifier: ^3.0.0 - version: 3.0.0(ethers@5.8.0) + version: 3.0.0(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) zod: specifier: ^3.24.2 version: 3.24.2 @@ -637,7 +643,7 @@ importers: version: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) vitest: specifier: ^3.0.9 - version: 3.0.9(@types/node@22.13.14)(jiti@2.4.2)(jsdom@16.7.0)(terser@5.39.0)(yaml@2.7.0) + version: 3.0.9(@types/node@22.13.14)(jiti@2.4.2)(jsdom@16.7.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(terser@5.39.0)(yaml@2.7.0) packages: @@ -823,6 +829,7 @@ packages: '@babel/plugin-proposal-class-properties@7.18.6': resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead. peerDependencies: '@babel/core': ^7.0.0-0 @@ -835,24 +842,28 @@ packages: '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6': resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead. peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-proposal-numeric-separator@7.18.6': resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead. peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-proposal-optional-chaining@7.21.0': resolution: {integrity: sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead. peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-proposal-private-methods@7.18.6': resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead. peerDependencies: '@babel/core': ^7.0.0-0 @@ -865,6 +876,7 @@ packages: '@babel/plugin-proposal-private-property-in-object@7.21.11': resolution: {integrity: sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead. peerDependencies: '@babel/core': ^7.0.0-0 @@ -2102,10 +2114,12 @@ packages: '@humanwhocodes/config-array@0.11.14': resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} @@ -2113,6 +2127,7 @@ packages: '@humanwhocodes/object-schema@2.0.3': resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead '@hutson/parse-repository-url@3.0.2': resolution: {integrity: sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==} @@ -2128,6 +2143,7 @@ packages: '@irys/sdk@0.1.24': resolution: {integrity: sha512-8vpgd4o/B1KaChPDgSBcLjAJapi16t/6wlV0SIPEJefbPdDKVuPyK36N3SlFeMkYexrBdqQOkLomHxBaWs0pLA==} engines: {node: '>=16.10.0'} + deprecated: 'Arweave support is deprecated - We recommend migrating to the Irys datachain: https://migrate-to.irys.xyz/' hasBin: true '@isaacs/cliui@8.0.2': @@ -2357,6 +2373,10 @@ packages: '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} + '@noble/ciphers@1.2.1': + resolution: {integrity: sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==} + engines: {node: ^14.21.3 || >=16} + '@noble/curves@1.8.1': resolution: {integrity: sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==} engines: {node: ^14.21.3 || >=16} @@ -3275,6 +3295,7 @@ packages: abab@2.0.6: resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} + deprecated: Use your platform's native atob() and btoa() methods instead accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} @@ -4580,6 +4601,7 @@ packages: domexception@2.0.1: resolution: {integrity: sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==} engines: {node: '>=8'} + deprecated: Use your platform's native DOMException instead domhandler@4.3.1: resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} @@ -4984,11 +5006,13 @@ packages: eslint@8.57.0: resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true eslint@8.57.1: resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true espree@9.6.1: @@ -5409,9 +5433,11 @@ packages: glob@7.1.7: resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} + deprecated: Glob versions prior to v9 are no longer supported glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported global-directory@4.0.1: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} @@ -5696,6 +5722,7 @@ packages: inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.3: resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} @@ -6392,6 +6419,7 @@ packages: lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. lodash.ismatch@4.4.0: resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==} @@ -7622,6 +7650,10 @@ packages: q@1.5.1: resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + deprecated: |- + You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qs@6.13.0: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} @@ -7637,6 +7669,7 @@ packages: querystring@0.2.1: resolution: {integrity: sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==} engines: {node: '>=0.4.x'} + deprecated: The querystring API is considered Legacy. new code should use the URLSearchParams API instead. querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} @@ -7905,6 +7938,7 @@ packages: rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@5.0.10: @@ -7916,6 +7950,7 @@ packages: rollup-plugin-terser@7.0.2: resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==} + deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser peerDependencies: rollup: ^2.0.0 @@ -8196,6 +8231,7 @@ packages: sourcemap-codec@1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + deprecated: Please use @jridgewell/sourcemap-codec instead spawndamnit@3.0.1: resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} @@ -8233,6 +8269,7 @@ packages: stable@0.1.8: resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} + deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} @@ -8338,6 +8375,7 @@ packages: stringify-package@1.0.1: resolution: {integrity: sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==} + deprecated: This module is not used anymore, and has been replaced by @npmcli/package-json strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} @@ -8435,6 +8473,7 @@ packages: svgo@1.3.2: resolution: {integrity: sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==} engines: {node: '>=4.0.0'} + deprecated: This SVGO version is no longer supported. Upgrade to v2.x.x. hasBin: true svgo@2.8.0: @@ -8992,6 +9031,7 @@ packages: w3c-hr-time@1.0.2: resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==} + deprecated: Use your platform's native performance.now() and performance.timeOrigin. w3c-xmlserializer@2.0.0: resolution: {integrity: sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==} @@ -9177,6 +9217,7 @@ packages: workbox-cacheable-response@6.6.0: resolution: {integrity: sha512-JfhJUSQDwsF1Xv3EV1vWzSsCOZn4mQ38bWEBR3LdvOxSPgB65gAM6cS2CX8rkkKHRgiLrN7Wxoyu+TuH67kHrw==} + deprecated: workbox-background-sync@6.6.0 workbox-core@6.6.0: resolution: {integrity: sha512-GDtFRF7Yg3DD859PMbPAYPeJyg5gJYXuBQAC+wyrWuuXgpfoOrIQIvFRZnQ7+czTIQjIr1DhLEGFzZanAT/3bQ==} @@ -9186,6 +9227,7 @@ packages: workbox-google-analytics@6.6.0: resolution: {integrity: sha512-p4DJa6OldXWd6M9zRl0H6vB9lkrmqYFkRQ2xEiNdBFp9U0LhsGO7hsBscVEyH9H2/3eZZt8c97NB2FD9U2NJ+Q==} + deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained workbox-navigation-preload@6.6.0: resolution: {integrity: sha512-utNEWG+uOfXdaZmvhshrh7KzhDu/1iMHyQOV6Aqup8Mm78D286ugu5k9MFD9SzBT5TcwgwSORVvInaXWbvKz9Q==} @@ -11505,6 +11547,8 @@ snapshots: dependencies: eslint-scope: 5.1.1 + '@noble/ciphers@1.2.1': {} + '@noble/curves@1.8.1': dependencies: '@noble/hashes': 1.7.1 @@ -11585,12 +11629,12 @@ snapshots: react-refresh: 0.11.0 schema-utils: 4.3.0 source-map: 0.7.4 - webpack: 5.98.0 + webpack: 5.98.0(webpack-cli@5.1.4) optionalDependencies: type-fest: 0.21.3 - webpack-dev-server: 4.15.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)(webpack@5.98.0) + webpack-dev-server: 4.15.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)(webpack-cli@5.1.4)(webpack@5.98.0) - '@pmmmwh/react-refresh-webpack-plugin@0.5.15(react-refresh@0.14.2)(type-fest@0.21.3)(webpack-dev-server@4.15.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)(webpack-cli@5.1.4)(webpack@5.98.0))(webpack@5.98.0(webpack-cli@5.1.4))': + '@pmmmwh/react-refresh-webpack-plugin@0.5.15(react-refresh@0.14.2)(type-fest@0.21.3)(webpack-dev-server@4.15.2)(webpack@5.98.0)': dependencies: ansi-html: 0.0.9 core-js-pure: 3.41.0 @@ -12442,7 +12486,7 @@ snapshots: - node-fetch - supports-color - '@vitest/coverage-v8@3.1.1(vitest@3.0.9(@types/node@20.17.28)(jiti@2.4.2)(jsdom@16.7.0)(terser@5.39.0)(yaml@2.7.0))': + '@vitest/coverage-v8@3.1.1(vitest@3.0.9(@types/node@20.17.28)(jiti@2.4.2)(jsdom@16.7.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(terser@5.39.0)(yaml@2.7.0))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -12456,7 +12500,7 @@ snapshots: std-env: 3.8.1 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.0.9(@types/node@20.17.28)(jiti@2.4.2)(jsdom@16.7.0)(terser@5.39.0)(yaml@2.7.0) + vitest: 3.0.9(@types/node@20.17.28)(jiti@2.4.2)(jsdom@16.7.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(terser@5.39.0)(yaml@2.7.0) transitivePeerDependencies: - supports-color @@ -12475,14 +12519,6 @@ snapshots: optionalDependencies: vite: 6.2.3(@types/node@20.17.28)(jiti@2.4.2)(terser@5.39.0)(yaml@2.7.0) - '@vitest/mocker@3.0.9(vite@6.2.3(@types/node@22.13.14)(jiti@2.4.2)(terser@5.39.0)(yaml@2.7.0))': - dependencies: - '@vitest/spy': 3.0.9 - estree-walker: 3.0.3 - magic-string: 0.30.17 - optionalDependencies: - vite: 6.2.3(@types/node@22.13.14)(jiti@2.4.2)(terser@5.39.0)(yaml@2.7.0) - '@vitest/pretty-format@3.0.9': dependencies: tinyrainbow: 2.0.0 @@ -12584,17 +12620,17 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 - '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4(webpack-dev-server@4.15.2)(webpack@5.98.0))(webpack@5.98.0(webpack-cli@5.1.4))': + '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.98.0)': dependencies: webpack: 5.98.0(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@4.15.2)(webpack@5.98.0) - '@webpack-cli/info@2.0.2(webpack-cli@5.1.4(webpack-dev-server@4.15.2)(webpack@5.98.0))(webpack@5.98.0(webpack-cli@5.1.4))': + '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.98.0)': dependencies: webpack: 5.98.0(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@4.15.2)(webpack@5.98.0) - '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4(webpack-dev-server@4.15.2)(webpack@5.98.0))(webpack-dev-server@4.15.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)(webpack-cli@5.1.4)(webpack@5.98.0))(webpack@5.98.0(webpack-cli@5.1.4))': + '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack-dev-server@4.15.2)(webpack@5.98.0)': dependencies: webpack: 5.98.0(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@4.15.2)(webpack@5.98.0) @@ -12967,7 +13003,7 @@ snapshots: loader-utils: 2.0.4 make-dir: 3.1.0 schema-utils: 2.7.1 - webpack: 5.98.0 + webpack: 5.98.0(webpack-cli@5.1.4) babel-plugin-istanbul@6.1.1: dependencies: @@ -13698,7 +13734,7 @@ snapshots: dependencies: toggle-selection: 1.0.6 - copy-webpack-plugin@12.0.2(webpack@5.98.0(webpack-cli@5.1.4)): + copy-webpack-plugin@12.0.2(webpack@5.98.0): dependencies: fast-glob: 3.3.3 glob-parent: 6.0.2 @@ -13838,7 +13874,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.1 optionalDependencies: - webpack: 5.98.0 + webpack: 5.98.0(webpack-cli@5.1.4) css-minimizer-webpack-plugin@3.4.1(webpack@5.98.0): dependencies: @@ -13848,7 +13884,7 @@ snapshots: schema-utils: 4.3.0 serialize-javascript: 6.0.2 source-map: 0.6.1 - webpack: 5.98.0 + webpack: 5.98.0(webpack-cli@5.1.4) css-prefers-color-scheme@6.0.3(postcss@8.5.3): dependencies: @@ -14401,7 +14437,7 @@ snapshots: dependencies: es6-promise: 4.2.8 - esbuild-loader@2.21.0(webpack@5.98.0(webpack-cli@5.1.4)): + esbuild-loader@2.21.0(webpack@5.98.0): dependencies: esbuild: 0.16.17 joycon: 3.1.1 @@ -14524,8 +14560,8 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.2.2) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.0(eslint-plugin-import@2.31.0)(eslint@8.57.0) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-typescript@3.10.0)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.10.0)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.0) eslint-plugin-react: 7.37.4(eslint@8.57.0) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.0) @@ -14543,8 +14579,8 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.8.2) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.0(eslint-plugin-import@2.31.0)(eslint@8.57.0) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.10.0)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.0) eslint-plugin-react: 7.37.4(eslint@8.57.0) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.0) @@ -14599,7 +14635,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0)(eslint@8.57.0): + eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.0))(eslint@8.57.0): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0 @@ -14610,38 +14646,43 @@ snapshots: tinyglobby: 0.2.12 unrs-resolver: 1.3.2 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.10.0)(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1): + eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.0): dependencies: - debug: 3.2.7 + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.0 + eslint: 8.57.0 + get-tsconfig: 4.10.0 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.12 + unrs-resolver: 1.3.2 optionalDependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.8.2) - eslint: 8.57.1 - eslint-import-resolver-node: 0.3.9 + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.2.2) - eslint: 8.57.0 + '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.8.2) + eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.0(eslint-plugin-import@2.31.0)(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): + eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.8.2) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - supports-color @@ -14698,7 +14739,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-typescript@3.10.0)(eslint@8.57.0): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.10.0)(eslint@8.57.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -14709,36 +14750,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0) - hasown: 2.0.2 - is-core-module: 2.16.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.1 - semver: 6.3.1 - string.prototype.trimend: 1.0.9 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.2.2) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - - eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.0): - dependencies: - '@rtsao/scc': 1.1.0 - array-includes: 3.1.8 - array.prototype.findlastindex: 1.2.6 - array.prototype.flat: 1.3.3 - array.prototype.flatmap: 1.3.3 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -14935,7 +14947,7 @@ snapshots: micromatch: 4.0.8 normalize-path: 3.0.0 schema-utils: 4.3.0 - webpack: 5.98.0 + webpack: 5.98.0(webpack-cli@5.1.4) eslint@8.57.0: dependencies: @@ -15239,17 +15251,11 @@ snapshots: dependencies: flat-cache: 3.2.0 - file-loader@6.2.0(webpack@5.98.0(webpack-cli@5.1.4)): - dependencies: - loader-utils: 2.0.4 - schema-utils: 3.3.0 - webpack: 5.98.0(webpack-cli@5.1.4) - file-loader@6.2.0(webpack@5.98.0): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.98.0 + webpack: 5.98.0(webpack-cli@5.1.4) file-uri-to-path@1.0.0: {} @@ -15362,7 +15368,7 @@ snapshots: semver: 7.7.1 tapable: 1.1.3 typescript: 5.8.2 - webpack: 5.98.0 + webpack: 5.98.0(webpack-cli@5.1.4) optionalDependencies: eslint: 8.57.1 @@ -15746,16 +15752,6 @@ snapshots: relateurl: 0.2.7 terser: 5.39.0 - html-webpack-plugin@5.6.3(webpack@5.98.0(webpack-cli@5.1.4)): - dependencies: - '@types/html-minifier-terser': 6.1.0 - html-minifier-terser: 6.1.0 - lodash: 4.17.21 - pretty-error: 4.0.0 - tapable: 2.2.1 - optionalDependencies: - webpack: 5.98.0(webpack-cli@5.1.4) - html-webpack-plugin@5.6.3(webpack@5.98.0): dependencies: '@types/html-minifier-terser': 6.1.0 @@ -15764,7 +15760,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.1 optionalDependencies: - webpack: 5.98.0 + webpack: 5.98.0(webpack-cli@5.1.4) htmlparser2@6.1.0: dependencies: @@ -17024,7 +17020,7 @@ snapshots: dependencies: schema-utils: 4.3.0 tapable: 2.2.1 - webpack: 5.98.0 + webpack: 5.98.0(webpack-cli@5.1.4) minimalistic-assert@1.0.1: {} @@ -17678,7 +17674,7 @@ snapshots: klona: 2.0.6 postcss: 8.5.3 semver: 7.7.1 - webpack: 5.98.0 + webpack: 5.98.0(webpack-cli@5.1.4) postcss-logical@5.0.4(postcss@8.5.3): dependencies: @@ -18120,7 +18116,7 @@ snapshots: shell-quote: 1.8.2 strip-ansi: 6.0.1 text-table: 0.2.0 - webpack: 5.98.0 + webpack: 5.98.0(webpack-cli@5.1.4) optionalDependencies: typescript: 5.8.2 transitivePeerDependencies: @@ -18192,8 +18188,8 @@ snapshots: style-loader: 3.3.4(webpack@5.98.0) tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@20.17.28)(typescript@5.8.2)) terser-webpack-plugin: 5.3.14(webpack@5.98.0) - webpack: 5.98.0 - webpack-dev-server: 4.15.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)(webpack@5.98.0) + webpack: 5.98.0(webpack-cli@5.1.4) + webpack-dev-server: 4.15.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)(webpack-cli@5.1.4)(webpack@5.98.0) webpack-manifest-plugin: 4.1.1(webpack@5.98.0) workbox-webpack-plugin: 6.6.0(@types/babel__core@7.20.5)(webpack@5.98.0) optionalDependencies: @@ -18536,7 +18532,7 @@ snapshots: dependencies: klona: 2.0.6 neo-async: 2.6.2 - webpack: 5.98.0 + webpack: 5.98.0(webpack-cli@5.1.4) sax@1.2.4: {} @@ -18741,7 +18737,7 @@ snapshots: uri-js: 4.4.1 valid-url: 1.0.9 - siwe@3.0.0(ethers@5.8.0): + siwe@3.0.0(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)): dependencies: '@spruceid/siwe-parser': 3.0.0 '@stablelib/random': 1.0.2 @@ -18781,7 +18777,7 @@ snapshots: abab: 2.0.6 iconv-lite: 0.6.3 source-map-js: 1.2.1 - webpack: 5.98.0 + webpack: 5.98.0(webpack-cli@5.1.4) source-map-support@0.5.21: dependencies: @@ -19030,7 +19026,7 @@ snapshots: style-loader@3.3.4(webpack@5.98.0): dependencies: - webpack: 5.98.0 + webpack: 5.98.0(webpack-cli@5.1.4) styled-jsx@5.1.1(react@18.3.1): dependencies: @@ -19158,15 +19154,6 @@ snapshots: ansi-escapes: 4.3.2 supports-hyperlinks: 2.3.0 - terser-webpack-plugin@5.3.14(webpack@5.98.0(webpack-cli@5.1.4)): - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - jest-worker: 27.5.1 - schema-utils: 4.3.0 - serialize-javascript: 6.0.2 - terser: 5.39.0 - webpack: 5.98.0(webpack-cli@5.1.4) - terser-webpack-plugin@5.3.14(webpack@5.98.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 @@ -19174,7 +19161,7 @@ snapshots: schema-utils: 4.3.0 serialize-javascript: 6.0.2 terser: 5.39.0 - webpack: 5.98.0 + webpack: 5.98.0(webpack-cli@5.1.4) terser@5.39.0: dependencies: @@ -19646,7 +19633,7 @@ snapshots: terser: 5.39.0 yaml: 2.7.0 - vitest@3.0.9(@types/node@20.17.28)(jiti@2.4.2)(jsdom@16.7.0)(terser@5.39.0)(yaml@2.7.0): + vitest@3.0.9(@types/node@20.17.28)(jiti@2.4.2)(jsdom@16.7.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(terser@5.39.0)(yaml@2.7.0): dependencies: '@vitest/expect': 3.0.9 '@vitest/mocker': 3.0.9(vite@6.2.3(@types/node@20.17.28)(jiti@2.4.2)(terser@5.39.0)(yaml@2.7.0)) @@ -19685,10 +19672,10 @@ snapshots: - tsx - yaml - vitest@3.0.9(@types/node@22.13.14)(jiti@2.4.2)(jsdom@16.7.0)(terser@5.39.0)(yaml@2.7.0): + vitest@3.0.9(@types/node@22.13.14)(jiti@2.4.2)(jsdom@16.7.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(terser@5.39.0)(yaml@2.7.0): dependencies: '@vitest/expect': 3.0.9 - '@vitest/mocker': 3.0.9(vite@6.2.3(@types/node@22.13.14)(jiti@2.4.2)(terser@5.39.0)(yaml@2.7.0)) + '@vitest/mocker': 3.0.9(vite@6.2.3(@types/node@20.17.28)(jiti@2.4.2)(terser@5.39.0)(yaml@2.7.0)) '@vitest/pretty-format': 3.0.9 '@vitest/runner': 3.0.9 '@vitest/snapshot': 3.0.9 @@ -19768,9 +19755,9 @@ snapshots: webpack-cli@5.1.4(webpack-dev-server@4.15.2)(webpack@5.98.0): dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4(webpack-dev-server@4.15.2)(webpack@5.98.0))(webpack@5.98.0(webpack-cli@5.1.4)) - '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4(webpack-dev-server@4.15.2)(webpack@5.98.0))(webpack@5.98.0(webpack-cli@5.1.4)) - '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4(webpack-dev-server@4.15.2)(webpack@5.98.0))(webpack-dev-server@4.15.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)(webpack-cli@5.1.4)(webpack@5.98.0))(webpack@5.98.0(webpack-cli@5.1.4)) + '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.98.0) + '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.98.0) + '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack-dev-server@4.15.2)(webpack@5.98.0) colorette: 2.0.20 commander: 10.0.1 cross-spawn: 7.0.6 @@ -19784,15 +19771,6 @@ snapshots: optionalDependencies: webpack-dev-server: 4.15.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)(webpack-cli@5.1.4)(webpack@5.98.0) - webpack-dev-middleware@5.3.4(webpack@5.98.0(webpack-cli@5.1.4)): - dependencies: - colorette: 2.0.20 - memfs: 3.5.3 - mime-types: 2.1.35 - range-parser: 1.2.1 - schema-utils: 4.3.0 - webpack: 5.98.0(webpack-cli@5.1.4) - webpack-dev-middleware@5.3.4(webpack@5.98.0): dependencies: colorette: 2.0.20 @@ -19800,7 +19778,7 @@ snapshots: mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 4.3.0 - webpack: 5.98.0 + webpack: 5.98.0(webpack-cli@5.1.4) webpack-dev-server@4.15.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)(webpack-cli@5.1.4)(webpack@5.98.0): dependencies: @@ -19832,7 +19810,7 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 5.3.4(webpack@5.98.0(webpack-cli@5.1.4)) + webpack-dev-middleware: 5.3.4(webpack@5.98.0) ws: 8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) optionalDependencies: webpack: 5.98.0(webpack-cli@5.1.4) @@ -19843,50 +19821,10 @@ snapshots: - supports-color - utf-8-validate - webpack-dev-server@4.15.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)(webpack@5.98.0): - dependencies: - '@types/bonjour': 3.5.13 - '@types/connect-history-api-fallback': 1.5.4 - '@types/express': 4.17.21 - '@types/serve-index': 1.9.4 - '@types/serve-static': 1.15.7 - '@types/sockjs': 0.3.36 - '@types/ws': 8.18.0 - ansi-html-community: 0.0.8 - bonjour-service: 1.3.0 - chokidar: 3.6.0 - colorette: 2.0.20 - compression: 1.8.0 - connect-history-api-fallback: 2.0.0 - default-gateway: 6.0.3 - express: 4.21.2 - graceful-fs: 4.2.11 - html-entities: 2.5.3 - http-proxy-middleware: 2.0.7(@types/express@4.17.21) - ipaddr.js: 2.2.0 - launch-editor: 2.10.0 - open: 8.4.2 - p-retry: 4.6.2 - rimraf: 3.0.2 - schema-utils: 4.3.0 - selfsigned: 2.4.1 - serve-index: 1.9.1 - sockjs: 0.3.24 - spdy: 4.0.2 - webpack-dev-middleware: 5.3.4(webpack@5.98.0) - ws: 8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) - optionalDependencies: - webpack: 5.98.0 - transitivePeerDependencies: - - bufferutil - - debug - - supports-color - - utf-8-validate - webpack-manifest-plugin@4.1.1(webpack@5.98.0): dependencies: tapable: 2.2.1 - webpack: 5.98.0 + webpack: 5.98.0(webpack-cli@5.1.4) webpack-sources: 2.3.1 webpack-merge@5.10.0: @@ -19907,36 +19845,6 @@ snapshots: webpack-sources@3.2.3: {} - webpack@5.98.0: - dependencies: - '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.7 - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/wasm-edit': 1.14.1 - '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.14.1 - browserslist: 4.24.4 - chrome-trace-event: 1.0.4 - enhanced-resolve: 5.18.1 - es-module-lexer: 1.6.0 - eslint-scope: 5.1.1 - events: 3.3.0 - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.0 - mime-types: 2.1.35 - neo-async: 2.6.2 - schema-utils: 4.3.0 - tapable: 2.2.1 - terser-webpack-plugin: 5.3.14(webpack@5.98.0) - watchpack: 2.4.2 - webpack-sources: 3.2.3 - transitivePeerDependencies: - - '@swc/core' - - esbuild - - uglify-js - webpack@5.98.0(webpack-cli@5.1.4): dependencies: '@types/eslint-scope': 3.7.7 @@ -19959,7 +19867,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.14(webpack@5.98.0(webpack-cli@5.1.4)) + terser-webpack-plugin: 5.3.14(webpack@5.98.0) watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: @@ -20186,7 +20094,7 @@ snapshots: fast-json-stable-stringify: 2.1.0 pretty-bytes: 5.6.0 upath: 1.2.0 - webpack: 5.98.0 + webpack: 5.98.0(webpack-cli@5.1.4) webpack-sources: 1.4.3 workbox-build: 6.6.0(@types/babel__core@7.20.5) transitivePeerDependencies: From dfb7647feefa531192305712574584791625fad2 Mon Sep 17 00:00:00 2001 From: Muhammad Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Wed, 9 Apr 2025 22:13:27 +0200 Subject: [PATCH 2/9] generate the shared secret test vectors from rust --- .../test/fixtures/shared-secret-vectors.json | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/shared/test/fixtures/shared-secret-vectors.json b/packages/shared/test/fixtures/shared-secret-vectors.json index 45bd1676d..ab877eeb0 100644 --- a/packages/shared/test/fixtures/shared-secret-vectors.json +++ b/packages/shared/test/fixtures/shared-secret-vectors.json @@ -28,7 +28,7 @@ }, { "id": "vector3", - "description": "Placeholder for Rust-generated ciphertext", + "description": "Rust-generated ciphertext for TypeScript compatibility check", "sharedSecret": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, @@ -36,11 +36,15 @@ 24, 25, 26, 27, 28, 29, 30, 31 ], "rustGeneratedCiphertext": [ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, - 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, - 218, 219, 220, 221, 222, 223, 224, 225, 226, 227 + 208, 248, 54, 195, 24, 29, 116, 93, 142, 5, 226, 81, + 0, 180, 190, 151, 127, 85, 8, 10, 113, 26, 107, 253, + 228, 24, 215, 116, 87, 194, 20, 220, 191, 92, 22, 154, + 247, 107, 224, 128, 190, 203, 88, 200, 15, 230, 248, 47, + 103, 100, 14, 220, 226, 62, 189, 57, 96, 83, 146, 94, + 64, 59, 182, 188, 175, 251, 87, 220, 67, 136, 94, 250, + 168, 9, 186, 82, 101, 138, 22, 13, 99, 150 ], - "expectedPlaintext": "Replace with actual plaintext from Rust" + "expectedPlaintext": "This is a message encrypted by the Rust implementation" } ] } From acc7bd3ee8a50419aabd357e6cb77f448f41326f Mon Sep 17 00:00:00 2001 From: Muhammad Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Thu, 10 Apr 2025 14:19:00 +0200 Subject: [PATCH 3/9] refactor shared secret compatibility tests mock to work in the CI + prettifying 2 other files --- .../shared/src/encryption/shared-secret.ts | 7 +- .../test/shared-secret-compatibility.test.ts | 62 +++++--- packages/shared/test/shared-secret.test.ts | 145 ++++++++++++------ 3 files changed, 139 insertions(+), 75 deletions(-) diff --git a/packages/shared/src/encryption/shared-secret.ts b/packages/shared/src/encryption/shared-secret.ts index 7100dab53..1ac2a5189 100644 --- a/packages/shared/src/encryption/shared-secret.ts +++ b/packages/shared/src/encryption/shared-secret.ts @@ -1,7 +1,12 @@ import { chacha20poly1305 } from '@noble/ciphers/chacha'; import { randomBytes } from '@noble/hashes/utils'; -import { DecryptionError, DecryptionErrorCode, EncryptionError, EncryptionErrorCode } from './errors'; +import { + DecryptionError, + DecryptionErrorCode, + EncryptionError, + EncryptionErrorCode, +} from './errors'; export function encryptWithSharedSecret( sharedSecret: Uint8Array, diff --git a/packages/shared/test/shared-secret-compatibility.test.ts b/packages/shared/test/shared-secret-compatibility.test.ts index dd61ae2a4..1400c7022 100644 --- a/packages/shared/test/shared-secret-compatibility.test.ts +++ b/packages/shared/test/shared-secret-compatibility.test.ts @@ -1,10 +1,24 @@ -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { DecryptionError, DecryptionErrorCode } from '../src/encryption/errors'; +import testVectorsFile from './fixtures/shared-secret-vectors.json'; + +// Mock the randomBytes function from @noble/hashes/utils +// This must be done before importing any modules that use it +vi.mock('@noble/hashes/utils', () => { + return { + // Return a function that will be replaced in each test + randomBytes: vi.fn().mockImplementation((bytesLength?: number) => { + return new Uint8Array(bytesLength || 32); + }), + }; +}); + +// Now import modules that depend on the mocked randomBytes +import { randomBytes } from '@noble/hashes/utils'; import { decryptWithSharedSecret, encryptWithSharedSecret, } from '../src/encryption/shared-secret'; -import testVectorsFile from './fixtures/shared-secret-vectors.json'; /** * This test file contains fixed test vectors to verify compatibility @@ -44,28 +58,30 @@ describe('Shared Secret Compatibility Tests', () => { const testVector2 = prepareTestVector(testVectors[1]); const rustCompatVector = prepareTestVector(testVectors[2]); - // Mock implementation for fixed nonce tests - const setupFixedNonceMock = (fixedNonce: Uint8Array) => { - // Save the original implementation - const originalRandomBytes = globalThis.crypto.getRandomValues; - - // Override with our fixed nonce for deterministic encryption - globalThis.crypto.getRandomValues = (( - array: T, - ): T => { - if (array && array.byteLength === fixedNonce.length) { - // Safe to type assert since we're checking the byteLength matches - const typedArray = array as unknown as Uint8Array; - typedArray.set(fixedNonce); + // Set up a fixed nonce for testing + function setupFixedNonceMock(fixedNonce: Uint8Array) { + // Get the mocked function + const mockedRandomBytes = vi.mocked(randomBytes); + + // Clear previous implementations + mockedRandomBytes.mockReset(); + + // Configure it to return our fixed nonce + mockedRandomBytes.mockImplementation((bytesLength?: number) => { + // If length is undefined or matches our nonce length, return the fixed nonce + // Otherwise return a new array of the requested length + if (bytesLength === undefined || bytesLength === fixedNonce.length) { + return fixedNonce; + } else { + return new Uint8Array(bytesLength); } - return array; - }) as typeof globalThis.crypto.getRandomValues; + }); + // Return a cleanup function return () => { - // Restore the original implementation - globalThis.crypto.getRandomValues = originalRandomBytes; + mockedRandomBytes.mockReset(); }; - }; + } describe('Round-trip fixed vector tests', () => { // Testing each vector for round-trip compatibility @@ -73,7 +89,7 @@ describe('Shared Secret Compatibility Tests', () => { const { sharedSecret, plaintext, fixedNonce, expectedCiphertext } = testVector1; - // Setup mock for deterministic output + // Set up our mock to return the fixed nonce const cleanupMock = setupFixedNonceMock(fixedNonce); try { @@ -113,7 +129,7 @@ describe('Shared Secret Compatibility Tests', () => { const { sharedSecret, plaintext, fixedNonce, expectedCiphertext } = testVector2; - // Setup mock for deterministic output + // Set up our mock to return the fixed nonce const cleanupMock = setupFixedNonceMock(fixedNonce); try { @@ -221,7 +237,7 @@ describe('Shared Secret Compatibility Tests', () => { it('should handle authentication failed error in the same way as Rust', () => { const { sharedSecret, fixedNonce } = testVector1; - // Setup mock for deterministic output + // Set up our mock to return the fixed nonce const cleanupMock = setupFixedNonceMock(fixedNonce); try { diff --git a/packages/shared/test/shared-secret.test.ts b/packages/shared/test/shared-secret.test.ts index 2c408c7ec..5bb6c1642 100644 --- a/packages/shared/test/shared-secret.test.ts +++ b/packages/shared/test/shared-secret.test.ts @@ -1,12 +1,19 @@ -import { describe, expect, it, beforeEach, vi } from 'vitest'; -import { encryptWithSharedSecret, decryptWithSharedSecret } from '../src/encryption/shared-secret'; -import { DecryptionError, DecryptionErrorCode, EncryptionError, EncryptionErrorCode } from '../src/encryption/errors'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { + DecryptionError, + DecryptionErrorCode, + EncryptionError, + EncryptionErrorCode, +} from '../src/encryption/errors'; +import { + decryptWithSharedSecret, + encryptWithSharedSecret, +} from '../src/encryption/shared-secret'; // Mock the noble/hashes/utils randomBytes function vi.mock('@noble/hashes/utils', () => { // Counter to ensure each call generates different values let counter = 0; - return { randomBytes: (size: number) => { const bytes = new Uint8Array(size); @@ -16,157 +23,193 @@ vi.mock('@noble/hashes/utils', () => { } counter++; // Increment counter for next call return bytes; - } + }, }; }); describe('Shared Secret Encryption/Decryption', () => { let sharedSecret: Uint8Array; let plaintext: Uint8Array; - + beforeEach(() => { // Create a deterministic shared secret for testing sharedSecret = new Uint8Array(32); // ChaCha20Poly1305 uses 32-byte keys for (let i = 0; i < 32; i++) { sharedSecret[i] = i; } - + // Create a simple plaintext for testing - plaintext = new TextEncoder().encode('This is a test message for encryption and decryption.'); + plaintext = new TextEncoder().encode( + 'This is a test message for encryption and decryption.', + ); }); - + describe('encryptWithSharedSecret', () => { it('should encrypt plaintext successfully', () => { const encrypted = encryptWithSharedSecret(sharedSecret, plaintext); - + // Ensure the encrypted result is longer than the plaintext (nonce + ciphertext + tag) expect(encrypted.length).toBeGreaterThan(plaintext.length); - + // Ensure the encrypted result is different from the plaintext - expect(Buffer.from(encrypted).toString('hex')).not.toEqual(Buffer.from(plaintext).toString('hex')); - + expect(Buffer.from(encrypted).toString('hex')).not.toEqual( + Buffer.from(plaintext).toString('hex'), + ); + // Verify the nonce is included in the result (first 12 bytes) expect(encrypted.length).toBeGreaterThanOrEqual(12); }); - + it('should produce different ciphertexts for the same plaintext due to random nonce', () => { const encrypted1 = encryptWithSharedSecret(sharedSecret, plaintext); const encrypted2 = encryptWithSharedSecret(sharedSecret, plaintext); - + // The ciphertexts should be different due to random nonces - expect(Buffer.from(encrypted1).toString('hex')).not.toEqual(Buffer.from(encrypted2).toString('hex')); + expect(Buffer.from(encrypted1).toString('hex')).not.toEqual( + Buffer.from(encrypted2).toString('hex'), + ); }); - + it('should handle empty plaintext', () => { const emptyPlaintext = new Uint8Array(0); const encrypted = encryptWithSharedSecret(sharedSecret, emptyPlaintext); - + // Result should at least contain the nonce (12 bytes) + authentication tag expect(encrypted.length).toBeGreaterThanOrEqual(12); }); - + it('should throw EncryptionError on encryption failure', () => { // Mock a scenario that would cause encryption to fail // For this test, we'll use an invalid shared secret (too short) const invalidSecret = new Uint8Array(16); // Too short for ChaCha20Poly1305 - - expect(() => encryptWithSharedSecret(invalidSecret, plaintext)).toThrow(EncryptionError); + + expect(() => encryptWithSharedSecret(invalidSecret, plaintext)).toThrow( + EncryptionError, + ); try { encryptWithSharedSecret(invalidSecret, plaintext); } catch (error) { expect(error).toBeInstanceOf(EncryptionError); - expect((error as EncryptionError).code).toBe(EncryptionErrorCode.PlaintextTooLarge); - expect((error as EncryptionError).message).toBe('Plaintext is too large to encrypt'); + expect((error as EncryptionError).code).toBe( + EncryptionErrorCode.PlaintextTooLarge, + ); + expect((error as EncryptionError).message).toBe( + 'Plaintext is too large to encrypt', + ); } }); }); - + describe('decryptWithSharedSecret', () => { it('should decrypt ciphertext back to the original plaintext', () => { const encrypted = encryptWithSharedSecret(sharedSecret, plaintext); const decrypted = decryptWithSharedSecret(sharedSecret, encrypted); - + // The decrypted result should match the original plaintext - expect(Buffer.from(decrypted).toString()).toEqual(Buffer.from(plaintext).toString()); + expect(Buffer.from(decrypted).toString()).toEqual( + Buffer.from(plaintext).toString(), + ); }); - + it('should handle empty ciphertext appropriately', () => { const emptyCiphertext = new Uint8Array(0); - + // Should throw because ciphertext is too short (doesn't contain nonce) - expect(() => decryptWithSharedSecret(sharedSecret, emptyCiphertext)).toThrow(DecryptionError); + expect(() => + decryptWithSharedSecret(sharedSecret, emptyCiphertext), + ).toThrow(DecryptionError); }); - + it('should throw DecryptionError when ciphertext is too short', () => { const shortCiphertext = new Uint8Array(8); // Less than the nonce length (12) - - expect(() => decryptWithSharedSecret(sharedSecret, shortCiphertext)).toThrow(DecryptionError); + + expect(() => + decryptWithSharedSecret(sharedSecret, shortCiphertext), + ).toThrow(DecryptionError); try { decryptWithSharedSecret(sharedSecret, shortCiphertext); } catch (error) { expect(error).toBeInstanceOf(DecryptionError); - expect((error as DecryptionError).code).toBe(DecryptionErrorCode.CiphertextTooShort); - expect((error as DecryptionError).message).toBe('The ciphertext must include the nonce'); + expect((error as DecryptionError).code).toBe( + DecryptionErrorCode.CiphertextTooShort, + ); + expect((error as DecryptionError).message).toBe( + 'The ciphertext must include the nonce', + ); } }); - + it('should throw DecryptionError on authentication failure', () => { // Encrypt the plaintext const encrypted = encryptWithSharedSecret(sharedSecret, plaintext); - + // Tamper with the ciphertext portion encrypted[20] = (encrypted[20] + 1) % 256; // Modify a byte - + // Decryption should fail due to authentication failure - expect(() => decryptWithSharedSecret(sharedSecret, encrypted)).toThrow(DecryptionError); + expect(() => decryptWithSharedSecret(sharedSecret, encrypted)).toThrow( + DecryptionError, + ); try { decryptWithSharedSecret(sharedSecret, encrypted); } catch (error) { expect(error).toBeInstanceOf(DecryptionError); - expect((error as DecryptionError).code).toBe(DecryptionErrorCode.AuthenticationFailed); + expect((error as DecryptionError).code).toBe( + DecryptionErrorCode.AuthenticationFailed, + ); // Check that it contains the expected error message - expect((error as DecryptionError).message).toContain('Decryption of ciphertext failed'); - expect((error as DecryptionError).message).toContain('either someone tampered with the ciphertext or'); - expect((error as DecryptionError).message).toContain('you are using an incorrect decryption key'); + expect((error as DecryptionError).message).toContain( + 'Decryption of ciphertext failed', + ); + expect((error as DecryptionError).message).toContain( + 'either someone tampered with the ciphertext or', + ); + expect((error as DecryptionError).message).toContain( + 'you are using an incorrect decryption key', + ); } }); - + it('should throw DecryptionError when using wrong shared secret', () => { // Encrypt with the correct shared secret const encrypted = encryptWithSharedSecret(sharedSecret, plaintext); - + // Create a different shared secret const wrongSecret = new Uint8Array(32); for (let i = 0; i < 32; i++) { wrongSecret[i] = 31 - i; } - + // Decryption should fail with the wrong shared secret - expect(() => decryptWithSharedSecret(wrongSecret, encrypted)).toThrow(DecryptionError); + expect(() => decryptWithSharedSecret(wrongSecret, encrypted)).toThrow( + DecryptionError, + ); try { decryptWithSharedSecret(wrongSecret, encrypted); } catch (error) { expect(error).toBeInstanceOf(DecryptionError); - expect((error as DecryptionError).code).toBe(DecryptionErrorCode.AuthenticationFailed); + expect((error as DecryptionError).code).toBe( + DecryptionErrorCode.AuthenticationFailed, + ); } }); }); - + describe('Integration of encryption and decryption', () => { it('should successfully roundtrip encryption/decryption with various data sizes', () => { // Test with different plaintext sizes const testSizes = [0, 1, 16, 64, 1024, 8192]; - + for (const size of testSizes) { const testData = new Uint8Array(size); // Fill with some deterministic pattern for (let i = 0; i < size; i++) { testData[i] = i % 256; } - + const encrypted = encryptWithSharedSecret(sharedSecret, testData); const decrypted = decryptWithSharedSecret(sharedSecret, encrypted); - + // Verify the decrypted data matches the original expect(decrypted.length).toEqual(testData.length); for (let i = 0; i < size; i++) { From 62b9958b9daaa2f694ed94564bd3e13be42af0bc Mon Sep 17 00:00:00 2001 From: Muhammad Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Mon, 14 Apr 2025 23:53:04 +0200 Subject: [PATCH 4/9] update json variables names accrding to new modifications at the generated json test vectors --- .../test/fixtures/shared-secret-vectors.json | 251 +++++++++++++++--- .../test/shared-secret-compatibility.test.ts | 22 +- 2 files changed, 229 insertions(+), 44 deletions(-) diff --git a/packages/shared/test/fixtures/shared-secret-vectors.json b/packages/shared/test/fixtures/shared-secret-vectors.json index ab877eeb0..58a15dc34 100644 --- a/packages/shared/test/fixtures/shared-secret-vectors.json +++ b/packages/shared/test/fixtures/shared-secret-vectors.json @@ -1,50 +1,235 @@ { - "testVectors": [ + "test_vectors": [ { "id": "vector1", - "description": "Basic encryption/decryption compatibility", - "sharedSecret": [ - 0, 1, 2, 3, 4, 5, 6, 7, - 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, 26, 27, 28, 29, 30, 31 + "description": "Fixed nonce encryption with known plaintext", + "shared_secret": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31 ], - "plaintext": "This is a fixed test message", - "fixedNonce": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], - "expectedCiphertext": "0102030405060708090a0b0c30e02d19293bd79329c9232eacc4e82eca99e0ea66e4da3808bccbacb3fd2ccbce94fe0c26cbbb81be779817" + "plaintext": "This is a test message", + "fixed_nonce": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "expected_ciphertext": "0000000000000000000000004cd02b428d8fd5f172412804dc376e4a9dc2809486c8b548d407dad8f75ee84d6d9e92a42c4a" }, { "id": "vector2", - "description": "Empty plaintext compatibility", - "sharedSecret": [ - 32, 33, 34, 35, 36, 37, 38, 39, - 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, - 56, 57, 58, 59, 60, 61, 62, 63 + "description": "Fixed nonce encryption with alternative values", + "shared_secret": [ + 31, + 30, + 29, + 28, + 27, + 26, + 25, + 24, + 23, + 22, + 21, + 20, + 19, + 18, + 17, + 16, + 15, + 14, + 13, + 12, + 11, + 10, + 9, + 8, + 7, + 6, + 5, + 4, + 3, + 2, + 1, + 0 ], "plaintext": "", - "fixedNonce": [16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192], - "expectedCiphertext": "102030405060708090a0b0c0d9abc79645718dc328d5c3faa129fced" + "fixed_nonce": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "expected_ciphertext": "0101010101010101010101018726c5f2d5b81872a86493ef5232aaa1" }, { "id": "vector3", "description": "Rust-generated ciphertext for TypeScript compatibility check", - "sharedSecret": [ - 0, 1, 2, 3, 4, 5, 6, 7, - 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, 26, 27, 28, 29, 30, 31 + "shared_secret": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31 ], - "rustGeneratedCiphertext": [ - 208, 248, 54, 195, 24, 29, 116, 93, 142, 5, 226, 81, - 0, 180, 190, 151, 127, 85, 8, 10, 113, 26, 107, 253, - 228, 24, 215, 116, 87, 194, 20, 220, 191, 92, 22, 154, - 247, 107, 224, 128, 190, 203, 88, 200, 15, 230, 248, 47, - 103, 100, 14, 220, 226, 62, 189, 57, 96, 83, 146, 94, - 64, 59, 182, 188, 175, 251, 87, 220, 67, 136, 94, 250, - 168, 9, 186, 82, 101, 138, 22, 13, 99, 150 + "rust_generated_ciphertext": [ + 87, + 119, + 14, + 255, + 68, + 116, + 249, + 152, + 58, + 121, + 245, + 185, + 157, + 62, + 23, + 3, + 50, + 163, + 150, + 212, + 167, + 157, + 244, + 227, + 159, + 130, + 110, + 26, + 30, + 161, + 173, + 70, + 232, + 207, + 110, + 161, + 252, + 228, + 149, + 42, + 173, + 185, + 61, + 157, + 144, + 163, + 128, + 200, + 87, + 80, + 172, + 63, + 235, + 241, + 169, + 124, + 248, + 158, + 213, + 62, + 241, + 209, + 117, + 216, + 202, + 230, + 106, + 248, + 170, + 150, + 126, + 40, + 203, + 34, + 14, + 44, + 71, + 186, + 234, + 92, + 13, + 248 ], - "expectedPlaintext": "This is a message encrypted by the Rust implementation" + "expected_plaintext": "This is a message encrypted by the Rust implementation" } ] -} +} \ No newline at end of file diff --git a/packages/shared/test/shared-secret-compatibility.test.ts b/packages/shared/test/shared-secret-compatibility.test.ts index 1400c7022..5777601d3 100644 --- a/packages/shared/test/shared-secret-compatibility.test.ts +++ b/packages/shared/test/shared-secret-compatibility.test.ts @@ -31,32 +31,32 @@ import { */ describe('Shared Secret Compatibility Tests', () => { // Parse test vectors from the loaded JSON file - const { testVectors } = testVectorsFile as { testVectors: any[] }; + const { test_vectors } = testVectorsFile as { test_vectors: any[] }; // Helper to convert test vector from JSON to usable objects const prepareTestVector = (vector: any) => { return { id: vector.id, description: vector.description, - sharedSecret: new Uint8Array(vector.sharedSecret), + sharedSecret: new Uint8Array(vector.shared_secret), plaintext: vector.plaintext ? new TextEncoder().encode(vector.plaintext) : new Uint8Array(0), - fixedNonce: new Uint8Array(vector.fixedNonce), - expectedCiphertext: vector.expectedCiphertext - ? Buffer.from(vector.expectedCiphertext, 'hex') + fixedNonce: new Uint8Array(vector.fixed_nonce), + expectedCiphertext: vector.expected_ciphertext + ? Buffer.from(vector.expected_ciphertext, 'hex') : null, - rustGeneratedCiphertext: vector.rustGeneratedCiphertext - ? new Uint8Array(vector.rustGeneratedCiphertext) + rustGeneratedCiphertext: vector.rust_generated_ciphertext + ? new Uint8Array(vector.rust_generated_ciphertext) : null, - expectedPlaintext: vector.expectedPlaintext, + expectedPlaintext: vector.expected_plaintext, }; }; // Parse test vectors into usable formats - const testVector1 = prepareTestVector(testVectors[0]); - const testVector2 = prepareTestVector(testVectors[1]); - const rustCompatVector = prepareTestVector(testVectors[2]); + const testVector1 = prepareTestVector(test_vectors[0]); + const testVector2 = prepareTestVector(test_vectors[1]); + const rustCompatVector = prepareTestVector(test_vectors[2]); // Set up a fixed nonce for testing function setupFixedNonceMock(fixedNonce: Uint8Array) { From 680422318506b161489478837192dfe7cc7ec4aa Mon Sep 17 00:00:00 2001 From: Muhammad Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Tue, 15 Apr 2025 07:38:05 +0200 Subject: [PATCH 5/9] add index file at encryption folder --- packages/shared/src/encryption/index.ts | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 packages/shared/src/encryption/index.ts diff --git a/packages/shared/src/encryption/index.ts b/packages/shared/src/encryption/index.ts new file mode 100644 index 000000000..c412ee984 --- /dev/null +++ b/packages/shared/src/encryption/index.ts @@ -0,0 +1,2 @@ +export * from './errors'; +export * from './shared-secret'; From 9f0cd8218f5e1028c2ca56cd537b5963038970e7 Mon Sep 17 00:00:00 2001 From: Muhammad Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Wed, 16 Apr 2025 13:38:00 +0200 Subject: [PATCH 6/9] set ChaCha20Poly1305 nonce length into a constant --- packages/shared/src/encryption/shared-secret.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/shared/src/encryption/shared-secret.ts b/packages/shared/src/encryption/shared-secret.ts index 1ac2a5189..329c20e54 100644 --- a/packages/shared/src/encryption/shared-secret.ts +++ b/packages/shared/src/encryption/shared-secret.ts @@ -8,17 +8,21 @@ import { EncryptionErrorCode, } from './errors'; +export const CHACHA20POLY1305_NONCE_LENGTH = 12; // bytes + export function encryptWithSharedSecret( sharedSecret: Uint8Array, plaintext: Uint8Array, ): Uint8Array { - const nonce = randomBytes(12); // Generate a 12-byte nonce + const nonce = randomBytes(CHACHA20POLY1305_NONCE_LENGTH); // Generate a 12-byte nonce const cipher = chacha20poly1305(sharedSecret, nonce); // Use an object with key try { const ciphertext = cipher.encrypt(plaintext); - const result = new Uint8Array(nonce.length + ciphertext.length); + const result = new Uint8Array( + CHACHA20POLY1305_NONCE_LENGTH + ciphertext.length, + ); result.set(nonce); - result.set(ciphertext, nonce.length); + result.set(ciphertext, CHACHA20POLY1305_NONCE_LENGTH); return result; } catch (error) { throw new EncryptionError(EncryptionErrorCode.PlaintextTooLarge); @@ -29,12 +33,11 @@ export function decryptWithSharedSecret( sharedSecret: Uint8Array, ciphertext: Uint8Array, ): Uint8Array { - const nonceLength = 12; // ChaCha20Poly1305 uses a 12-byte nonce - if (ciphertext.length < nonceLength) { + if (ciphertext.length < CHACHA20POLY1305_NONCE_LENGTH) { throw new DecryptionError(DecryptionErrorCode.CiphertextTooShort); } - const nonce = ciphertext.slice(0, nonceLength); - const encryptedData = ciphertext.slice(nonceLength); + const nonce = ciphertext.slice(0, CHACHA20POLY1305_NONCE_LENGTH); + const encryptedData = ciphertext.slice(CHACHA20POLY1305_NONCE_LENGTH); const cipher = chacha20poly1305(sharedSecret, nonce); // Use an object with key try { return cipher.decrypt(encryptedData); From 3961182afff616922eeabff742bebf79f356cec7 Mon Sep 17 00:00:00 2001 From: Muhammad Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Wed, 16 Apr 2025 22:17:52 +0200 Subject: [PATCH 7/9] simplify shared secret test by removing verbose logging and conditional checks --- .../test/shared-secret-compatibility.test.ts | 34 +++---------------- 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/packages/shared/test/shared-secret-compatibility.test.ts b/packages/shared/test/shared-secret-compatibility.test.ts index 5777601d3..c02fc9846 100644 --- a/packages/shared/test/shared-secret-compatibility.test.ts +++ b/packages/shared/test/shared-secret-compatibility.test.ts @@ -98,21 +98,8 @@ describe('Shared Secret Compatibility Tests', () => { // Check against expected ciphertext from the JSON file const ciphertextHex = Buffer.from(ciphertext).toString('hex'); - if (expectedCiphertext) { - const expectedHex = Buffer.from(expectedCiphertext).toString('hex'); - expect(ciphertextHex).toEqual(expectedHex); - console.debug( - `✓ Vector ${testVector1.id} ciphertext matches expected output`, - ); - } else { - console.error( - `Generated ciphertext for vector ${testVector1.id}:`, - ciphertextHex, - ); - console.error( - 'Add this to the test vectors JSON file to enable validation', - ); - } + const expectedHex = Buffer.from(expectedCiphertext!).toString('hex'); + expect(ciphertextHex).toEqual(expectedHex); // Verify decryption works correctly const decrypted = decryptWithSharedSecret(sharedSecret, ciphertext); @@ -138,21 +125,8 @@ describe('Shared Secret Compatibility Tests', () => { // Check against expected ciphertext from the JSON file const ciphertextHex = Buffer.from(ciphertext).toString('hex'); - if (expectedCiphertext) { - const expectedHex = Buffer.from(expectedCiphertext).toString('hex'); - expect(ciphertextHex).toEqual(expectedHex); - console.debug( - `✓ Vector ${testVector2.id} ciphertext matches expected output`, - ); - } else { - console.error( - `Generated ciphertext for vector ${testVector2.id}:`, - ciphertextHex, - ); - throw Error( - 'Add this to the test vectors JSON file to enable validation', - ); - } + const expectedHex = Buffer.from(expectedCiphertext!).toString('hex'); + expect(ciphertextHex).toEqual(expectedHex); // Verify decryption works correctly const decrypted = decryptWithSharedSecret(sharedSecret, ciphertext); From 0bd45aa01cf4ab8b9b4ae0eab7df01dc4ef3557c Mon Sep 17 00:00:00 2001 From: Muhammad Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Wed, 16 Apr 2025 23:58:23 +0200 Subject: [PATCH 8/9] Refactor test vectors and improve test structure using it.each --- .../test/fixtures/shared-secret-vectors.json | 239 ++---------------- .../test/shared-secret-compatibility.test.ts | 202 +++++---------- 2 files changed, 91 insertions(+), 350 deletions(-) diff --git a/packages/shared/test/fixtures/shared-secret-vectors.json b/packages/shared/test/fixtures/shared-secret-vectors.json index 58a15dc34..61bbdaae7 100644 --- a/packages/shared/test/fixtures/shared-secret-vectors.json +++ b/packages/shared/test/fixtures/shared-secret-vectors.json @@ -4,232 +4,47 @@ "id": "vector1", "description": "Fixed nonce encryption with known plaintext", "shared_secret": [ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 ], "plaintext": "This is a test message", - "fixed_nonce": [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ], - "expected_ciphertext": "0000000000000000000000004cd02b428d8fd5f172412804dc376e4a9dc2809486c8b548d407dad8f75ee84d6d9e92a42c4a" + "fixed_nonce": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "expected_ciphertext": [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 76, 208, 43, 66, 141, 143, 213, 241, + 114, 65, 40, 4, 220, 55, 110, 74, 157, 194, 128, 148, 134, 200, 181, 72, + 212, 7, 218, 216, 247, 94, 232, 77, 109, 158, 146, 164, 44, 74 + ] }, { "id": "vector2", "description": "Fixed nonce encryption with alternative values", "shared_secret": [ - 31, - 30, - 29, - 28, - 27, - 26, - 25, - 24, - 23, - 22, - 21, - 20, - 19, - 18, - 17, - 16, - 15, - 14, - 13, - 12, - 11, - 10, - 9, - 8, - 7, - 6, - 5, - 4, - 3, - 2, - 1, - 0 + 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, + 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ], "plaintext": "", - "fixed_nonce": [ - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1 - ], - "expected_ciphertext": "0101010101010101010101018726c5f2d5b81872a86493ef5232aaa1" + "fixed_nonce": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + "expected_ciphertext": [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 135, 38, 197, 242, 213, 184, 24, + 114, 168, 100, 147, 239, 82, 50, 170, 161 + ] }, { "id": "vector3", "description": "Rust-generated ciphertext for TypeScript compatibility check", "shared_secret": [ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31 - ], - "rust_generated_ciphertext": [ - 87, - 119, - 14, - 255, - 68, - 116, - 249, - 152, - 58, - 121, - 245, - 185, - 157, - 62, - 23, - 3, - 50, - 163, - 150, - 212, - 167, - 157, - 244, - 227, - 159, - 130, - 110, - 26, - 30, - 161, - 173, - 70, - 232, - 207, - 110, - 161, - 252, - 228, - 149, - 42, - 173, - 185, - 61, - 157, - 144, - 163, - 128, - 200, - 87, - 80, - 172, - 63, - 235, - 241, - 169, - 124, - 248, - 158, - 213, - 62, - 241, - 209, - 117, - 216, - 202, - 230, - 106, - 248, - 170, - 150, - 126, - 40, - 203, - 34, - 14, - 44, - 71, - 186, - 234, - 92, - 13, - 248 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 ], - "expected_plaintext": "This is a message encrypted by the Rust implementation" + "plaintext": "This is a message encrypted by the Rust implementation", + "expected_ciphertext": [ + 100, 196, 241, 204, 119, 208, 40, 31, 29, 138, 199, 108, 168, 89, 32, + 208, 157, 93, 80, 94, 60, 106, 168, 38, 62, 206, 143, 135, 56, 12, 142, + 15, 156, 9, 227, 26, 97, 154, 204, 11, 179, 200, 3, 180, 203, 200, 221, + 190, 122, 118, 154, 147, 180, 170, 1, 23, 168, 86, 226, 78, 224, 176, + 218, 159, 127, 47, 55, 28, 193, 231, 42, 92, 118, 112, 139, 150, 73, + 205, 155, 90, 66, 172 + ] } ] -} \ No newline at end of file +} diff --git a/packages/shared/test/shared-secret-compatibility.test.ts b/packages/shared/test/shared-secret-compatibility.test.ts index c02fc9846..c1bad0a12 100644 --- a/packages/shared/test/shared-secret-compatibility.test.ts +++ b/packages/shared/test/shared-secret-compatibility.test.ts @@ -42,21 +42,15 @@ describe('Shared Secret Compatibility Tests', () => { plaintext: vector.plaintext ? new TextEncoder().encode(vector.plaintext) : new Uint8Array(0), - fixedNonce: new Uint8Array(vector.fixed_nonce), - expectedCiphertext: vector.expected_ciphertext - ? Buffer.from(vector.expected_ciphertext, 'hex') - : null, - rustGeneratedCiphertext: vector.rust_generated_ciphertext - ? new Uint8Array(vector.rust_generated_ciphertext) - : null, - expectedPlaintext: vector.expected_plaintext, + fixedNonce: vector.fixed_nonce + ? new Uint8Array(vector.fixed_nonce) + : undefined, + expectedCiphertext: Buffer.from(vector.expected_ciphertext, 'hex'), // new Uint8Array(vector.expected_ciphertext) }; }; - // Parse test vectors into usable formats - const testVector1 = prepareTestVector(test_vectors[0]); - const testVector2 = prepareTestVector(test_vectors[1]); - const rustCompatVector = prepareTestVector(test_vectors[2]); + // Parse all test vectors into usable formats + const testVectors = test_vectors.map(prepareTestVector); // Set up a fixed nonce for testing function setupFixedNonceMock(fixedNonce: Uint8Array) { @@ -83,115 +77,46 @@ describe('Shared Secret Compatibility Tests', () => { }; } - describe('Round-trip fixed vector tests', () => { - // Testing each vector for round-trip compatibility - it('should correctly encrypt and decrypt test vector 1', () => { - const { sharedSecret, plaintext, fixedNonce, expectedCiphertext } = - testVector1; - - // Set up our mock to return the fixed nonce - const cleanupMock = setupFixedNonceMock(fixedNonce); - - try { - // Encrypt with fixed nonce - const ciphertext = encryptWithSharedSecret(sharedSecret, plaintext); - - // Check against expected ciphertext from the JSON file - const ciphertextHex = Buffer.from(ciphertext).toString('hex'); - const expectedHex = Buffer.from(expectedCiphertext!).toString('hex'); - expect(ciphertextHex).toEqual(expectedHex); - - // Verify decryption works correctly - const decrypted = decryptWithSharedSecret(sharedSecret, ciphertext); - expect(Buffer.from(decrypted).toString()).toEqual( - Buffer.from(plaintext).toString(), - ); - } finally { - // Always restore the original implementation - cleanupMock(); - } - }); - - it('should correctly encrypt and decrypt test vector 2 (empty plaintext)', () => { - const { sharedSecret, plaintext, fixedNonce, expectedCiphertext } = - testVector2; - - // Set up our mock to return the fixed nonce - const cleanupMock = setupFixedNonceMock(fixedNonce); - - try { - // Encrypt with fixed nonce - const ciphertext = encryptWithSharedSecret(sharedSecret, plaintext); - - // Check against expected ciphertext from the JSON file - const ciphertextHex = Buffer.from(ciphertext).toString('hex'); - const expectedHex = Buffer.from(expectedCiphertext!).toString('hex'); - expect(ciphertextHex).toEqual(expectedHex); + describe('Vector-based compatibility tests', () => { + it.each(testVectors)( + 'should correctly encrypt and decrypt test vector $id: $description', + (vector) => { + const { sharedSecret, plaintext, fixedNonce, expectedCiphertext } = + vector; + + if (fixedNonce && sharedSecret) { + // Set up our mock to return the fixed nonce + const cleanupMock = setupFixedNonceMock(fixedNonce); + + // Encrypt with fixed nonce + const ciphertext = encryptWithSharedSecret(sharedSecret, plaintext); + // Always restore the original implementation + cleanupMock(); + + // Check against expected ciphertext from the JSON file + const ciphertextHex = Buffer.from(ciphertext).toString('hex'); + const expectedHex = Buffer.from(expectedCiphertext).toString('hex'); + expect(ciphertextHex).toEqual(expectedHex); + } // Verify decryption works correctly - const decrypted = decryptWithSharedSecret(sharedSecret, ciphertext); - expect(decrypted.length).toEqual(plaintext.length); - expect(decrypted.byteLength).toEqual(0); // Empty plaintext - } finally { - // Always restore the original implementation - cleanupMock(); - } - }); - }); - - describe('Cross-implementation compatibility tests', () => { - // Compatibility test for decryption of pre-generated ciphertext from Rust - it('should correctly decrypt ciphertext generated by the Rust implementation', () => { - // Using test vector with Rust-generated ciphertext from the JSON file - const { sharedSecret, rustGeneratedCiphertext, expectedPlaintext } = - rustCompatVector; - - // Skip if no Rust-generated ciphertext data is available - if (!rustGeneratedCiphertext) { - throw Error('No Rust-generated ciphertext provided - skipping test'); - } - - try { const decrypted = decryptWithSharedSecret( sharedSecret, - rustGeneratedCiphertext, + expectedCiphertext, ); - const decryptedText = Buffer.from(decrypted).toString(); - - if ( - expectedPlaintext && - expectedPlaintext !== 'Replace with actual plaintext from Rust' - ) { - expect(decryptedText).toEqual(expectedPlaintext); - console.debug('✓ Successfully decrypted Rust-generated ciphertext'); - } else { - console.error('Decrypted Rust-generated ciphertext:', decryptedText); - throw Error( - 'Update the test vector with expected plaintext to enable validation', - ); - } - } catch (error) { - if (rustGeneratedCiphertext.length <= 12) { - throw Error( - 'Ciphertext too short - update with real Rust-generated data', - ); - } else if ( - error instanceof DecryptionError && - error.code === DecryptionErrorCode.AuthenticationFailed - ) { - throw Error( - 'Authentication failed - update with real Rust-generated data', - ); - } else { - throw error; - } - } - }); + + expect(Buffer.from(decrypted).toString()).toEqual( + Buffer.from(plaintext).toString(), + ); + }, + ); }); describe('Error handling compatibility tests', () => { + // Using the first test vector's shared secret for error tests + const { sharedSecret, plaintext } = testVectors[0]; + it('should handle ciphertext too short error in the same way as Rust', () => { - const { sharedSecret } = testVector1; const tooShortCiphertext = new Uint8Array([0x01, 0x02, 0x03]); // Less than nonce length try { @@ -199,43 +124,44 @@ describe('Shared Secret Compatibility Tests', () => { expect.fail('Should have thrown an error'); } catch (error) { expect(error).toBeInstanceOf(DecryptionError); - expect((error as DecryptionError).code).toBe( + const decryptionError = error as DecryptionError; + expect(decryptionError.code).toBe( DecryptionErrorCode.CiphertextTooShort, ); - expect((error as DecryptionError).message).toBe( + expect(decryptionError.message).toContain( 'The ciphertext must include the nonce', ); } }); - it('should handle authentication failed error in the same way as Rust', () => { - const { sharedSecret, fixedNonce } = testVector1; + it('should handle tampered ciphertext in the same way as Rust', () => { + // We need to setup a fixed nonce for the encryption + const mockNonce = new Uint8Array(12).fill(0); // 12 zeros as nonce - // Set up our mock to return the fixed nonce - const cleanupMock = setupFixedNonceMock(fixedNonce); + // Use the existing helper to setup the mock + const cleanupMock = setupFixedNonceMock(mockNonce); try { - // Generate valid ciphertext - const validCiphertext = encryptWithSharedSecret( - sharedSecret, - new TextEncoder().encode('Test message'), - ); - - // Tamper with the ciphertext - validCiphertext[validCiphertext.length - 1] = - (validCiphertext[validCiphertext.length - 1] + 1) % 256; + // Encrypt normally + const ciphertext = encryptWithSharedSecret(sharedSecret, plaintext); - // Try to decrypt tampered ciphertext - decryptWithSharedSecret(sharedSecret, validCiphertext); - expect.fail('Should have thrown an error'); - } catch (error) { - expect(error).toBeInstanceOf(DecryptionError); - expect((error as DecryptionError).code).toBe( - DecryptionErrorCode.AuthenticationFailed, - ); - expect((error as DecryptionError).message).toContain( - 'Decryption of ciphertext failed', - ); + // Tamper with the ciphertext by changing one byte + const tamperedCiphertext = new Uint8Array(ciphertext); + tamperedCiphertext[tamperedCiphertext.length - 1] ^= 0x01; // Flip one bit in the last byte + + try { + decryptWithSharedSecret(sharedSecret, tamperedCiphertext); + expect.fail('Should have thrown an error'); + } catch (error) { + expect(error).toBeInstanceOf(DecryptionError); + const decryptionError = error as DecryptionError; + expect(decryptionError.code).toBe( + DecryptionErrorCode.AuthenticationFailed, + ); + expect(decryptionError.message).toContain( + 'Decryption of ciphertext failed', + ); + } } finally { // Always restore the original implementation cleanupMock(); From 74e60048a13c810472c046e10c7bd9fa5c890777 Mon Sep 17 00:00:00 2001 From: Muhammad Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Mon, 21 Apr 2025 08:21:13 +0200 Subject: [PATCH 9/9] tiny edits at shared secret tests in responce to review comments --- .../test/shared-secret-compatibility.test.ts | 20 ++++++------------- packages/shared/test/shared-secret.test.ts | 2 +- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/packages/shared/test/shared-secret-compatibility.test.ts b/packages/shared/test/shared-secret-compatibility.test.ts index c1bad0a12..eb50b2edd 100644 --- a/packages/shared/test/shared-secret-compatibility.test.ts +++ b/packages/shared/test/shared-secret-compatibility.test.ts @@ -39,13 +39,11 @@ describe('Shared Secret Compatibility Tests', () => { id: vector.id, description: vector.description, sharedSecret: new Uint8Array(vector.shared_secret), - plaintext: vector.plaintext - ? new TextEncoder().encode(vector.plaintext) - : new Uint8Array(0), + plaintext: new TextEncoder().encode(vector.plaintext), fixedNonce: vector.fixed_nonce ? new Uint8Array(vector.fixed_nonce) : undefined, - expectedCiphertext: Buffer.from(vector.expected_ciphertext, 'hex'), // new Uint8Array(vector.expected_ciphertext) + expectedCiphertext: Buffer.from(vector.expected_ciphertext, 'hex'), }; }; @@ -84,7 +82,7 @@ describe('Shared Secret Compatibility Tests', () => { const { sharedSecret, plaintext, fixedNonce, expectedCiphertext } = vector; - if (fixedNonce && sharedSecret) { + if (fixedNonce) { // Set up our mock to return the fixed nonce const cleanupMock = setupFixedNonceMock(fixedNonce); @@ -114,7 +112,7 @@ describe('Shared Secret Compatibility Tests', () => { describe('Error handling compatibility tests', () => { // Using the first test vector's shared secret for error tests - const { sharedSecret, plaintext } = testVectors[0]; + const { expectedCiphertext, fixedNonce, sharedSecret } = testVectors[0]; it('should handle ciphertext too short error in the same way as Rust', () => { const tooShortCiphertext = new Uint8Array([0x01, 0x02, 0x03]); // Less than nonce length @@ -135,18 +133,12 @@ describe('Shared Secret Compatibility Tests', () => { }); it('should handle tampered ciphertext in the same way as Rust', () => { - // We need to setup a fixed nonce for the encryption - const mockNonce = new Uint8Array(12).fill(0); // 12 zeros as nonce - // Use the existing helper to setup the mock - const cleanupMock = setupFixedNonceMock(mockNonce); + const cleanupMock = setupFixedNonceMock(fixedNonce!); try { - // Encrypt normally - const ciphertext = encryptWithSharedSecret(sharedSecret, plaintext); - // Tamper with the ciphertext by changing one byte - const tamperedCiphertext = new Uint8Array(ciphertext); + const tamperedCiphertext = new Uint8Array(expectedCiphertext); tamperedCiphertext[tamperedCiphertext.length - 1] ^= 0x01; // Flip one bit in the last byte try { diff --git a/packages/shared/test/shared-secret.test.ts b/packages/shared/test/shared-secret.test.ts index 5bb6c1642..7dc6787ae 100644 --- a/packages/shared/test/shared-secret.test.ts +++ b/packages/shared/test/shared-secret.test.ts @@ -48,7 +48,7 @@ describe('Shared Secret Encryption/Decryption', () => { it('should encrypt plaintext successfully', () => { const encrypted = encryptWithSharedSecret(sharedSecret, plaintext); - // Ensure the encrypted result is longer than the plaintext (nonce + ciphertext + tag) + // Ensure the encrypted result is longer than the plaintext (nonce + ciphertext + authentication tag) expect(encrypted.length).toBeGreaterThan(plaintext.length); // Ensure the encrypted result is different from the plaintext