Reply To: Extracurricular challenges
A Tale of 2 Secrets › Forums › T.E.M.P.E.S.T. › Extracurricular challenges › Reply To: Extracurricular challenges
21st November 2025 at 1:51 pm
#113797
upsidedown
Participant
@Puzzling_Pelican yep, correct again. Glad you enjoyed them! I may have an idea for a part 3 so we shall see.
@madness md5 correct.
Would you not agree that since matrix multiplication is distributive over addition, ie. M(u + v) = Mu + Mv, it doesn’t actually matter whether the shifts were applied before or after the matrix multiplication?
Suppose the encryption matrix is E, its inverse is D, and we add vector v before encryption (vectors p and c for plain/cipher):
# shifts added BEFORE matrix encryption
E(p + v) = c
Ep + Ev = c
let u = Ev
# shifts added AFTER matrix encryption
Ep + u = c
Here’s the code for upsidedown-2025-2 as requested:
import numpy as np
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ09:._"
m = len(alphabet)
size = 5
vector = "AAAAA"
# The actual key phrase has more than one letter!
keyword = "A"
V = list(alphabet.index(c) for c in vector)
V = np.array(V)
# extend key phrase to fill matrix
E = list(alphabet.index(c) for c in keyword)[:size**2]
while len(E) < size**2:
E.append((E[-1] + 1) % m)
assert len(E) == size**2
# create encryption matrix E
E = np.array(E)
E = np.reshape(E, shape=(size, size))
# convert plaintext to numeric array
plaintext = "THE_PLAINTEXT_WITH_UNDERSCORES"
plaintext = "".join(c for c in plaintext if c in alphabet)
plaintext = list(alphabet.index(c) for c in plaintext)
plaintext = np.array(plaintext)
assert len(plaintext) % size == 0
# [aaaaabbbbbccccc] => [[aaaaa],[bbbbb],[ccccc]]
plaintext = plaintext.reshape(len(plaintext) // size, size)
# (p + V) % m
plaintext = (plaintext + V) % m
# matvec multiplies E by each vector in plaintext
# E(p + V) % m
ciphertext = np.matvec(E, plaintext) % m
ciphertext = ciphertext.reshape(len(ciphertext) * size)
print("".join(alphabet[c] for c in ciphertext))