Question 1: (Turn in on paper) Design a machine that can compute the exclusive-or of two inputs. Your machine can use the normal control and inverted control transistors we introduced in class.
(or (and a (not b)) (and (not a) b))
We saw how to make or out of and and not in class using De Morgan’s Law:
(or a b) = (not (and (not a) (not b)))
So, the logical expression above is equivalent to:
(not (and (not (and a (not b))) (not (and (not a) b))))
This could be implemented using our normal control (AND) and inverted control (NOT) transistors.
Question 2: Define an xor-bits function (Scheme procedure) that takes two bits as parameters and evaluates to 1 if exactly one of the parameters is 1 and evaluates to 0 otherwise. (Note that this function is slightly different from the one defined in Chapter 6, since the inputs are not Booleans.)
(define (xor-bits a b) (if (eq? a b) 0 1))
Question 3: (Turn in on paper) Design a Turing Machine that computes the bitwise XOR of two input sequences. The input is a tape containing a string in the language described by this BNF grammar:
Input ::= BitSeq + BitSeq #
BitSeq ::= ε
BitSeq ::= 0 BitSeq
BitSeq ::= 1 BitSeq
When your machine reaches the Halt state, the tape should contain BitSeq # (its okay if there is more stuff to the right of the #) where the output BitSeq is the XOR of the two inputs. For example, if the input is 0110+1111# the output should be 1001#. If the two input sequences are of different lengths, your machine should end in an Error state.
Your answer should show the design of a Turing Machine that solves the problem. The fewer states you use in your answer, the better.
We write each rule as
(Current State, Read Symbol) → (Next State, Write Symbol, Direction)
Here are the rules:
; Read the current bit from the left input.
(ReadBit, 0) → (GotZero, X, R)
(ReadBit, 1) → (GotOne, X, R)
; If it is a +, that means we’ve reached the end of the left input and should try to produce the final output.
(ReadBit, +) → (ProduceOutput, #, R)
; In GotZero, we are remembering that we read a 0, and searching the tape for the +.
(GotZero, 0) → (GotZero, 0, R)
(GotZero, 1) → (GotZero, 1, R)
(GotZero, +) → (GotZeroAfter, +, R)
; In GotZeroAfter, we are remembering that we read a 0, and searching the tape for the first unmatched bit in the right input sequence.
(GotZeroAfter, A) → (GotZeroAfter, A, R)
(GotZeroAfter, B) → (GotZeroAfter, B, R)
; Once we found it, replace it with the XOR result and proceed with the next bit.
(GotZeroAfter, 0) → (FindNextBit, A, L) ; 0 XOR 0 = 0 (A)
(GotZeroAfter, 1) → (FindNextBit, B, L) ; 0 XOR 1 = 1 (B)
; GotOne and GotOneAfter are similar to GotZero and GotZeroAfter, but keep track of the first input bit begin a 1.
(GotOne, 0) → (GotOne, 0, R)
(GotOne, 1) → (GotOne, 1, R)
(GotOne, +) → (GotOneAfter, +, R)
(GotOneAfter, A) → (GotZeroAfter, A, R)
(GotOneAfter, B) → (GotZeroAfter, B, R)
(GotOneAfter, 0) → (FindNextBit, B, L) ; 1 XOR 0 = 1 (B)
(GotOneAfter, 1) → (FindNextBit, A, L) ; 1 XOR 1 = 0 (A)
; FindNextBit moves left to find the +.
(FindNextBit, A) → (FindNextBit, A, L)
(FindNextBit, B) → (FindNextBit, B, L)
(FindNextBit, +) → (FindNextBitLeft, +, L)
; FindNextBitLeft moves left to find the rightmost X (which marks the last bit processed).
(FindNextBitLeft, 0) → (FindNextBitLeft, 0, L)
(FindNextBitLeft, 1) → (FindNextBitLeft, 1, L)
(FindNextBitLeft, X) → (ReadBit, X, R)
; In ProduceOutput, we convert the A/B to 0/1, moving R across the tape until finding the #
; If we see anything other than an A, B, or #, the inputs were unbalanced (different lengths) and
; we end with an error.
(ProduceOutput, A) → (ProduceOutput, 0, R)
(ProduceOutput, B) → (ProduceOutput, 1, R)
(ProduceOutput, #) → (Halt, #, Halt) ; Yipee! Everything worked out!
Note: I have not actually tried executing this machine, so there are problem some bugs in it. Finding and fixing them, or producing a significantly shorter TM that correctly solves this problem, is worth a Gold Star bonus!
a. Define a procedure, string-to-baudot, that takes a string and transforms it into a list of Baudot codes.
(define (string-to-baudot string) (map char-to-baudot (string->list string)))
b. Write the inverse function, baudot-to-string, that takes a list of lists of baudot codes and transforms it back into a string.
(define (baudot-to-string baudot-list) (list->string (map baudot-to-char baudot-list)))
Question 5: Define a procedure rotate-wheel that takes as input a wheel (which is a represented as a list). It should return the wheel rotated once. For example, (rotate-wheel (list 1 0 0 1 0)) should evaluate to (0 0 1 0 1). Although all the wheels in our simulated Lorenz cipher machine have five bits, your rotate-wheel procedure should work for any non-empty list.
(define (rotate-wheel wheel) (append (cdr wheel) (list (car wheel))))
Question 6: Define a procedure rotate-wheel-by that takes two inputs: a wheel as the first input and a number as the second. It should output result of rotating the wheel the number of times passed in. For example, (rotate-wheel-by (list 1 0 0 1 0) 2) should evaluate to (0 1 0 1 0) and (rotate-wheel-by wheel 0) should evaluate to wheel.
(define (rotate-wheel-by wheel n) (if (= n 0) wheel (rotate-wheel-by (rotate-wheel wheel) (- n 1))))
Another approach is to recall the n-times procedure we used in PS3:
(define (rotate-wheel-by wheel n) ((n-times rotate-wheel n) wheel))
Question 7: Define a procedure rotate-wheel-list that takes a list of wheels (like K-wheels) as its input and evaluates to a list of wheels where each of the wheels in the parameter list of wheels has rotated once. For example, (rotate-wheel-list K-wheels) should evaluate to ((1 0 1 0 1) (1 0 0 1 0) (0 0 1 0 1) (1 1 0 1 1) (0 0 0 1 1)).
(define (rotate-wheel-list wheellist) (map rotate-wheel wheellist))
Question 8: Define a procedure rotate-wheel-list-by that takes a list of wheels and a number as parameters, and evaluates to a list where each of the wheels in the parameter list of wheels has rotated the number parameter times. For example, (rotate-wheel-list-by K-wheels 5) should evaluate to the same list as K-wheels.
(define (rotate-wheel-list-by wheellist n) ((n-times rotate-wheel-list n) wheellist))
(define (rotate-wheel-list-by wheels n) (map (lambda (wheel) (rotate-wheel-by wheel n) wheels))
Question 9: Define a function wheel-encrypt that takes a Baudot-encoded letter (a list of 5 bits) and a list of wheels as parameters. The function should xor each bit of the Baudot list with the first value of its respective wheel and return the resulting list. For example, (wheel-encrypt (list 0 0 0 1 1) K-wheels) should produce (1 0 1 0 0). Your wheel-encrypt procedure should have running time in Θ(n) where n is the length of the list passed as the first parameter to wheel-encrypt.
(define (wheel-encrypt baudot-letter wheel-list) (map xor-bits baudot-letter (map car wheel-list)))
Question 10: Define a function called do-lorenz that takes a list of Baudot values, the K wheels, S wheels and M wheel. The function should encrypt the first Baudot code with the K and S wheels, then recursively encrypt the rest of the Baudot codes with the wheels rotated. The function should return the encrypted values in the form of a list of Baudot values.
(define (do-lorenz code k-wheels s-wheels m-wheel) (if (null? code) code (cons (wheel-encrypt (wheel-encrypt (car code) k-wheels) s-wheels) (do-lorenz (cdr code) (rotate-wheel-list k-wheels) (if (= (car m-wheel) 1) (rotate-wheel-list s-wheels) s-wheels) (rotate-wheel m-wheel)))))
Question 11: Define a function lorenz-encrypt that takes four parameters: a string and three integers. The integers represent the starting positions of the three wheels, respectively. The function should call do-lorenz with the string converted to Baudot and the wheels rotated to the correct starting positions. The function should return the ciphertext in the form of a string.
(define (lorenz-encrypt string n1 n2 n3) (if (null? string) string (baudot-to-string (do-lorenz (string-to-baudot string) (rotate-wheel-list-by k-wheels n1) (rotate-wheel-list-by s-wheels n2) (rotate-wheel-by m-wheels n3)))))
Question 12: Define a procedure, brute-force-lorenz, that takes two inputs: a procedure and a ciphertext string. Your procedure should evaluate lorenz-encrypt on the ciphertext for all 125 possible starting positions, passing in the resulting string to the input procedure. For example, evaluating
(brute-force-lorenz (lambda (s) (begin (display s) (newline))) ciphertext)
should print out the 125 possible decoded strings for the ciphertext input (defined in lorenz.rkt). If your procedure works correctly, the one message generated that looks like sensible English is the plaintext message.
(define (for index end proc) (if (>= index end) (void) ; this evaluates to no value (begin (proc index) (for (+ index 1) end proc)))) (define (brute-force-lorenz proc ciphertext) (for 0 5 (lambda (k-step) (for 0 5 (lambda (s-step) (for 0 5 (lambda (m-step) (proc (lorenz-encrypt ciphertext k-step s-step m-step)))))))))
Here is another without using for:
(define (brute-force-lorenz proc ciphertext) (define (brute-force-lorenz-helper string position1 position2 position3) (if (< position1 5) (if (< position2 5) (if (< position3 5) (begin ; need begin to do evaluate both expressions (proc (lorenz-encrypt string position1 position2 position3)) (brute-force-lorenz-helper string position1 position2 (+ 1 position3))) (brute-force-lorenz-helper string position1 (+ 1 position2) 0)) (brute-force-lorenz-helper string (+ 1 position1) 0 0)))) (brute-force-lorenz-helper ciphertext 0 0 0))
The encrypted message: AT THE TIME, I HAD NO THOUGHT OR KNOWLEDGE OF
COMPUTERS IN THE MODERN SENSE, AND HAD NEVER HEARD THE TERM USED EXCEPT
TO DESCRIBE SOMEBODY WHO DID CALCULATIONS. - THOMAS FLOWERS.