Skip to main content
The National Cipher Challenge

Reply To: Extracurricular challenges

A Tale of 2 Secrets Forums T.E.M.P.E.S.T. Extracurricular challenges Reply To: Extracurricular challenges

#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))
Report a problem