18 minute read

Stanford professor Persi Diaconis described an event when a psychologist from the California prison system brought encoded messages obtained from inmates to the Statistics Department’s drop-in consulting service and asked if the messages could be decrypted. The problem was tackled by Stanford student Marc Coram, who guessed that the message was encoded using a simple substitution cipher. Using the Markov Chain Monte Carlo (MCMC) method, Coram’s algorithm was able to correctly decipher the message without enumerating through all of the possible letter combinations. This paper presents an algorithm in Python that replicates Coram’s approach and evaluates some minimum conditions required for the technique to be successful. The Python code used for this project is included as an appendix to this report.

Cryptography Background

Text encryption is the process of turning intelligible text into unintelligible text, and text decryption is the opposite process. A cipher is a pair of algorithms the create the encrypted and decrypted texts. The operation of the cipher is controlled by the both the algorithm and a key. The cipher used in the inmates’ messages substituted a different symbol for each letter of the alphabet, punctuation, and spaces between words. Instead of using symbols in this project, letters were randomly chosen to substitute for other letters in a string of text.

Monte Carlo Markov Chain Method

When faced with the challenge of deciphering text encoded with a substitution cipher, an initial approach might be to attempt to match letter frequencies in the encoded text with average letter frequencies for the English language. Chen tried this approach and found that it had limited success, correctly decoding just 16 out of 26 letters. Because of this, Coram instead used two letter transition frequencies.

Two Letter Transitions

For this project, I obtained a text file of War and Peace from www.textfiles.com and counted the number of times each of the two letter combinations appeared. All numbers, punctuation, and spaces were combined into a single category of a non-letter. I used the space character to represent these non-letters in order to have the resulting decrypted text be more readable. I chose War and Peace as the reference text because of its length. A longer text produces a larger sample size for calculating statistics on two-letter frequencies. Additionally, a novel is a better choice than a dictionary of the English language because the resulting two-letter frequencies will represent what would be expected in natural language. For example, since the word “the” is the most common word in written English, the two-letter pairs of TH and HE should be relatively frequent.

This is a plot of the normalized transition probabilities that I used to check the accuracy of the Python function. The QU transition stands out, and the TH and HE frequencies are high as expected from the common use of the word “the”. T is the most common letter to start a word, and D and Y are the most common letters to end a word. These results match expectations, so the function appears to be correct.

Metropolis-Hastings Algorithm

A state space of the Markov Chain in this application is represented by a unique sequence of each of the 26 letters. For example, if the original text was encoded with a key where only the first two letters were swapped, that would be represented by the state BACDEF…XYZ. Since the order of substitution for encrypted text is unknown, the correct decryption key is one of 26! possible states. Iterating through each of the 26! possibilities is infeasible, and that is where the MCMC approach comes to the rescue.

The Metropolis-Hastings Algorithm is a technique for randomly sampling from the possible state spaces in such a way as to efficiently converge to the correct decryption. As described in Chen, for each successive pair of characters , the expression records the number of times each particular pair of characters appears in the reference text War and Peace. Similarly, records the number of times each two-letter pair occurs in the target text after it was decrypted with key x from the state space. For a particular decryption key x, its score function is as follows~\cite{chen}.

When I implemented this expression in Python, there were significant numerical errors due to multiplying large numbers. Therefore, I modified the expression so that the target text letter count is multiplied by the logarithm of the reference text count. I then added the products of all of the two-letter pairs since adding logarithms is equivalent to successive multiplication. For example, if the number of times AB appeared in the decrypted text was 12 and the number of times AB appeared in War and Peace was 3,437, then 12 was multiplied by log(3437). This product was added to the product of every other possible two-letter pair for a total score for that particular decryption key. This logic is implemented in the following code where in the last line, key_score represents the score for the decryption key, v represents a two-letter count in the target text, and trans_counts[k] represents the same two-letter pair count in War and Peace.

for k,v in target_counts.items():
        if k in trans_counts:
            key_score += v * math.log(trans_counts[k])

After calculating a score for the decryption key, a proposal key is generated by randomly choosing two letters from the key and swapping their positions. So if A and G are randomly chosen and A mapped to S and G mapped to Y, then in the proposed key, A maps to Y and G maps to S. A score for the proposed key is calculated and compared to the current key. If the proposed key’s score is greater than the current key’s score, the score ratio is greater than one, and it produces a decrypted text with letter transitions that more closely match the reference text, so it is accepted as the new decryption key. If the score ratio is less than one, then the proposed key is accepted with a probability equal to the score ratio. This allows lower probability keys to be explored during the random walk. In Python, it is implemented by drawing a random number from a uniform distribution ranging from 0 to 1 and comparing it to the score ratio. The Python implementation of this logic is below. Note that the exponent of the difference of the scores is required to negate the logarithm of the two-letter count described earlier.

#generate a proposed decryption key and get scores
proposed_decrypt_key = get_proposed_key(current_decrypt_key)
current_score = score(current_decrypt_key, encoded_text, trans_counts)
proposed_score = score(proposed_decrypt_key, encoded_text, trans_counts)

#calculate the acceptance probability
ap = min(1, math.exp(proposed_score - current_score))

#generate a random number between 0 and 1
runif = np.random.uniform()

#accept the proposed key if the random number is less than the acceptance probability
if runif <= ap: accept_proposed_key = True
else: accept_proposed_key = False
if accept_proposed_key: current_decrypt_key = proposed_decrypt_key

Results

In this section, I will test the algorithm on two different short texts, which I refer to as the target texts, using War and Peace as the reference text for letter transition probabilities. The first target text is taken from a passage in the movie Strange Brew. I chose this text to represent typical spoken English, and it contains 12 sentences, 202 words, and 944 characters. I will repeat the test on the movie passage using increasingly shorter segments of the passage to determine the approximate minimum length of text needed. I will also test the algorithm on the movie massage by using a shorter reference text Alice’s Adventures in Wonderland. The other target text is a portion of the decrypted text obtained from the prison psychologist. Because this text contains slang, mis-spellings, and several words in Spanish, it should provide a challenge to the decryption algorithm. For this target text, I will again use War and Peace as the reference text.

import numpy as np
import math
import pandas as pd
def get_trans_counts(plaintext):
    '''takes .txt file and creates a dictionary with two-letter combinations as keys and their counts as values.
    returns a dictionary of the counts in the form {AB:343, AC:112, etc}'''
    trans_counts = {}
    chars = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']
    f = open(plaintext)
    wp = []
    for line in f:
        wp.append(line)
    f.close()
    for line in wp:
        #convert to upper case
        line = str.upper(line)
        #remove the \n newline characters at the end of the line
        line = line[0:len(line)-1]
        #count the number of two letter pairs
        for i in range(len(line)-1):
            twoletter_key = line[i] + line[i+1]
            #two consecutive letters
            if (line[i] in chars) & (line[i+1] in chars):
                if twoletter_key not in trans_counts: trans_counts[twoletter_key] = 1
                else: trans_counts[twoletter_key] += 1
            #non-letter followed by a letter
            elif (line[i] not in chars) & (line[i+1] in chars):
                twoletter_key = " " + line[i+1]
                if twoletter_key not in trans_counts: trans_counts[twoletter_key] = 1
                else: trans_counts[twoletter_key] += 1
            #letter followed by a non-letter
            elif (line[i] in chars) & (line[i+1] not in chars):
                twoletter_key = line[i] + " "
                if twoletter_key not in trans_counts: trans_counts[twoletter_key] = 1
                else: trans_counts[twoletter_key] += 1
            #two non-letters
            elif (line[i] not in chars) & (line[i+1] not in chars):
                twoletter_key = " " + " "
                if twoletter_key not in trans_counts: trans_counts[twoletter_key] = 1
                else: trans_counts[twoletter_key] += 1
    return trans_counts

def map_key(key):
    '''given a encryption or decryption key, returns a dictionary where the dictionary key is the letter from the key
    and the value is the letter of the alphabet it maps to in the form {A:R, B:W, C:O, etc}'''
    alphabet = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']
    mapping = {}
    for i in range(len(key)): mapping[alphabet[i]] = key[i]
    return mapping

def apply_key(key, text):
    '''encodes/decodes text given a de/encryption key (a sequence of 26 letters) and a string of text
    returns a new text (string)'''
    mapped_text = ""
    #convert the text to a list
    text = list(text)
    #get the mapping from map_key
    mapping = map_key(key)
    #apply the mapping based on the key
    for letter in text:
        #convert to upper case
        letter = str.upper(letter)
        if letter in mapping: mapped_text += mapping[letter]
        else: mapped_text += " "
    return mapped_text

def score(key, text, trans_counts):
    '''calculates the score of a decryption key based on it's log-likliness to the referece text transition counts.
    Returns a score value (float)'''
    #get the current mapping
    mapping = map_key(key)
    #decode the text based on the mapping
    decoded = apply_key(key, text)
    key_score = 0
    target_counts = {}
    chars = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']
    #strip the text
    stripped_text = decoded.strip()
    #convert the target text into a list of characters
    text_list = list(stripped_text)
    #count the number of two letter pairs in the target text
    for i in range(len(text_list)-1):
        twoletter_key = text_list[i] + text_list[i+1]
        #two consecutive letters
        if (text_list[i] in chars) & (text_list[i+1] in chars):
            if twoletter_key not in target_counts: target_counts[twoletter_key] = 1
            else: target_counts[twoletter_key] += 1
        #non-letter followed by a letter
        elif (text_list[i] not in chars) & (text_list[i+1] in chars):
            twoletter_key = " " + text_list[i+1]
            if twoletter_key not in target_counts: target_counts[twoletter_key] = 1
            else: target_counts[twoletter_key] += 1
        #letter followed by a non-letter
        elif (text_list[i] in chars) & (text_list[i+1] not in chars):
            twoletter_key = text_list[i] + " "
            if twoletter_key not in target_counts: target_counts[twoletter_key] = 1
            else: target_counts[twoletter_key] += 1
        #two non-letters
        elif (text_list[i] not in chars) & (text_list[i+1] not in chars):
            twoletter_key = " " + " "
            if twoletter_key not in target_counts: target_counts[twoletter_key] = 1
            else: target_counts[twoletter_key] += 1
    for k,v in target_counts.items():
        if k in trans_counts:
            key_score += v*math.log(trans_counts[k])
    return key_score

def get_proposed_key(key):
    '''Takes a decryption key, randomly selects two letters, and swaps they keys for those letters. So if A mapped to X
    and B mapped to Q, then A maps to Q and B to X.
    Returns a new decryption key (string)'''
    proposed_key = ""
    chars = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']
    #randomly select two letters to swap
    char1, char2 = np.random.choice(chars, size=2, replace=False)
    #create a new key, swapping letters in the old key
    new_key = list(key)
    for i in range(len(new_key)):
        if new_key[i] == char1: index1 = i
        if new_key[i] == char2: index2 = i
    new_key[index1] = char2
    new_key[index2] = char1
    for letter in new_key:
        proposed_key += letter
    return proposed_key
### MAIN PROGRAM

#get letter transition counts for reference text
trans_counts = get_trans_counts('wp_full.txt')  # war and peace
#trans_counts = get_trans_counts('alice.txt')  # alice in wonderland

alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

#generate a random encryption key
chars = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']
encrypt_list = list(np.random.choice(chars, size=26, replace=False))
encrypt_key = ""
for i in encrypt_list:
    encrypt_key = encrypt_key + i
encrypt_test ="abcdefghijklmnopqrstuvwxyz"
test_key = apply_key(encrypt_key, encrypt_test)

#encrypt the target text
encoded_text = apply_key(encrypt_key, target_text)

#generate a random decryption key
decrypt_list = list(np.random.choice(chars, size=26, replace=False))
current_decrypt_key = ""
for i in decrypt_list:
    current_decrypt_key = current_decrypt_key + i

#Results!

print('Unencrypted text:\n', target_text)
print('\nEncrypted text:\n', encoded_text)
print('\n')

for iters in range(20001):
    proposed_decrypt_key = get_proposed_key(current_decrypt_key)
    current_score = score(current_decrypt_key, encoded_text, trans_counts)
    proposed_score = score(proposed_decrypt_key, encoded_text, trans_counts)

    #calculate the acceptance probability based on the ratio of the proposed and current scores
    ap = min(1, math.exp(proposed_score - current_score))

    #generate a random number between 0 and 1
    runif = np.random.uniform()

    #accept the proposed key only if the random number is less than the probability of acceptance
    if runif >= ap: accept_proposed_key = False
    else: accept_proposed_key = True
    if accept_proposed_key: current_decrypt_key = proposed_decrypt_key

    #print every 1000th iteration
    if iters%500 == 0:
        print('Iter:', iters, apply_key(current_decrypt_key, encoded_text)[0:99])
        test = apply_key(encrypt_key, alphabet)
        check = apply_key(current_decrypt_key, test)
        correct = 0
        for i in range(len(alphabet)):
            if alphabet[i] == check[i]: correct += 1
        #print('Number of correctly decoded letters:', correct)

print('\nDecrypted text:')
print(apply_key(current_decrypt_key, encoded_text))

#get number of correct letters
test = apply_key(encrypt_key, alphabet)
check = apply_key(current_decrypt_key, test)
correct = 0
for i in range(len(alphabet)):
    if alphabet[i] == check[i]: correct += 1
print('\n')
#print(alphabet)
#print(check)
print('Number of correctly decoded letters:', correct)
Unencrypted text:
 to bat—rb. con todo mi respeto. i was sitting down playing chess with danny de emf and boxer de el centro was sitting next to us. boxer was making loud and loud voices so i tell him por favor can you kick back homie cause im playing chess. a minute later the vato starts back up again so this time i tell him con respecto homie can you kick back. the vato stop for a minute and he starts up again so i tell him check this out shut the fuck up cause im tired of your voice and if you got a problem with it we can go to celda and handle it. i really felt disrespected thats why i told him. anyways after i tell him that the next thing I know that vato slashes me and leaves. by the time i figure im hit i try to get away but the c.o. is walking in my direction and he gets me right by a celda. so i go to the hole. when im in the hole my home boys hit boxer so now b is also in the hole. while im in the hole im getting schoold wrong.

Encrypted text:
 WK YMW NY  AKQ WKUK PX NGEZGWK  X VME EXWWXQD UKVQ ZLMSXQD AIGEE VXWI UMQQS UG GPC MQU YKBGN UG GL AGQWNK VME EXWWXQD QGBW WK FE  YKBGN VME PMHXQD LKFU MQU LKFU OKXAGE EK X WGLL IXP ZKN CMOKN AMQ SKF HXAH YMAH IKPXG AMFEG XP ZLMSXQD AIGEE  M PXQFWG LMWGN WIG OMWK EWMNWE YMAH FZ MDMXQ EK WIXE WXPG X WGLL IXP AKQ NGEZGAWK IKPXG AMQ SKF HXAH YMAH  WIG OMWK EWKZ CKN M PXQFWG MQU IG EWMNWE FZ MDMXQ EK X WGLL IXP AIGAH WIXE KFW EIFW WIG CFAH FZ AMFEG XP WXNGU KC SKFN OKXAG MQU XC SKF DKW M ZNKYLGP VXWI XW VG AMQ DK WK AGLUM MQU IMQULG XW  X NGMLLS CGLW UXENGEZGAWGU WIMWE VIS X WKLU IXP  MQSVMSE MCWGN X WGLL IXP WIMW WIG QGBW WIXQD X HQKV WIMW OMWK ELMEIGE PG MQU LGMOGE  YS WIG WXPG X CXDFNG XP IXW X WNS WK DGW MVMS YFW WIG A K  XE VMLHXQD XQ PS UXNGAWXKQ MQU IG DGWE PG NXDIW YS M AGLUM  EK X DK WK WIG IKLG  VIGQ XP XQ WIG IKLG PS IKPG YKSE IXW YKBGN EK QKV Y XE MLEK XQ WIG IKLG  VIXLG XP XQ WIG IKLG XP DGWWXQD EAIKKLU VNKQD  


Iter: 0 KT NXK QN  ETA KTHT WJ QZMYZKT  J GXM MJKKJAI HTGA YRXBJAI ESZMM GJKS HXAAB HZ ZWO XAH NTPZQ HZ ZR
Iter: 500 SO HAS FH  CON SOGO DI FLEPLSO  I WAE EISSINV GOWN PRAYINV CTLEE WIST GANNY GL LDM ANG HOZLF GL LR
Iter: 1000 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOZER DE EL
Iter: 1500 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 2000 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 2500 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 3000 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 3500 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 4000 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 4500 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 5000 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 5500 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 6000 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 6500 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 7000 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 7500 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 8000 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 8500 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 9000 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 9500 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 10000 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 10500 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 11000 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 11500 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 12000 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 12500 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 13000 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 13500 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 14000 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 14500 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 15000 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 15500 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 16000 TO BAT LB  CON TODO MI LESPETO  I WAS SITTING DOWN PRAYING CHESS WITH DANNY DE EMF AND BOXEL DE ER
Iter: 16500 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 17000 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 17500 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 18000 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 18500 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 19000 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 19500 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL
Iter: 20000 TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER DE EL

Decrypted text:
TO BAT RB  CON TODO MI RESPETO  I WAS SITTING DOWN PLAYING CHESS WITH DANNY DE EMF AND BOXER
DE EL CENTRO WAS SITTING NEXT TO US  BOXER WAS MAKING LOUD AND LOUD VOICES SO I TELL HIM POR
FAVOR CAN YOU KICK BACK HOMIE CAUSE IM PLAYING CHESS  A MINUTE LATER THE VATO STARTS BACK UP
AGAIN SO THIS TIME I TELL HIM CON RESPECTO HOMIE CAN YOU KICK BACK  THE VATO STOP FOR A MINUTE
AND HE STARTS UP AGAIN SO I TELL HIM CHECK THIS OUT SHUT THE FUCK UP CAUSE IM TIRED OF YOUR
VOICE AND IF YOU GOT A PROBLEM WITH IT WE CAN GO TO CELDA AND HANDLE IT  I REALLY FELT
DISRESPECTED THATS WHY I TOLD HIM  ANYWAYS AFTER I TELL HIM THAT THE NEXT THING I KNOW THAT
VATO SLASHES ME AND LEAVES  BY THE TIME I FIGURE IM HIT I TRY TO GET AWAY BUT THE C O  IS
WALKING IN MY DIRECTION AND HE GETS ME RIGHT BY A CELDA  SO I GO TO THE HOLE  WHEN IM IN THE
HOLE MY HOME BOYS HIT BOXER SO NOW B IS ALSO IN THE HOLE  WHILE IM IN THE HOLE IM GETTING
SCHOOLD WRONG  


Number of correctly decoded letters: 23

Comments