From cfba1d9954ab5b4a33251c7af3f7f55a256a0de5 Mon Sep 17 00:00:00 2001 From: Oleksii Trekhleb Date: Sun, 20 Dec 2020 19:57:51 +0100 Subject: [PATCH] Add Rail Fence Cipher. --- README.md | 2 +- .../cryptography/rail-fence-cipher/README.md | 15 +- ...Cypher.test.js => railFenceCipher.test.js} | 35 ++- .../rail-fence-cipher/decodeRailFence.js | 108 -------- .../rail-fence-cipher/encodeRailFence.js | 52 ---- .../rail-fence-cipher/railFenceCipher.js | 244 ++++++++++++++++-- 6 files changed, 254 insertions(+), 202 deletions(-) rename src/algorithms/cryptography/rail-fence-cipher/__test__/{railFenceCypher.test.js => railFenceCipher.test.js} (51%) delete mode 100644 src/algorithms/cryptography/rail-fence-cipher/decodeRailFence.js delete mode 100644 src/algorithms/cryptography/rail-fence-cipher/encodeRailFence.js diff --git a/README.md b/README.md index c6d87134..feb9832d 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ a set of rules that precisely define a sequence of operations. * `A` [Travelling Salesman Problem](src/algorithms/graph/travelling-salesman) - shortest possible route that visits each city and returns to the origin city * **Cryptography** * `B` [Polynomial Hash](src/algorithms/cryptography/polynomial-hash) - rolling hash function based on polynomial - * `B` [Rail Fence Cypher](src/algorithms/cryptography/rail-fence-cipher) - a transposition cipher algorithm for encoding messages + * `B` [Rail Fence Cipher](src/algorithms/cryptography/rail-fence-cipher) - a transposition cipher algorithm for encoding messages * `B` [Caesar Cipher](src/algorithms/cryptography/caesar-cipher) - simple substitution cipher * `B` [Hill Cipher](src/algorithms/cryptography/hill-cipher) - substitution cipher based on linear algebra * **Machine Learning** diff --git a/src/algorithms/cryptography/rail-fence-cipher/README.md b/src/algorithms/cryptography/rail-fence-cipher/README.md index e090f557..d01395f5 100644 --- a/src/algorithms/cryptography/rail-fence-cipher/README.md +++ b/src/algorithms/cryptography/rail-fence-cipher/README.md @@ -1,18 +1,18 @@ -# Rail fence Cipher +# Rail Fence Cipher -This is a [transposition cipher](https://en.wikipedia.org/wiki/Transposition_cipher) in which the message is split accross a set of rails on a fence for encoding. The fence is populated with the message's characters, starting at the top left and adding a character on each position, traversing them diagonally to the bottom. Upon reaching the last rail, the direction should then turn diagonal and upwards up to the very first rail in a zig-zag motion. Rinse and repeat until the message is fully disposed across the fence. The encoded message is the result of concatenating the text in each rail, from top to bottom. +The **rail fence cipher** (also called a **zigzag cipher**) is a [transposition cipher](https://en.wikipedia.org/wiki/Transposition_cipher) in which the message is split across a set of rails on a fence for encoding. The fence is populated with the message's characters, starting at the top left and adding a character on each position, traversing them diagonally to the bottom. Upon reaching the last rail, the direction should then turn diagonal and upwards up to the very first rail in a zig-zag motion. Rinse and repeat until the message is fully disposed across the fence. The encoded message is the result of concatenating the text in each rail, from top to bottom. -From [wikipedia](https://en.wikipedia.org/wiki/Rail_fence_cipher), this is what the message `WE ARE DISCOVERED. FLEE AT ONCE` looks like on a 3-rail fence: +From [wikipedia](https://en.wikipedia.org/wiki/Rail_fence_cipher), this is what the message `WE ARE DISCOVERED. FLEE AT ONCE` looks like on a `3`-rail fence: ``` W . . . E . . . C . . . R . . . L . . . T . . . E . E . R . D . S . O . E . E . F . E . A . O . C . . . A . . . I . . . V . . . D . . . E . . . N . . ------------------------------------------------- - ECRLTEERDSOEEFEAOCAIVDEN + WECRLTEERDSOEEFEAOCAIVDEN ``` -The message can then be decoded by re-creating the encode fence, with the same traversal pattern, except characters should only be added on one rail at a time. To ilustrate that, a dash can be added on the rails that are not supposed to be poupated yet. This is what the fence would look like after populating the first rail, the dashes represent positions that were visited but not populated. +The message can then be decoded by re-creating the encoded fence, with the same traversal pattern, except characters should only be added on one rail at a time. To illustrate that, a dash can be added on the rails that are not supposed to be populated yet. This is what the fence would look like after populating the first rail, the dashes represent positions that were visited but not populated. ``` W . . . E . . . C . . . R . . . L . . . T . . . E @@ -22,4 +22,7 @@ W . . . E . . . C . . . R . . . L . . . T . . . E It's time to start populating the next rail once the number of visited fence positions is equal to the number of characters in the message. -[Learn more](https://crypto.interactive-maths.com/rail-fence-cipher.html) +## References + +- [Rail Fence Cipher on Wikipedia](https://en.wikipedia.org/wiki/Rail_fence_cipher) +- [Rail Fence Cipher Calculator](https://crypto.interactive-maths.com/rail-fence-cipher.html) diff --git a/src/algorithms/cryptography/rail-fence-cipher/__test__/railFenceCypher.test.js b/src/algorithms/cryptography/rail-fence-cipher/__test__/railFenceCipher.test.js similarity index 51% rename from src/algorithms/cryptography/rail-fence-cipher/__test__/railFenceCypher.test.js rename to src/algorithms/cryptography/rail-fence-cipher/__test__/railFenceCipher.test.js index 285841e2..db0c49eb 100644 --- a/src/algorithms/cryptography/rail-fence-cipher/__test__/railFenceCypher.test.js +++ b/src/algorithms/cryptography/rail-fence-cipher/__test__/railFenceCipher.test.js @@ -1,26 +1,43 @@ -import encodeRailFenceCipher from '../encodeRailFence'; -import decodeRailFenceCipher from '../decodeRailFence'; +import { encodeRailFenceCipher, decodeRailFenceCipher } from '../railFenceCipher'; -describe('rail fence cipher', () => { +describe('railFenceCipher', () => { it('encodes a string correctly for base=3', () => { expect(encodeRailFenceCipher('', 3)).toBe(''); - expect(encodeRailFenceCipher('WEAREDISCOVEREDFLEEATONCE', 3)).toBe('WECRLTEERDSOEEFEAOCAIVDEN'); - expect(encodeRailFenceCipher('Hello, World!', 3)).toBe('Hoo!el,Wrdl l'); + expect(encodeRailFenceCipher('12345', 3)).toBe( + '15243', + ); + expect(encodeRailFenceCipher('WEAREDISCOVEREDFLEEATONCE', 3)).toBe( + 'WECRLTEERDSOEEFEAOCAIVDEN', + ); + expect(encodeRailFenceCipher('Hello, World!', 3)).toBe( + 'Hoo!el,Wrdl l', + ); }); it('decodes a string correctly for base=3', () => { expect(decodeRailFenceCipher('', 3)).toBe(''); - expect(decodeRailFenceCipher('WECRLTEERDSOEEFEAOCAIVDEN', 3)).toBe('WEAREDISCOVEREDFLEEATONCE'); - expect(decodeRailFenceCipher('Hoo!el,Wrdl l', 3)).toBe('Hello, World!'); + expect(decodeRailFenceCipher('WECRLTEERDSOEEFEAOCAIVDEN', 3)).toBe( + 'WEAREDISCOVEREDFLEEATONCE', + ); + expect(decodeRailFenceCipher('Hoo!el,Wrdl l', 3)).toBe( + 'Hello, World!', + ); + expect(decodeRailFenceCipher('15243', 3)).toBe( + '12345', + ); }); it('encodes a string correctly for base=4', () => { expect(encodeRailFenceCipher('', 4)).toBe(''); - expect(encodeRailFenceCipher('THEYAREATTACKINGFROMTHENORTH', 4)).toBe('TEKOOHRACIRMNREATANFTETYTGHH'); + expect(encodeRailFenceCipher('THEYAREATTACKINGFROMTHENORTH', 4)).toBe( + 'TEKOOHRACIRMNREATANFTETYTGHH', + ); }); it('decodes a string correctly for base=4', () => { expect(decodeRailFenceCipher('', 4)).toBe(''); - expect(decodeRailFenceCipher('TEKOOHRACIRMNREATANFTETYTGHH', 4)).toBe('THEYAREATTACKINGFROMTHENORTH'); + expect(decodeRailFenceCipher('TEKOOHRACIRMNREATANFTETYTGHH', 4)).toBe( + 'THEYAREATTACKINGFROMTHENORTH', + ); }); }); diff --git a/src/algorithms/cryptography/rail-fence-cipher/decodeRailFence.js b/src/algorithms/cryptography/rail-fence-cipher/decodeRailFence.js deleted file mode 100644 index b1826b48..00000000 --- a/src/algorithms/cryptography/rail-fence-cipher/decodeRailFence.js +++ /dev/null @@ -1,108 +0,0 @@ -import { - addChar, - buildFence, - DIRECTIONS, - getNextDirection, -} from './railFenceCipher'; - -/** - * @param {object} params - * @param {number} params.railCount - * @param {number} params.strLen - * @param {Array} params.string - * @param {Array} params.fence - * @param {number} params.targetRail - * @param {number} params.direction - * @param {Array} params.coords - * - * @returns {Array} - */ -const fillDecodeFence = ({ - railCount, strLen, string, fence, targetRail, direction, coords, -}) => { - if (string.length === 0) return fence; - - const [currentRail, currentColumn] = coords; - const shouldGoNextRail = currentColumn === strLen - 1; - const nextDirection = shouldGoNextRail - ? DIRECTIONS.DOWN - : getNextDirection({ railCount, currentRail, direction }); - const nextRail = shouldGoNextRail ? targetRail + 1 : targetRail; - const nextCoords = [ - shouldGoNextRail ? 0 : currentRail + nextDirection, - shouldGoNextRail ? 0 : currentColumn + 1, - ]; - - const shouldAddChar = currentRail === targetRail; - const [currentChar, ...remainderString] = string; - const nextString = shouldAddChar ? remainderString : string; - const nextFence = shouldAddChar ? fence.map(addChar(currentRail, currentChar)) : fence; - - return fillDecodeFence({ - railCount, - strLen, - string: nextString, - fence: nextFence, - targetRail: nextRail, - direction: nextDirection, - coords: nextCoords, - }); -}; - -/** - * @param {object} params - * @param {number} params.railCount - * @param {number} params.strLen - * @param {Array} params.fence - * @param {number} params.currentRail - * @param {number} params.direction - * @param {Array} params.code - * - * @returns {string} - */ -const decodeFence = ({ - railCount, strLen, fence, currentRail, direction, code, -}) => { - if (code.length === strLen) return code.join(''); - - const [currentChar, ...nextRail] = fence[currentRail]; - const nextDirection = getNextDirection({ railCount, currentRail, direction }); - - return decodeFence({ - railCount, - strLen, - currentRail: currentRail + nextDirection, - direction: nextDirection, - code: [...code, currentChar], - fence: fence.map((rail, idx) => (idx === currentRail ? nextRail : rail)), - }); -}; - -/** - * @param {string} string - * @param {number} railCount - * - * @returns {string} - */ -export default function decodeRailFenceCipher(string, railCount) { - const strLen = string.length; - const emptyFence = buildFence(railCount); - const filledFence = fillDecodeFence({ - railCount, - strLen, - string: string.split(''), - fence: emptyFence, - targetRail: 0, - direction: DIRECTIONS.DOWN, - coords: [0, 0], - }); - - return decodeFence({ - railCount, - strLen, - fence: filledFence, - currentRail: 0, - direction: DIRECTIONS.DOWN, - code: [], - }); -} diff --git a/src/algorithms/cryptography/rail-fence-cipher/encodeRailFence.js b/src/algorithms/cryptography/rail-fence-cipher/encodeRailFence.js deleted file mode 100644 index 9a581312..00000000 --- a/src/algorithms/cryptography/rail-fence-cipher/encodeRailFence.js +++ /dev/null @@ -1,52 +0,0 @@ -import { - addChar, - buildFence, - DIRECTIONS, - getNextDirection, -} from './railFenceCipher'; - -/** - * @param {object} params - * @param {number} params.railCount - * @param {number} params.currentRail - * @param {number} params.direction - * @param {Array} params.string - * - * @returns {Array} - */ -const fillEncodeFence = ({ - railCount, fence, currentRail, direction, string, -}) => { - if (string.length === 0) return fence; - - const [letter, ...nextString] = string; - const nextDirection = getNextDirection({ railCount, currentRail, direction }); - - return fillEncodeFence({ - railCount, - fence: fence.map(addChar(currentRail, letter)), - currentRail: currentRail + nextDirection, - direction: nextDirection, - string: nextString, - }); -}; - -/** - * @param {string} string - * @param {number} railCount - * - * @returns {string} - */ -export default function encodeRailFenceCipher(string, railCount) { - const fence = buildFence(railCount); - - const filledFence = fillEncodeFence({ - railCount, - fence, - currentRail: 0, - direction: DIRECTIONS.DOWN, - string: string.split(''), - }); - - return filledFence.flat().join(''); -} diff --git a/src/algorithms/cryptography/rail-fence-cipher/railFenceCipher.js b/src/algorithms/cryptography/rail-fence-cipher/railFenceCipher.js index e50b477a..7b58037e 100644 --- a/src/algorithms/cryptography/rail-fence-cipher/railFenceCipher.js +++ b/src/algorithms/cryptography/rail-fence-cipher/railFenceCipher.js @@ -1,50 +1,242 @@ +/** + * @typedef {string[]} Rail + * @typedef {Rail[]} Fence + * @typedef {number} Direction + */ + /** * @constant DIRECTIONS * @type {object} - * @property {number} UP - * @property {number} DOWN + * @property {Direction} UP + * @property {Direction} DOWN */ -export const DIRECTIONS = { UP: -1, DOWN: 1 }; +const DIRECTIONS = { UP: -1, DOWN: 1 }; /** - * @param {number} rows + * Builds a fence with a specific number of rows. * - * @returns {Array} + * @param {number} rowsNum + * @returns {Fence} */ -export const buildFence = (rows) => Array(rows) - .fill() +const buildFence = (rowsNum) => Array(rowsNum) + .fill(null) .map(() => []); /** - * @param {object} params - * @param {number} params.railCount - * @param {number} params.currentRail - * @param {number} params.direction + * Get next direction to move (based on the current one) while traversing the fence. * - * @returns {number} + * @param {object} params + * @param {number} params.railCount - Number of rows in the fence + * @param {number} params.currentRail - Current row that we're visiting + * @param {Direction} params.direction - Current direction + * @returns {Direction} - The next direction to take */ -export const getNextDirection = ({ railCount, currentRail, direction }) => { +const getNextDirection = ({ railCount, currentRail, direction }) => { switch (currentRail) { - case 0: return DIRECTIONS.DOWN; - case railCount - 1: return DIRECTIONS.UP; - default: return direction; + case 0: + // Go down if we're on top of the fence. + return DIRECTIONS.DOWN; + case railCount - 1: + // Go up if we're at the bottom of the fence. + return DIRECTIONS.UP; + default: + // Continue with the same direction if we're in the middle of the fence. + return direction; } }; /** - * Given a rail, adds a char to it - * if it matches a targetIndex. - * @callback charAdder - * @param {number} rail - * @param {currentRail} number + * @param {number} targetRailIndex + * @param {string} letter + * @returns {Function} */ +const addCharToRail = (targetRailIndex, letter) => { + /** + * Given a rail, adds a char to it if it matches a targetIndex. + * + * @param {Rail} rail + * @param {number} currentRail + * @returns {Rail} + */ + function onEachRail(rail, currentRail) { + return currentRail === targetRailIndex + ? [...rail, letter] + : rail; + } + return onEachRail; +}; /** - * @param {number} targetIndex - * @param {string} letter + * Hangs the characters on the fence. * - * @returns {charAdder} + * @param {object} params + * @param {Fence} params.fence + * @param {number} params.currentRail + * @param {Direction} params.direction + * @param {string[]} params.chars + * @returns {Fence} */ -export const addChar = (targetIndex, letter) => (rail, currentRail) => { - return (currentRail === targetIndex ? [...rail, letter] : rail); +const fillEncodeFence = ({ + fence, + currentRail, + direction, + chars, +}) => { + if (chars.length === 0) { + // All chars have been placed on a fence. + return fence; + } + + const railCount = fence.length; + + // Getting the next character to place on a fence. + const [letter, ...nextChars] = chars; + const nextDirection = getNextDirection({ + railCount, + currentRail, + direction, + }); + + return fillEncodeFence({ + fence: fence.map(addCharToRail(currentRail, letter)), + currentRail: currentRail + nextDirection, + direction: nextDirection, + chars: nextChars, + }); +}; + +/** + * @param {object} params + * @param {number} params.strLen + * @param {string[]} params.chars + * @param {Fence} params.fence + * @param {number} params.targetRail + * @param {Direction} params.direction + * @param {number[]} params.coords + * @returns {Fence} + */ +const fillDecodeFence = (params) => { + const { + strLen, chars, fence, targetRail, direction, coords, + } = params; + + const railCount = fence.length; + + if (chars.length === 0) { + return fence; + } + + const [currentRail, currentColumn] = coords; + const shouldGoNextRail = currentColumn === strLen - 1; + const nextDirection = shouldGoNextRail + ? DIRECTIONS.DOWN + : getNextDirection( + { railCount, currentRail, direction }, + ); + const nextRail = shouldGoNextRail ? targetRail + 1 : targetRail; + const nextCoords = [ + shouldGoNextRail ? 0 : currentRail + nextDirection, + shouldGoNextRail ? 0 : currentColumn + 1, + ]; + + const shouldAddChar = currentRail === targetRail; + const [currentChar, ...remainderChars] = chars; + const nextString = shouldAddChar ? remainderChars : chars; + const nextFence = shouldAddChar ? fence.map(addCharToRail(currentRail, currentChar)) : fence; + + return fillDecodeFence({ + strLen, + chars: nextString, + fence: nextFence, + targetRail: nextRail, + direction: nextDirection, + coords: nextCoords, + }); +}; + +/** + * @param {object} params + * @param {number} params.strLen + * @param {Fence} params.fence + * @param {number} params.currentRail + * @param {Direction} params.direction + * @param {number[]} params.code + * @returns {string} + */ +const decodeFence = (params) => { + const { + strLen, + fence, + currentRail, + direction, + code, + } = params; + + if (code.length === strLen) { + return code.join(''); + } + + const railCount = fence.length; + + const [currentChar, ...nextRail] = fence[currentRail]; + const nextDirection = getNextDirection( + { railCount, currentRail, direction }, + ); + + return decodeFence({ + railCount, + strLen, + currentRail: currentRail + nextDirection, + direction: nextDirection, + code: [...code, currentChar], + fence: fence.map((rail, idx) => (idx === currentRail ? nextRail : rail)), + }); +}; + +/** + * Encodes the message using Rail Fence Cipher. + * + * @param {string} string - The string to be encoded + * @param {number} railCount - The number of rails in a fence + * @returns {string} - Encoded string + */ +export const encodeRailFenceCipher = (string, railCount) => { + const fence = buildFence(railCount); + + const filledFence = fillEncodeFence({ + fence, + currentRail: 0, + direction: DIRECTIONS.DOWN, + chars: string.split(''), + }); + + return filledFence.flat().join(''); +}; + +/** + * Decodes the message using Rail Fence Cipher. + * + * @param {string} string - Encoded string + * @param {number} railCount - The number of rows in a fence + * @returns {string} - Decoded string. + */ +export const decodeRailFenceCipher = (string, railCount) => { + const strLen = string.length; + const emptyFence = buildFence(railCount); + const filledFence = fillDecodeFence({ + strLen, + chars: string.split(''), + fence: emptyFence, + targetRail: 0, + direction: DIRECTIONS.DOWN, + coords: [0, 0], + }); + + return decodeFence({ + strLen, + fence: filledFence, + currentRail: 0, + direction: DIRECTIONS.DOWN, + code: [], + }); };