diff --git a/Ciphers/SubstitutionCipher.js b/Ciphers/SubstitutionCipher.js new file mode 100644 index 0000000000..2383a99796 --- /dev/null +++ b/Ciphers/SubstitutionCipher.js @@ -0,0 +1,59 @@ +/** + * Substitution Cipher + * + * A monoalphabetic substitution cipher replaces each letter of the plaintext + * with another letter based on a fixed permutation (key) of the alphabet. + * https://en.wikipedia.org/wiki/Substitution_cipher + */ + +const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +const defaultKey = 'QWERTYUIOPASDFGHJKLZXCVBNM' + +/** + * Encrypts a string using a monoalphabetic substitution cipher + * @param {string} text - The text to encrypt + * @param {string} key - The substitution key (must be 26 uppercase letters) + * @returns {string} + */ +export function substitutionCipherEncryption(text, key = defaultKey) { + if (key.length !== 26 || !/^[A-Z]+$/.test(key)) { + throw new RangeError('Key must be 26 uppercase English letters.') + } + + let result = '' + const textUpper = text.toUpperCase() + for (let i = 0; i < textUpper.length; i++) { + const char = textUpper[i] + const index = alphabet.indexOf(char) + if (index !== -1) { + result += key[index] + } else { + result += char + } + } + return result +} +/** + * Decrypts a string encrypted with the substitution cipher + * @param {string} text - The encrypted text + * @param {string} key - The substitution key used during encryption + * @returns {string} + */ +export function substitutionCipherDecryption(text, key = defaultKey) { + if (key.length !== 26 || !/^[A-Z]+$/.test(key)) { + throw new RangeError('Key must be 26 uppercase English letters.') + } + + let result = '' + const textUpper = text.toUpperCase() + for (let i = 0; i < textUpper.length; i++) { + const char = textUpper[i] + const index = key.indexOf(char) + if (index !== -1) { + result += alphabet[index] + } else { + result += char + } + } + return result +} diff --git a/Ciphers/test/SubstitutionCipher.test.js b/Ciphers/test/SubstitutionCipher.test.js new file mode 100644 index 0000000000..820d0303d6 --- /dev/null +++ b/Ciphers/test/SubstitutionCipher.test.js @@ -0,0 +1,44 @@ +import { describe, it, expect } from 'vitest' +import { + substitutionCipherEncryption, + substitutionCipherDecryption +} from '../SubstitutionCipher.js' + +describe('Substitution Cipher', () => { + const key = 'QWERTYUIOPASDFGHJKLZXCVBNM' + + it('correctly encrypts a message', () => { + const encrypted = substitutionCipherEncryption('HELLO WORLD', key) + expect(encrypted).toBe('ITSSG VGKSR') + }) + + it('correctly decrypts a message', () => { + const decrypted = substitutionCipherDecryption('ITSSG VGKSR', key) + expect(decrypted).toBe('HELLO WORLD') + }) + + it('handles non-alphabetic characters', () => { + const encrypted = substitutionCipherEncryption('Test! 123', key) + expect(encrypted).toBe('ZTLZ! 123') + }) + + it('throws error for invalid key', () => { + expect(() => substitutionCipherEncryption('HELLO', 'BADKEY')).toThrow( + RangeError + ) + }) + it('encrypts using default key if none provided', () => { + const encrypted = substitutionCipherEncryption('HELLO WORLD') + expect(encrypted).toBe('ITSSG VGKSR') + }) + + it('decrypts using default key if none provided', () => { + const decrypted = substitutionCipherDecryption('ITSSG VGKSR') + expect(decrypted).toBe('HELLO WORLD') + }) + + it('throws error for invalid key in decryption', () => { + expect(() => substitutionCipherDecryption('HELLO', 'BADKEY')).toThrow(RangeError) + }) + +}) diff --git a/Maths/MobiusFunction.js b/Maths/MobiusFunction.js index bd268b8bbd..4239d6ab31 100644 --- a/Maths/MobiusFunction.js +++ b/Maths/MobiusFunction.js @@ -28,6 +28,6 @@ export const mobiusFunction = (number) => { return primeFactorsArray.length !== new Set(primeFactorsArray).size ? 0 : primeFactorsArray.length % 2 === 0 - ? 1 - : -1 + ? 1 + : -1 }