## Decryption Some messages have been intercepted. Each message has been disguised using a classic cipher. Your job is to work out what each message says. Work through the messages in order. Use the [Cipher Toolkit](https://app.joshuacussen.com/cipher-toolkit) to help you. > [!info] Blocks of five > It is common to group the letters of encrypted messages in blocks of five. ### Message 1: Caesar Left scribbled in the back of a library book. ``` GURCH ECYRZ HFUEB BZFNG ORGJR RAGJB YNETR BENAT REBPX FARNE NFYRR CVATS EBTNA QNIRE LPBAS HFRQF ANVY ``` ### Message 2: Substitution Passed between two students during a fire drill. ``` YGKZN RQFEOFU HTFUXOFL WGKKGVTR ZVTSCT UGSRTF WXEATZL YKGD ZIT LSTTHOFU VQSKXL WN ZIT SOUIZIGXLT ``` ### Message 3: Atbash Mailed anonymously to the head of the student newspaper. ``` HVEVM BVOOL DVOVK SZMGH NZIXS VWKZH GGSVY ILPVM ULFMG ZRMZM WMLYL WBHVV NVWGL MLGRX VLIXZ IVEVI BNFXS ``` ### Message 4: Rail fence Found pinned to the noticeboard outside the staffroom. ``` TATEE CTOEN YHKNA ETELS ELGHM ULLOA AEEGO SNNKB RINER SPART DBUAO EEPCE OMIEO PRDEU SLRVI FBNUD E ``` ### Message 5: Transposition Found scratched into the side of a desk. ``` TBIET GUIRE SGUOS NYHBR CEHSO EBATS AIEBE LAODT PUEBL HTDDM AWYFU EYISN LOEYB EPROG FNISC GPEND REATN ``` ### Extension: Vigenère Found in the French office bin alongside a dead `BUMBLEBEE`. ``` BGMHYMGMGFHFHZPEIRUIDUZMTIABHPFCIEWPPQXZELSSYHBFIPECEREIZFOKSIIOBAVDIDSPMYOUT RHJSSAAUEIOWIFXEGCSNIQQNKTSIMZIT ``` ## Caesar cipher program You are now going to write a program that can encrypt and decrypt messages using a Caesar cipher—and you are going to design it following the engineering principles of: - modularity; - comprehensive documentation; and - test-driven development. ### Documentation first [[Documentation]] is an essential part of software development. Before the body of each [[subprogram]], you'll find some [[Documentation#Header documentation|header documentation]] in a docstring (`"""`). It's called header documentation as it goes at the head of the subprogram. > [!info] Docstrings > Docstrings are a special kind of string in Python, delimited with triple quotation marks: `"""`. They appear underneath the subprogram [[signature]] and are used for header documentation. They work a bit like a comment—the code doesn't run any differently whether the docstring is there or not—but because they always sit in the same place, right under the signature, anyone reading your code knows exactly where to look for the contract a subprogram promises to fulfil. This project uses a documentation style called [[6P documentation]]. The Ps stand for: - procedure; - parameters; - purpose; - product; - preconditions; and - postconditions. This documentation forms a contract between the program and its users: what the subprogram promises to do, what it assumes is true before it runs, and what will be true after it runs. For the first few subprograms you'll write, the documentation is provided for you. Writing documentation for the other subprograms isn't just busywork—deciding what your code is allowed to assume and what it will do is an important part of good software design. It can make the difference between code that seems to work and code you can trust. Read all the documentation carefully as it tells you the contract your code needs to fulfil. ### Starter code Download [this file](https://raw.githubusercontent.com/joshuacussen/labs/refs/heads/main/one-shots/code-breaking/caesar-starter.py) or copy and paste its contents into a new Python file in an IDE. You'll use it for testing your code later on. This file is very long—don't be too intimidated by it! You can ignore everything in `TESTING FRAMEWORK` as you won't be making any changes to this code. You will, however, run these tests after you implement each subprogram. You should only move on to the next task when all the tests for the current task pass. If you don't have access to a Python IDE on your computer, you could use [Programiz](https://www.programiz.com/python-programming/online-compiler/). Save and run your file now. Nothing should happen yet. When you start implementing a subprogram, remove the `pass` line. ### Step 1: `shift_lower()` This subprogram does the heart of the hard work—shifting a single lowercase letter along the alphabet, wrapping around from `'z'` back to `'a'` when needed. Every other subprogram eventually leans on this one (and its partner `shift_upper()`), so it's worth taking your time here. You are given a complete signature and docstring with 6P documentation, and some of the code is already written for you—the comments walk you through the three things this subprogram needs to do. Your job is to fill in the two `TODO` sections. Each `TODO` includes a hint about how to write that line, so read them carefully. Note that there are no restrictions on the value of `shift`—it could be positive, negative, or 0. If you are struggling to visualise how the shift should work, look at the Caesar cipher wheel on the [Cipher Toolkit](https://app.joshuacussen.com/cipher-toolkit). When you have implemented this subprogram, test it by uncommenting `run_tests(shift_lower_tests)` in your main program, then running the program. All tests should pass before you move on. ### Step 2: `shift_upper()` Now do the same job again, but for uppercase letters this time. There's no working code here, just the docstring and three hint comments in the same shape as the ones you just used for `shift_lower()`—use them to guide you, and try to write this one with less help than last time. When you have implemented `shift_upper()`, test it by uncommenting `run_tests(shift_upper_tests)`. All tests should pass before you move on. > [!info] Why split it into two? > You might wonder why we don't just have one `shift_letter()` subprogram that handles both cases itself. Splitting it into `shift_lower()` and `shift_upper()` means each subprogram only has to do one job—shift a letter of one specific case—rather than juggling "which case is this?" and "how do I wrap it?" at the same time. You'll bring the two together in the next step. ### Step 3: `shift_letter()` You now have two subprograms that can each shift a letter of a known case. `shift_letter()` doesn't do any shifting itself—its whole job is to look at the letter it's been given, decide whether it's uppercase or lowercase, and call the matching subprogram. This is a good example of a subprogram whose job is to _delegate_ rather than to _do_. Read the docstring, then implement it—it should only take a couple of lines. When you have implemented `shift_letter()`, test it by uncommenting `run_tests(shift_letter_tests)`. All tests should pass before you move on. ### Step 4: `is_letter()` We can now shift individual letters. Yay! We want to move on to shifting whole strings of text, but before we can do that we need to write another helper subprogram. This subprogram checks an individual character to determine if it is a letter or not. We need this because part of the contract with `shift_letter()` is that it only accepts letters. Once again, complete 6P documentation is provided for you and you just need to write the code. The postconditions might look like a complete solution—but they aren't, they are just pseudocode. When you have implemented `is_letter()`, test it by uncommenting `run_tests(is_letter_tests)`. All tests should pass before you move on. ### Step 5: `shift_string()` `shift_letter()` only knows how to deal with one letter. This subprogram applies it to a whole string of text—and has to make a decision that `shift_letter()` doesn't: what happens to characters that aren't letters? Handily, we already have a way to determine if a character is a letter or not. Some of this documentation is filled in for you already. Before writing any code, fill in the `Purpose`, `Preconditions`, and `Postconditions` sections yourself, in the comments where it says `TODO`. Test your code by uncommenting `run_tests(shift_string_tests)`. All tests should pass before you move on. ### Step 6: `encrypt()` and `decrypt()` These two don't need any new logic—they just call `shift_string()` with the right sign. Complete the header documentation first, then implement `encrypt()` and `decrypt()`. Finally, test them by uncommenting `run_tests(encrypt_tests)` and `run_tests(decrypt_tests)` respectively. You should now do some of your own testing in the main program. Try doing your own round-trip encryption and decryption—prove to yourself that your subprograms work. ### Step 7: `main()` You've now built everything you need. This last subprogram doesn't do any cipher logic at all—it just asks the user what they want to do, and calls the subprograms you've already written. Notice that it doesn't know or care _how_ encryption works—that's the point of building your program this way. Write your 6P documentation then implement `main()`. Test the usability of your program by uncommenting `main()` and running the whole program. Encrypt a word, note the result, then run it again and decrypt that result with the same shift—you should get your original word back. ### Extension If your program is working and tested, try one or more of these: 1. **Validate the shift.** What happens right now if someone types `"five"` instead of `5` when asked for the shift amount? Add a check that asks again if the input isn't a whole number. 2. **Crack a Caesar cipher without knowing the shift.** Write a subprogram that tries all 26 possible shifts on a piece of ciphertext and prints every result, so a human can spot which one is readable English. 3. **Additional ciphers**. Have a go at creating encryption and decryption tools for one of the other ciphers.