Reply To: Daily Cipher
A Tale of 2 Secrets › Forums › T.E.M.P.E.S.T. › Daily Cipher › Reply To: Daily Cipher
@madness
The graphics would be amazing to see, could you try sending them to Harry and he would be able to forward it on towards me? (@Harry would this be possible?)
Also, I love the name P³ !
Regarding some oddities about how the cipher chooses to encrypt, I think a few of them may simply come down to how I implemented it in code a while ago.
If you are certain that your version of the code is closer to how I intended the cipher to work (like with the 1×1 and where your code has the centers closer than my code mistakenly places them), then please do fix the ciphertext on the archive to reflect this. I would prefer a slightly altered ciphertext to a slightly flawed cipher. I appreciate the effort you have put in to reasearching this cipher and look forward to seeing the results.
This is the original code I wrote for this, hopefully you can make some sense out of it (I still can’t remember how all of it works).
import math
import typing
import re
TYPE_COORDS = typing.Tuple[float, float]
TYPE_GRID = typing.Dict[TYPE_COORDS, str]
grid: TYPE_GRID = {}
invalid_points = set()
def generate_spiral(limit):
"""
Generate a spiral on a triangular grid.
"""
directions = [
(1, 0), # right
(0.5, 1), # down-right
(-0.5, 1), # down-left
(-1, 0), # left
(-0.5, -1), # up-left
(0.5, -1), # up-right
]
dir_between_circles = (-0.5, -1) # up-left after each hex
path = []
x, y = 0.0, 0 # start
dir_idx = 0 # start facing right
steps_in_leg = 1
steps_taken = 0
# center
path.append((x, y))
x += dir_between_circles[0]
y += dir_between_circles[1]
for _ in range(limit - 1):
# Move in current direction
dx, dy = directions[dir_idx]
x += dx
y += dy
steps_taken += 1
# Snap x to integer or half-integer depending on row
if y % 2 == 0:
x = round(x) # even row -> integer x
else:
x = round(x - 0.5) + 0.5 # odd row -> half-integer x
coord = (x, y)
path.append(coord)
# Spiral logic: after finishing a leg, turn clockwise
if steps_taken == steps_in_leg:
steps_taken = 0
dir_idx = (dir_idx + 1) % 6
# completed full hexagon
if dir_idx == 0:
steps_in_leg += 1
x += dir_between_circles[0]
y += dir_between_circles[1]
return path
def generate_grid(key: str) -> TYPE_GRID:
"""Generates a grid from a key string."""
grid: TYPE_GRID = {}
padding = "abcdefghijklmnopqrstuvwxy!#$%&()*+,-./:;<=>?@[\]^_{}~£z"
key += padding
spiral: typing.List[TYPE_COORDS] = generate_spiral(len(key))
for i in range(len(key)):
col, row = spiral[i]
grid[(col,row)] = key[i]
return grid
def get_coord_of_letter(letter: str, grid : TYPE_GRID) -> TYPE_COORDS:
"""Finds the coords that map to a specific letter in the grid."""
for coords, l in grid.items():
if l == letter:
return coords
def get_letter(coords: TYPE_COORDS, grid : TYPE_GRID) -> str:
"""Returns the letter at a position in the grid."""
if coords not in grid:
invalid_points.add(coords)
return grid.get(coords, "")
def get_center_of_traingle(a: TYPE_COORDS, b: TYPE_COORDS, c: TYPE_COORDS) -> TYPE_COORDS:
"""Returns the center of a triangle generated by taking an avarage."""
return ((a[0] + b[0] + c[0]) / 3, (a[1] + b[1] + c[1]) / 3)
def _is_whole_num(value: float) -> bool:
"""Returns whether a float is an intiger, compensates for floating point error"""
return abs(value - round(value)) < 1e-9
def round_midpoint(p: TYPE_COORDS) -> TYPE_COORDS:
"""Round the point to either the grid or directly between 2 points.
3 cases:
- On an intersection of axes: stays the same
- In the middle of a cell (not on any axes): Moved vertically up or down (The original version used for 10B ignored this case because of broken code)
- On 1 axis: rounded to the middle of that axis (half way between nearest points on axis)
"""
x, y = p
# Check if it is on an intersection
if _is_whole_num(y): # y must be an intiger
# if even row, x is int, if odd, x is int+0.5
if (int(y) % 2 == 0 and _is_whole_num(x)) or (int(y) % 2 == 1 and (_is_whole_num(x - 0.5))):
# Leave midpoint as is
return (x, y)
# Check if it is in center of cell
# The original code written for this case was broken so the original 10B encrypt ignored this and treated it the same as the axis case.
# Check if the centerpoint is on the i axis
if _is_whole_num(y):
# round the int+0.5 if on even axis and int if odd (halfway between 2 points)
if int(y) % 2 == 0:
x = round(x + 0.5) - 0.5
else:
x = round(x)
return (x, y)
# Must be on either the j or k axis
# y should be rounded to int+0.5
# x should be rounded to int+0.25 or int+0.75
y = round(y + 0.5) - 0.5
x = round(2 * x + 0.5) / 2 - 0.25
return (x, y)
def reflect_point(p: TYPE_COORDS, mid: TYPE_COORDS) -> TYPE_COORDS:
"""Reflects a point in the midpoint. A in M goes to A + 2(A->M)."""
x, y = p
dx = mid[0] - x
dy = mid[1] - y
nx = x + 2 * dx
ny = y + 2 * dy
return (nx, ny)
def map_points(a: TYPE_COORDS, b: TYPE_COORDS, c: TYPE_COORDS) -> typing.Tuple[TYPE_COORDS, TYPE_COORDS, TYPE_COORDS]:
# get midpoint
mid = round_midpoint(get_center_of_traingle(a, b, c))
# reflect each point in midpoint
na = reflect_point(a, mid)
nb = reflect_point(b, mid)
nc = reflect_point(c, mid)
return na, nb, nc
def map_trigram(a: str, b: str, c: str, grid : TYPE_GRID) -> typing.Tuple[str, str, str]:
point_a = get_coord_of_letter(a, grid)
point_b = get_coord_of_letter(b, grid)
point_c = get_coord_of_letter(c, grid)
new_point_a, new_point_b, new_point_c = map_points(point_a, point_b, point_c)
na=get_letter(new_point_a, grid)
nb=get_letter(new_point_b, grid)
nc=get_letter(new_point_c, grid)
return na, nb, nc
def display_grid(grid: TYPE_GRID, point_spacing: int = 40, margin: int = 50):
"""Displays the key using tkinter. AI generated but works"""
import tkinter as tk
import math
root = tk.Tk()
root.title("Triangular Lattice Viewer")
# Extract bounds of grid
xs = [x for (x, y) in grid.keys()]
ys = [y for (x, y) in grid.keys()]
min_x, max_x = min(xs), max(xs)
min_y, max_y = min(ys), max(ys)
width = int((max_x - min_x + 2) * point_spacing) + 2 * margin
height = int((max_y - min_y + 2) * (math.sqrt(3)/2) * point_spacing) + 2 * margin
canvas = tk.Canvas(root, bg="white", width=width, height=height)
canvas.pack(fill=tk.BOTH, expand=True)
def grid_to_pixel(col, row):
"""Convert triangular lattice coords to pixel coords."""
px = (col - min_x) * point_spacing + margin
py = (row - min_y) * (math.sqrt(3)/2) * point_spacing + margin
return px, py
# Draw optional mesh lines
directions = [
(1, 0), (0.5, 1), (-0.5, 1),
(-1, 0), (-0.5, -1), (0.5, -1)
]
for (col, row), letter in grid.items():
x, y = grid_to_pixel(col, row)
# Draw point
r = 3
canvas.create_oval(x-r, y-r, x+r, y+r, fill="black")
# Draw letter
canvas.create_text(x, y-12, text=letter, font=("Arial", 10, "bold"))
# Draw connecting lines
for dx, dy in directions:
neigh = (col+dx, row+dy)
if neigh in grid:
nx, ny = grid_to_pixel(*neigh)
canvas.create_line(x, y, nx, ny, fill="black")
root.mainloop()
def gen_key(word: str) -> str:
alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +"0123456789"
padding = alpha[alpha.index(word[-1]):] + alpha[:alpha.index(word[-1])]
# padding = alpha
key = ""
for l in word + padding:
if l not in key:
key += l
return key
key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
key="RLSYXQKFGMTZ432WPJEABCHNU0598761VOID"
key = gen_key("CONTAIN")
grid = generate_grid(key)
display_grid(grid)
def encrypt(text, grid):
"""Encrypts (or decrypts since encryption and decryption are symmetrical) text using a grid."""
res = ""
tri = ""
text = list(text)
padd_1 = "0"
padd_2 = "1"
while text:
l = text[0]
if l in tri:
if len(tri) == 1:
tri += padd_1
if len(tri) == 2:
if padd_1 in tri:
tri += padd_2
else:
tri += padd_1
else:
tri += l
text.pop(0)
if len(tri) == 3:
ntri = map_trigram(*tri,grid)
res += "".join(ntri)
tri = ""
return res
text = """DEARCOUNCILMEMBERSIAMWRITINGTHISURGENTMES0SAGETOBRINGTOY0OURAT0TENTIONAMAT0TEROFUTMOSTIMPORTANCEREGARDINGTHELIGHT0HOUSEREC0ENTEV0ENTSHAVEFORCEDUSINTOASITUATIONWEHAVELONGWORKEDTOPREVENTAGENTSHAR01RYANDJODIEHAVEUNCOVEREDTHE0HIDDENLAYERSOFTHELIGHTHOUSESOPERATIONSTHOUGHWEHAVETAKENGREATCARETOENSURETHATOURT0RUEPURPOSER0EMAINSOBSCUREDTHEIRINVESTIGATIONHASPEN0ETRATED01DE0EP0ERTHANANYPREVIOUSINTERNALTHREAT01THEYHAVEPIECEDTOGETHERTHELIGHTHOUSE0SROLENOTASA0SIMPLERESEARCHANDARCHIVALINSTITU0TIONBUTASAFUL01LYOPERATIONALEXTENSIONOFBOSSINTELLIGENCEOPERATIONSTASKEDWITHACQUIRINGSENSITIVEDOCUMENTSINFILTRATINGEXTERNALREPOSITORIESAND0DELIVERINGACTIONABLEINTEL01LIGENCEUNDERTHEGUISEOFHISTORICALSTUDYTHEIMPLICATIONSOFTHEIRDISCOVERYCAN0NOTBEOVERSTATEDTHEIRKNOWLEDGENOWTOUCHESEV0ERYLEVELOFOURWORKFROMPROJECTSENTINELSEARLYSTAGEMONITORINGCAPABILITIESTO0THECONSTRUCTIONOFTHENEWCOMPUTATIONALINFRASTRUCTURECAP0ABLEOFPROCESSINGVOLUMESOFINTELLIGENCEPREVIOUSLYCONSIDEREDUNAT01TAINABLETHEYHAVEIDENTIF0IEDPATTERNSINPERSON01NELAS0SIGNMENTSFINANCIALTRANSACTIONSANDOPERATIONALDIRECTIVESTHATLINKTHELIGHTHOUSEEXPLICITLYTOBOSSANDHAVEBEGUNTOTRACETHECHAINOFCOMMANDTHATUNDERPINSOURCOVERTOPERATIONSIT0ISCLEARTHATHAR0RYANDJODIESPRESENCEWITHINOURSPHERECAN01NOLONGERBETOLERATEDWITHOUTSIGNIF0ICANTRISKTHEIRCURIOSITYWHILECOM01MENDABLEINORDINARYCIRCUMSTANCESNOWCONSTITUTESADIRECT01THREATTO0THEINTEGRITYOFTHELIGHTHOUSEANDBYEXTENSIONBOS0SWEMUSTACTDECISIVELYTOC0ONTAINTHEEXPOSUREANDENSURETHATTHEKNOWLEDGETHEYHAVEACQUIRED01DOESNOTPROPAGATEB0EYONDWHATCANBECONTROLLEDEF0FECTIVEIMMEDIATELYTHECOUNCILMUSTCONSIDERAMULTIPRONGEDCONTAINMENTS0TRATEGYALLFILESANDDIGITALRECORDSAC01CESSEDBYTHEAGENTS0SHOULDBESECUREDANDWHEREAP01PROPRIATEREMOVEDFROMSTANDARDOPERATIONALCHAN01NELSANYTRACESOFTHEIRACTIVITYONINTERNALCOM0MUNICATION01NETWORKSMUSTBECAREFULLYSCRUB01BEDENSURINGTHATNORESIDUALDATACOULDHINTAT01THESCOPEOFTHEIRDISCOVERIESPERSON0NELWHOMAYHAVEINADVERTENTLYGUIDEDORINFLUENCEDTHEAGENTSMUSTBEDISCRE0ETLYREAS0SIGNEDANDAC0CESSPROT0OC0OLSMUSTBEREINFORCEDTOPREVENTFURTHERBREACHESITISEQUAL01LYIMPORTANT0TOMAN0AGETHEAGENTSTHEMSELVESWHILETHEIRLOYALTYANDSKIL01LSARE0RECOGNIZEDTHEIRCONTINUEDPROXIMITYTOCRITICALOPERATIONSCAN0NOTBEAL01LOWEDCONTROLLEDENGAGEMENTMONITOREDCOM01MUNICATIONA0NDCAREFULLYORCHESTRATED0DISINFORMATIONWIL0LBEREQUIREDTOPREV0ENTTHEMFROMRECONSTRUCTINGTHEFUL01LSCOPEOF0OUROPERATIONSTHEIRUNDERSTANDINGOFPROJECTSENTINELTHESUPERCOMPUTERANDOURCOVERTDOCUMENTACQUISIT0IONMUSTBEDELIBERATELYOBFUSCATEDWHILEPRESERVINGTHEFUNCTIONALITYOFTHELIGHTHOUSEFOR0ONGOINGMIS01SIONSTHECOUNCILMUSTALSOCONSIDERTHEBROADERIMPLICATIONS0SHOULDHARRYANDJODIEB0EALLOWEDTOLEAVEWITHANYDEGREEOFUNMIT0IGATEDKNOWLEDGETHERESULTINGEXPOSUREWOULDCOMPROMISEDECADESOFINTEL01LIGENCEWORKANDENDANGERTHELIVESOFCOUNTLES01SOPERATIVESAT01THESAMETIMETHEMORALANDPSYCHOL0OGICALWEIGHTOFTHESEACTIONSCAN0NOTBEIGNOREDWEOPERATEINTHESHADOWSNOTFORCONVENIENCEBUTBECAUSETHEWORLDWEPROTECTRELIESUPONOURDISCRETIONTHISREALITYREQUIRESDECISIONSTHATARENOTWITHOUTPERSONALORETHICALCOSTWEHAVESURVIVEDTHREATSTOTHELIGHT0HOUSEBEFORE0EXTERNAL0ADVERSARIESINTERNALDIS01SENTANDUNFORES0E01ENLEAKSBUT01THISREPRESENTSATHREATUNLIKEANYWEHAVEFACEDTHEAGENTSINGENUITYAND0DETERMINATIONCOMBINEDWITHT0HEIRPARTIAL0LOYALTYTOBOSSCREATEASCENARIOWHER0EANYDELAYINACTIONCOULDBECATASTROPHICTHECOUNCILMUSTACTWITHPRECISIONDISCRETIONA0NDUNITYHESITATIONISALUX0URYWENOL0ONGERPOS0SE0S01SFINALLYIREMINDYOUTHAT01THESURVI0VALOFTHELIGHTHOUSEANDTHESECURITYOFBOS01S01SWIDEROPERATIONSREST0SUPON0OURABILITYTOMANAGEBOTHT0HESITUATIONANDPERCEPTIONTHEAGENTSMUSTNEVERFULLYGRASPTHECONSEQUENCESOFTHEIRDISCOVERIESTHEYMUSTBEGUIDEDINTOAN0AR0RATIVETHATSATISFIESTHEIRCURIOSITYWITHOUTGRANTINGFULLACCESSTOOURIN0NERWORKINGSAL01LMEASURESTAKENSHOULDPRESERVETHEOPERATIONALFACADEOFTHELIGHTHOUSEWHILE0ELIMINATINGTHEIM01MEDIATERISKTHEYPOSETIMEISOFTHEES0SENCETHECOUNCILISDIRECTEDTOCONVENEIMMEDIATELYAUTHORIZETHECONTAINMENTMEASURESHEREINDESCRIBEDANDOVERSE01ETHEIREX0ECUTIONWITHOUTDELAYOURACTIONSMUSTBEMETICULOUSTHOROUGHANDWITHOUTHESITATIONTHEFUTUREOFTHELIGHT0HOUSEANDTHEPRESERVATION0OFDECADESOFSTRATEGICOPERATIONSDEPENDSUPONTHEDECISIONSMADEINTHESECOMINGHOURSTHEAGENTSHAVESTUMBLEDUPONATRUTHT0HATCANNOTBELEFTUNCHECKEDIT0ISNOW0OURDUTYTOENSURETHAT01THELIGHT0HOUSEANDTHELEGACYOFITSFOUNDINGME0MBERSCONTINUESUNBROKENSECUREFROMDISCOVERYANDCAP0ABLEOFFULFILLINGITSPURPOSEINTHISTHERECANBENOCOMPROMISEWITHUNWAVERINGRESOLVETRINITY01"""
text = re.sub("[^A-Z0-9a-z]*","",text.upper())
print(encrypt(text,grid))
text="XWmCR OCNUw 10Pm0 5WT1M AQXUC VAArX 7CP?N I12V2 HVGTS OTE2X UGNYH PTSQN I0TWN EPNA1 QVI0T NREW6 A2VIA OUO2M XOSXW O?INU EQXHO GWZYJ MCJX7 XZT5W 1YWNE WIHDN EWHSC oWBOC R9VnC 7OCXN AUOAU NSCPG YDBXN UI1NZ pG92O VDVX4 XNEYX NNEWH SCKQP GOCG0 BX2VZ WFWUE OSNCR EWXnA KFVE0 TVnX0 15WTF OAAKo GIAIS YTW6P OSQFN PNANT S7XZX ErZWF XNWHE KA6FC WXORA OTERT XTD6H TYSVC Z2SDT 67QNS OP2GV MEI7O C9RVS PTWXn T0FAS PTSEH FCCIT YZSRI NPYBR NEWXC wTXZA mMYTR ENNAH TYNVX 4PUTP C7EWN TO6XK PFQNL CAGWZ ZAHAO DWPXW XnpNI GWZGI SwXZA Xr7XZ H2VQO RB2ZX CNJPN LQNYW MW5TQ NFHCZ ECBHI RT0CN PLAOU CZ9PN A9SVN UXWNG 0LKT2 JV4XC ITTCS n0ZWN EISCA XRR9V C7OZ1 DYJMO XWQXW QFNPN ANTSP JUWXE YJCQP AP7TX HOXRT AUODY CQWVZ lQNST WYOIT LXSOX HOTWE RET2N JQWXA UOPIR OFVY1 B0ImD YCASP CYGPN AW9CX wZEWN 0LKYJ MOXWS XWCN= GWZSH WXRW0 AGCRN ZOPQU 0BENA HTMAE 0C1AC ISCPX W6GWZ 9EQSI ODTSY RCCZV CTOAN nCREX NUnWX GWZ1t OTWOm I0VAp WONNP SVIqI HDPpN lPZ2B Y6WAT 4NrV2 12SRP QW0GV ESACV Z2BFO VJN3X NUIK> CPSRN TXHOI PAT0W TILVQ Y1ZNG WZSNO CRTAV NSCPF OAVIG NTDMO 2ANUP NA0PV WYOT6 C6VOV NAERV 1ZCCL BCoNR PQVRX C7OSZ FO}CR XWOWY Z1DYJ MOXW4 VXCYD UONX1 JNOCm AT=NC ATULC AICTW 9CD1Z EHFVX HTmAN EWY1X mTAXC INREO C7AWQ OQRLO CZ2BP JNHTR ZD2NS TOWYN AT0LA OXSCA SAITN OCBEC XWQXS OSCPA DLVFQ AITTS EHTYL TIZ9I AKoGI AISYT W6IAo C1LIL TOIYR 9VASC JWFD9 WT9pC OTOXS ECXVI FCZYA XR21N VQ1ZF BUNAn TVIOR OCNPT SDTST RNV4X CITNO CA0TS IOELZ GISYH TQZSG OCG0B X2VRX BW5TO XWCAD JCYSU OOQ6U PpIFX LOC1I UYZRS nCOTE VaHWX CCTlI SYN7V XOHY1 XTACO RTPKS GWZOZ PPT7C ISXVA 1JYQT X0MLD 2ZTlX XwZRS NPBXC GOPOZ PLVXN UXWOC ONOVW NTS9N AVSEX 0EERV LCAIS GCWX1 ZNGWZ VACIN ?XZCF OAAKo GIAIS YTW6B ECFlW WETOC 7BVOI 0SoTN 82PIC AnXWO SIDYC X1KIP STCOC TZGWZ IAoUN OFQNn TV7US ERNTY HGWZ7 1IZmI GDFGW ZZAHX NBSVP VFQwT XCnNC ONQNA ICQNX YCZlI TpDNV TYHTN AANDT CO126 mI0ZA nTAoC VIMAE <I0TC IKoZG WZSNP ICNOC }AONN OCmAT 03N09 PROIY ZRTAn TCOCT ZD2ZP ZUOXS pYW10 XXMZB 2HBEC JoTTI CVHaR OCOE9 LPIVR XnUNA 9WWZF VApPZ UZ6AZ k2TUn SBX=N CBECG DYFQN 0PIRP QOIRW XCZbV DTSCU =2SMC NAQUE QWBQF NPNA0 PVHCZ LOCZ2 BASCS CFXFI XW6GW ZOZQA ITIVC SXAVA CRET1 T0LCQ 91PTA CPNAL OCENW 4NTRv CNV7A NDNFQ QJBVQ JSTPL VWEBW RTXPT 7GNYT YHRSN QYVCF BDLAI CAILU 0VEAC NLCAG WZCSN WQXAG WQVFQ 9XNTP QFVRX B5WTI 1SZ2B IUFF0 1VXHP WYBCX RENNE WpJ0X kONCn ASPQB JOXWW XnWZF VAp20 VNV7E BWSIO G2V1Z DNpPP JNHTR nVTBE CIZTV RXQ6O Z1NIP SP718 2PDXV FQVAR XEVRO VDVX4 XNEWU FGISS nCFQN FVIAU OVYQV P60LK X1ZO2 MXOSZ IV2IV CZVoW YGWZo WYNST GWZBZ 3lZPG IUZXw GWZ2K OYOCX K1BEC KSP0L KU0QG 2VERV YRZFX 0ZFBQ VFSNO CVAn9 VRPQM IXXZC OTNAS QTACO L1QFN PNAOC SCZVC TOYTD 0LKVZ MTAnT CO126 mI0Ap VoWYD 2Z2VI AVCTX 4QVW0 ML91P TACPN AZCVW OBNFQ QJBT2 JHIRS EVXSO AZmQ9 XWYO2 1SCIT WTOLI 1mJIT dVP7T WXnQR PIHDN EWGWZ VcZ2S 1QTXN TSTPS CVAAr XTVp0 LKU0P XWQH2 TSQNX WQXSO SCPHI SQVFW BS5WT CNAPB XNpCR PQW0G VESAC VZ2BG WZCPU REVMO 2FWNT 6ORBT CROCR EVODP LVNEW QPAP7 CA0TS CP82P DXV0I mlANX SOKoZ nAUON 7WXCY FlZXw VX4T5 WC9TA rXTVp TVNSC PLA0I OYAGW wXZAX r7XZo NUPI6 YZRCS P2FZK OCISC NTSVI FUOSL 1C82P 0IWCN SNOCm ATGIS SCnEV PQFV1 w2TAC PNAPI 7Z6AZ k2SCG GOCG0 BX2VC ZlLZE VZMWX nYB2B XNIWA NAHDY XN?IC oN1P9 A0TGI HnWX7 1IZmI GDFGW ZW5T0 9PXHO IAoUN OnSNI LUQVW SQ1LC 2nUNI FXNUn YQTEW N0LKY JMOXW NTnJS 2nTVE CBpVA GISwX ZTSEY QAUOS ZD1KO CPOSQ FNCVI OFVLC AGWZ0 QVIYE D2ICL oCARV P0QBX ZrAKC PHQA0 1TACD AXrFO AVIqI XFPNA OCSCZ VCTOA NlRZY =NCTN nV4XW XCVAC VIqWF JVRTC TO5TX SNOXN EWPYD NAW9N IFXTW 6GWZ4 NTm0Z 4VXET OSRTw XZ6WT NCPSQ NQ9XV ERPNA YAU6P CLZEX ZCTdV P7TUn NPXWI SCNTS TYHNF QCTOI WA7XZ AEPOQ R0PVT X4JCY 01TSN CEXTV XH6WT TQ7nB WISGC WXCRN GWZYJ MCJX7 XZnUT CNoG2 VTWER ETLAI BCX5W TQZOQ YVEWN TO6Ww AKOCX RTCNA SWB5T X2HVw AY2ZB KHEBS VLCAJ CYTW5 VX4XR TNXUI SGCWX 1P8YW LTYNG YDBXN CXFWX nWZFV ApOAU pVACI UYTXY 1BXWn bZVTZ CPNAO SNWwC nVTIW ACJXT 0FZCQ AQSLA ITJ2I W0OIY R9VO6 NCWXO VFWOX QZOFI U2GVC WFDYX X01TZ CAITN OSILU WEBAC IUXNP QRPZJ GWZSN PICNO C}XNU WTCYJ CVX4O SISCP Q9XVE RPNAZ CVBSW AVCFE HAUOC ITCPS 0QUJP VIX6T NEKCP YZR4X VI0SH 2VKOC HQT0P VX1ZZ bVBXP 7WAHT YLCAG WZSRU 0TV0C BFOAA KoGIA ISYTW 6BECG WZXRV PT7IO YUAnK OCKOC XO9CN =XWQX SOSCP TW5ZP UCPUI 1SSQN T0WTI LOIYV Q1oWY PWNCJ XVIq9 NACIT TCSTQ BREVP EASCP GWZoW YNST8 2PWEN CREQJ BpJ0T 6CY0O QTXTX RDARX WOXW6 GWZ9E QSIOD TSFVQ HISLI oNV7G XBXkO TAmCO TCZVQ ZSXSO DYCHT YNXUO UAAoZ HISQV FSPTS CIXZC IWA7X ZIRGN ACXHO QJBT1 0VRXC RNSQN C0NTR E4NTN IKSGT 0LKAY ~7TCW 5TK0T RTX7X ZBMXW 5TREC ZGWQX WQFNP NA0PV CXFmX WFOAA KoGIA ISYTW 6JTFX wAwXZ TPLCI TGNYT 0F0ML <I0TC IQFVJ U1EHF SOPIY EEMAX W6GWZ 2HVXR TECXV IFUOS L1C9X QVFQE XCOVD SNOXN EMAE< I0TCI KoZUA N5IZF X0GWZ SNOIC TZD21 2VOVF TD6qV IFQVn TVO6N lANCB EODT5 WTwAY ZGWQV FIADS BXPNA APVIS YEBNL EZ7WA AROPN A21UN V70Pm IYEPL UUONI SYNSQ ZYrDN VYJCV CSVIq TICPN AYAUT Vp6OV CoNGW ZYJMC JX7XZ FOVEC WT0pW 5TREC CITI1 SnUCI FXNUn W6XOX SpYWO PIV4X CITNO CCNmV nTCPU TCODY HPXWI SC21U mXWVA CVIqQ TXTPL IA[RU SGWZo WYNST VXHSE VIk2m I0PUN ANCVO 6CJXT YHTNA CTOJI mXWoC NUVIF mZJA0 T7COY 1VSQN NEBOI YRTXT D6HTY LCAGW ZYJMC JX7XZ FOVEC WAKoY WoAOY CEYX6 WWBSX HOFwA m0PNO 6TCOU CI6WT URBHV MTXRS PTCN> ABaSI ODTSG OCWOB 1ZCCL BCoNQ JBMZX NPLCF HCUPP RQYQV YAUUA OUPpI FXDTU ONSMO 212SV YQIWA 7ZATB XQFVA 6FRXW lZPQA SAVC1 AY "
text=text.replace(" ","")
print(encrypt(text,grid))
P.S. I believe that P³ is potentially hill-climbable, I have successfully hill climbed it a few times before though it seemed very unreliable.