Logo
  • Home
  • Classes
  • Conveying Computing
  • Exams
  • Fractal Gallery
  • Guides
  • Problem Sets
  • Syllabus

PS4 – Comments

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.

We got many interesting answer to this, some of which I’ll show in class (5 October). A simple solution is to view xor as the logical expression,

   (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.






There are definitely simpler and better ways to implement XOR, but this is the most straightforward transformation starting from the logical expression.

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.)

A simple way to define xor is:

   (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 need to keep track of the results as we XOR the bits, so it is helpful to add some extra symbols to the tape alphabet. We use A to represent a position that should hold a 0 when we are done, and B to represent a position that should hold a 1 when we are done. Our basic strategy is to reach the bit in the first bit sequence and replace it with X. We use a state to keep track of whether that bit was a 0 or 1. Then, we search the tape for the + and then the first 0 or 1 following it, and replace that with A or B. Then, we search back to the left until we find the rightmost X, and continue to the next bit.

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!

Question 4:
a. Define a procedure, string-to-baudot, that takes a string and transforms it into a list of Baudot codes.

The easiest way is to use map:

(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.

We use append (defined in Chapter 5 as list-append, or the built-in append) to put the first wheel at the end of the rest of the wheels. Since both inputs to append must be lists, we use (list (car wheel)) to make a singleton list containing only the first element.

(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.

One way is to use ntimes:

(define (rotate-wheel-list-by wheellist n)
  ((n-times rotate-wheel-list n) wheellist))

Another approach:

(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.

There are many different ways to do this. One good way is to use the for procedure we defined Class 16:

(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
.

Print Friendly Print Get a PDF version of this webpage PDF

Leave a Reply Cancel reply

You must be logged in to post a comment.

cs1120 | RSS | Comments RSS | Book | Using These Materials | Login | Admin | Powered by Wordpress