Logo
Overview

Daily Alpacahack 2026 #27 - ToyPQC

January 27, 2026
2 min read

Executive Summary

Challenge: ToyPQC CTF: Daily Alpacahack 2026 #27 Category: Cryptography Difficulty: Hard Flag: Alpaca{sa6em4th_i5_u5efu1^_^}

ToyPQC is a cryptography challenge based on the Learning With Errors (LWE) problem. We are given an overdetermined system of linear equations over a finite field, perturbed by a “noise” vector. The critical vulnerability lies in the small size of the error vector (binary elements, length 10), which allows for a brute-force attack to recover the correct error, solve the linear system, and decrypt the flag.


1. Challenge Analysis

The provided SageMath script implements an LWE-based system:

  1. Setup: Defines a finite field FpF_p with p=8380417p = 8380417.
  2. Secret (ss): The flag is split into 7 chunks, forming the secret vector sFp7s \in F_p^7.
  3. Matrix (AA): A random 10×710 \times 7 matrix AA is generated.
  4. Error (ee): A random error vector e{0,1}10e \in \{0, 1\}^{10} is generated.
  5. Encryption: The public vector bb is computed as b=As+eb = A \cdot s + e.

We are provided with AA and bb.

2. Vulnerability Assessment

The equation b=As+eb = A \cdot s + e represents a noisy system of linear equations. while LWE is typically hard, the specific parameters here make it vulnerable.

The error vector ee has length m=10m=10, and each element is chosen from {0,1}\{0, 1\}. Total search space: 210=10242^{10} = 1024.

This extremely small search space allows us to brute-force the error vector ee.

3. Solution Strategy

  1. Iterate: Generate all 2102^{10} possible binary vectors for ee.
  2. Clean: Compute candidate target b=beguessb' = b - e_{guess}.
  3. Solve: Attempt to solve As=bA \cdot s = b'.
    • If eguesse_{guess} is incorrect, the system is inconsistent (no solution).
    • If eguesse_{guess} is correct, we find the unique ss.
  4. Decode: Convert the resulting secret vector ss back to bytes.

4. Solve Script (SageMath)

import itertools
from Crypto.Util.number import long_to_bytes
# Challenge Parameters
p = 8380417
n = 7
m = 10
F = GF(p)
# Assume A (matrix) and b (vector) are loaded from challenge output
# A = matrix(F, ...)
# b = vector(F, ...)
print("[-] Starting Brute-Force on Error Vector...")
possible_errors = itertools.product([0, 1], repeat=m)
for err_tuple in possible_errors:
e_guess = vector(F, err_tuple)
target = b - e_guess
try:
# Attempt to solve A * s = target
s_candidate = A.solve_right(target)
flag_content = b""
for val in s_candidate:
flag_content += int(val).to_bytes(3, "big")
full_flag = f"Alpaca{{{flag_content.decode()}}}"
print(f"[+] Success! Error vector: {err_tuple}")
print(f"[+] FLAG: {full_flag}")
break
except ValueError:
continue
except UnicodeDecodeError:
continue

5. Conclusion

By exploiting the low entropy of the error vector, we reduced the problem to a trivial brute-force check. Validating each of the 1024 possibilities against the linear system allowed us to isolate the correct error and recover the plaintext.