Numeric Enigma Machine - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: Python Coding (https://python-forum.io/forum-7.html) +--- Forum: General Coding Help (https://python-forum.io/forum-8.html) +--- Thread: Numeric Enigma Machine (/thread-41855.html) |
Numeric Enigma Machine - idev - Mar-27-2024 I have come across Lockheed Martin's problem from the 2018 CodeQuest, a numeric Enigma machine: [attachment=2797] I am wondering what the best way to code the rotors and their mappings is, considering that "rotation" needs to be applied- similar to how an odometer works. My first supposition was using lists/arrays: rotor1 = [1, 3, 6, 0, 5, 4, 8, 7, 9, 2] rotor2 = [0, 3, 5, 2, 6, 9, 1, 4, 8, 7] rotor3 = [5, 9, 1, 7, 3, 8, 0, 2, 4, 6] rotor4 = [1, 6, 5, 2, 9, 0, 7, 4, 3, 8]In this setup, the index value represents the input side of the rotor and the value represents the output side of the rotor. So, if you want to input 1 into rotor 1, you would get an output of 3. This works okay, if the rotors are all in position 0, as depicted. However, once rotation comes into play, this doesn't seem to work anymore. For instance, if I use rotors 1,2,3 all initially set to their 0 position (as depicted), and if I want to encrypt the message 1234567890. I know, as a test case, that the output should be 4805344463. I do get the first digit encrypted correctly, to 4. However, then the rightmost rotor (rotor 3 in this case) must "shift down one position." I used slicing to shift the arrays: shifted_rotor = rotor[1:] + rotor[:1]I have also tried going in the other direction: shifted_rotor = rotor[-1:] + rotor[:-1]However, after applying the aforementioned shift, in either direction, and then attempting to encrypt the next digit in the message sequence, which would be 2, I don't get 8, as I should get. This has lead me to believe that, perhaps, using the rotor config/mappings as I have laid them out won't work. Is anyone familiar with this problem? Can anyone offer me any help or advice on this? RE: Numeric Enigma Machine - bowlofred - Mar-27-2024 When you shift the array, you are changing how the inputs map to the identical outputs. But I think in your problem thats not how rotors work. As an example the first line on R1 maps input 0 to output 1. With your shift, you would now map input 1 to output 1. But presumably the output also moves, so that line (moved down 1) would now map input 1 to output 2. Given that, I would probably model the rotors as an addition mod 10. The first rotor line would then be [+1]. When the rotor is in position 0, input 1 becomes output 2. When the rotor is in position 7, input 8 becomes output 9. When the rotor is in position 8, input 9 becomes output 0. RE: Numeric Enigma Machine - idev - Mar-28-2024 I am not sure I understood that correctly, but I am going to try something and see how it works out. (Mar-27-2024, 10:52 PM)bowlofred Wrote: When you shift the array, you are changing how the inputs map to the identical outputs. But I think in your problem thats not how rotors work. RE: Numeric Enigma Machine - idev - Mar-28-2024 I have revisited all of my previous tinkering and I have tried a new codebase, which seems to be progress. def enigma(starting_rotors, starting_positions, message): # Define the rotors and reflector rotors = { 1: [1, 3, 6, 0, 5, 4, 8, 7, 9, 2], 2: [0, 3, 5, 2, 6, 9, 1, 4, 8, 7], 3: [5, 9, 1, 7, 3, 8, 0, 2, 4, 6], 4: [1, 6, 5, 2, 9, 0, 7, 4, 3, 8] } reflector = [3, 6, 8, 0, 5, 4, 1, 9, 2, 7] selected_rotors = [rotors[i] for i in starting_rotors] rotor_positions = starting_positions[:] # Copy to avoid modifying the original encrypted_message = "" def rotate_rotors(): # Increment the rightmost rotor's position rotor_positions[-1] += 1 # Check for rotation carry-over for i in reversed(range(1, len(rotor_positions))): if rotor_positions[i] > 9: rotor_positions[i] = 0 rotor_positions[i - 1] += 1 for digit in message: digit = int(digit) # Forward pass for i, rotor in enumerate(selected_rotors): adjusted_input = (digit + rotor_positions[i]) % 10 digit = rotor[adjusted_input] # Reflection digit = reflector[digit] # # Reverse pass # for i in reversed(range(len(selected_rotors))): # rotor = selected_rotors[i] # position = rotor_positions[i] # # Find the digit mapping # digit = (rotor.index((digit - position) % 10) + position) % 10 # Reverse pass for i in reversed(range(len(selected_rotors))): rotor = selected_rotors[i] digit = rotor.index(digit) # Find the index directly, reflecting the true reverse pass logic encrypted_message += str(digit) rotate_rotors() return encrypted_message # Example usage starting_rotors = [1, 2, 3] # Rotors to use starting_positions = [0, 0, 0] # Initial positions of the rotors message = "1234567890" # The message to encrypt # Encrypt the message encrypted_message = enigma(starting_rotors, starting_positions, message) print(f"Encrypted message: {encrypted_message}")I haven't got something quite right, though. The first two digits are correctly encrypted, but after that all other digits are encrypted incorrectly. The expected output is: 4805344463 The output I am getting is: 4444626524 RE: Numeric Enigma Machine - bowlofred - Mar-28-2024 Think of it this way. Imagine you have a very simplified rotor: [0, 1, 2, 3, 4] This is five straight lines. 0->0, 1->1 etc... What happens when the rotor is moved one click down? Nothing at all. The 0->0 line is now the 1->1 line, and the previous 4->4 line becomes the new 0->0 line. This "null" rotor doesn't change anything when it is moved. What happens in your program? You advance the input, but not the output. If your rotor is in postion 1, then you run: adjusted_input = (digit + rotor_positions[i]) % 10 digit = rotor[adjusted_input]If we pass in digit=0 to this, we get adjusted_input = 1 and then the new digit becomes 1. This is incorrect. You might consider testing your program with a set of null rotors (each is list(range(9))), and not using the reflector. If your code is correct, you should get the input string back without encryption. RE: Numeric Enigma Machine - bowlofred - Mar-28-2024 And that's just on the forward pass. You're not applying any rotation at all on the reverse pass. RE: Numeric Enigma Machine - idev - Mar-28-2024 The reverse pass isn't supposed to have rotation. The rotors get set to their initial position. The first digit in the sequence is encrypted (forward, reflector, backward). Then, rotation happens. The second digit is encrypted (forward, reflector, backward)...rotation...encryption of the third digit, and so on. (Mar-28-2024, 06:07 AM)bowlofred Wrote: And that's just on the forward pass. You're not applying any rotation at all on the reverse pass. RE: Numeric Enigma Machine - idev - Mar-28-2024 Okay, you made an interesting point (if it's a valid one) and I decided to try your test method: # Define the rotors and reflector rotors = { 0: list(range(10)), 1: list(range(10)), 2: list(range(10)), 3: list(range(10)), 4: list(range(10)) } reflector = [3, 6, 8, 0, 5, 4, 1, 9, 2, 7] def apply_rotor(input_digit, rotor, position): # Adjust input based on the rotor's position adjusted_input = (input_digit - position) % 10 # Apply the mapping output_digit = rotor[adjusted_input] return output_digit def encrypt_digit(digit, rotor_positions, selected_rotors): # Convert digit from string to integer digit = int(digit) # Forward pass through the selected rotors for i in range(len(selected_rotors)): digit = apply_rotor(digit, selected_rotors[i], rotor_positions[i]) print(f"Forward - Digit after rotor {i} : {digit}") # Reverse pass through the selected rotors for i in reversed(range(len(selected_rotors))): # For reverse path, adjust input based on the rotor's position before applying the mapping adjusted_input = (digit + rotor_positions[i]) % 10 digit = selected_rotors[i].index(adjusted_input) print(f"Reverse - Digit after rotor {i} : {digit}") # Convert digit back to string for output return str(digit) def rotate_rotors(rotor_positions): # Rotate the rightmost rotor after encrypting each digit rotor_positions[-1] += 1 print("Rotating rotors...") print("Rotor positions after rotation:", rotor_positions) for i in reversed(range(len(rotor_positions) - 1)): if rotor_positions[i + 1] > 9: # Check if the right rotor completed a revolution rotor_positions[i + 1] = 0 # Reset the right rotor rotor_positions[i] += 1 # Rotate the next rotor to the left print("Rotor positions after reset:", rotor_positions) def encrypt_sequence(sequence, selected_rotors_indices, starting_positions): # Extract the selected rotors based on indices provided selected_rotors = [rotors[i] for i in selected_rotors_indices] rotor_positions = starting_positions[:] # Copy to avoid modifying the original starting positions encrypted_sequence = "" for digit in sequence: # Encrypt each digit in the sequence encrypted_digit = encrypt_digit(digit, rotor_positions, selected_rotors) encrypted_sequence += encrypted_digit # Rotate rotors after encrypting each digit rotate_rotors(rotor_positions) return encrypted_sequence sequence = "1234567890" # The sequence to be encrypted selected_rotors_indices = [0, 1, 2] # Indices of the rotors selected for the encryption starting_positions = [0, 0, 0] # Starting positions of the selected rotors # Encrypt the sequence encrypted_sequence = encrypt_sequence(sequence, selected_rotors_indices, starting_positions) print(f"Encrypted sequence: {encrypted_sequence}")With the code changes I have implemented, using your debugging strategy, I have been able to get the same output from the input, albeit with a slight quirk: For some reason, after the input passes through "rotor 2" on the forward pass, it always comes out as 1. Yet, that is always resolved and I am getting the correct output. Still, I am wondering why that occurs and if it is a cause for further debugging.(Mar-28-2024, 04:44 AM)bowlofred Wrote: Think of it this way. Imagine you have a very simplified rotor: RE: Numeric Enigma Machine - idev - Mar-28-2024 As an update to my previous reply, there seems to be an issue with apply_rotor(input_digit, rotor, position):
RE: Numeric Enigma Machine - idev - Mar-29-2024 In the end, I realized that the issue is how I was configuring my rotors. I adjusted the configuration to something that was more appropriate and enabled the whole rotor (input, output, wiring) to rotate and that resolved the biggest of obstacles with this project. I just had to work through a few smaller issues, but ended up getting the whole thing coded properly. |