Skip to content

Commit c93cda3

Browse files
feat: add Substitution Cipher algorithm and tests
1 parent 1d252d7 commit c93cda3

File tree

2 files changed

+89
-0
lines changed

2 files changed

+89
-0
lines changed

Diff for: Ciphers/SubstitutionCipher.js

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Substitution Cipher
3+
*
4+
* A monoalphabetic substitution cipher replaces each letter of the plaintext
5+
* with another letter based on a fixed permutation (key) of the alphabet.
6+
* https://en.wikipedia.org/wiki/Substitution_cipher
7+
*/
8+
9+
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
10+
const defaultKey = 'QWERTYUIOPASDFGHJKLZXCVBNM'
11+
12+
/**
13+
* Encrypts a string using a monoalphabetic substitution cipher
14+
* @param {string} text - The text to encrypt
15+
* @param {string} key - The substitution key (must be 26 uppercase letters)
16+
* @returns {string}
17+
*/
18+
export function substitutionCipherEncryption(text, key = defaultKey) {
19+
if (key.length !== 26 || !/^[A-Z]+$/.test(key)) {
20+
throw new RangeError('Key must be 26 uppercase English letters.')
21+
}
22+
23+
let result = ''
24+
const textUpper = text.toUpperCase()
25+
for (let i = 0; i < textUpper.length; i++) {
26+
const char = textUpper[i]
27+
const index = alphabet.indexOf(char)
28+
if (index !== -1) {
29+
result += key[index]
30+
} else {
31+
result += char
32+
}
33+
}
34+
return result
35+
}
36+
/**
37+
* Decrypts a string encrypted with the substitution cipher
38+
* @param {string} text - The encrypted text
39+
* @param {string} key - The substitution key used during encryption
40+
* @returns {string}
41+
*/
42+
export function substitutionCipherDecryption(text, key = defaultKey) {
43+
if (key.length !== 26 || !/^[A-Z]+$/.test(key)) {
44+
throw new RangeError('Key must be 26 uppercase English letters.')
45+
}
46+
47+
let result = ''
48+
const textUpper = text.toUpperCase()
49+
for (let i = 0; i < textUpper.length; i++) {
50+
const char = textUpper[i]
51+
const index = key.indexOf(char)
52+
if (index !== -1) {
53+
result += alphabet[index]
54+
} else {
55+
result += char
56+
}
57+
}
58+
return result
59+
}

Diff for: Ciphers/test/SubstitutionCipher.test.js

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { describe, it, expect } from 'vitest'
2+
import {
3+
substitutionCipherEncryption,
4+
substitutionCipherDecryption
5+
} from './SubstitutionCipher'
6+
7+
describe('Substitution Cipher', () => {
8+
const key = 'QWERTYUIOPASDFGHJKLZXCVBNM'
9+
10+
it('correctly encrypts a message', () => {
11+
const encrypted = substitutionCipherEncryption('HELLO WORLD', key)
12+
expect(encrypted).toBe('ITSSG VGKSR')
13+
})
14+
15+
it('correctly decrypts a message', () => {
16+
const decrypted = substitutionCipherDecryption('ITSSG VGKSR', key)
17+
expect(decrypted).toBe('HELLO WORLD')
18+
})
19+
20+
it('handles non-alphabetic characters', () => {
21+
const encrypted = substitutionCipherEncryption('Test! 123', key)
22+
expect(encrypted).toBe('ZTLZ! 123')
23+
})
24+
25+
it('throws error for invalid key', () => {
26+
expect(() => substitutionCipherEncryption('HELLO', 'BADKEY')).toThrow(
27+
RangeError
28+
)
29+
})
30+
})

0 commit comments

Comments
 (0)