Skip to content
Open
59 changes: 59 additions & 0 deletions Ciphers/SubstitutionCipher.js
Original file line number Diff line number Diff line change
@@ -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
}
44 changes: 44 additions & 0 deletions Ciphers/test/SubstitutionCipher.test.js
Original file line number Diff line number Diff line change
@@ -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)
})

})
4 changes: 2 additions & 2 deletions Maths/MobiusFunction.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ export const mobiusFunction = (number) => {
return primeFactorsArray.length !== new Set(primeFactorsArray).size
? 0
: primeFactorsArray.length % 2 === 0
? 1
: -1
? 1
: -1
}
Loading