not sure if this is the right place, but any help is greatly appreciated (noob here).
I am playing around with sagemath and I created the following problem ($A x = y$) in $GF(2^8)$ (which I hoped mirrored byte arithmetic which I see in C f.e). Specifically, let's say I would have given the matrix $A$ and $y$, which $x$ would solve the equation:
F.<a> = GF(2^8, modulus="minimal_weight")
A = Matrix(F, 3, 3)
x = [13, 83, 29]
y = [ 141, 40, 10 ]
m = [
[23, 56, 2],
[92, 234, 182],
[12, 94, 132]
]
x = vector([ F.fetch_int(i) for i in x ])
y = vector([ F.fetch_int(i) for i in y ])
for i in range(len(m)):
A.set_row(i, [ F.fetch_int(i) for i in m[i] ])
First of all, why is it in fact not mirroring the result I would expect from byte arithmetic (i. e. let's say I would implement the same thing in C using unsigned char and specifically ignore overflows):
#include <stdio.h>
int main() {
unsigned char A[3][3] = {
{ 23, 56, 2 },
{ 92, 234, 182 },
{ 12, 94, 132 }
};
unsigned char x[3] = { 13, 83, 29 };
for (int i = 0; i < 3; ++i) {
unsigned char sum = 0;
for (int j = 0; j < 3; ++j) {
sum += x[j] * A[i][j];
}
printf("y[%d]: %d\n", i, (int)sum);
}
}
Which outputs [ 141, 40, 10 ].
But in sagemath the result is the following:
>>> A * x
[214, 224, 90]
Therefor obviously recovering the solution does not work either:
>>> A.inverse() * y
[194, 215, 241]
Well I was playing around, not understanding a lot, and tried the following:
F = Zmod(2^8)
A = Matrix(F, [
[23, 56, 2],
[92, 234, 182],
[12, 94, 132]
])
x = vector(F, [13, 83, 29])
y = vector(F, [141, 40, 10 ])
With this one I at least get the expected result:
>>> A * x
[ 141, 40, 10 ]
.. however the matrix is not invertible anymore:
>>> A.is_invertible()
False
Obviously I have some kind of knowledge gap here and I am not sure where to go next. Thank you for your help.
Part of the answer is that the two questions in the question are in fact different questions.
The ring $\mathbb{Z}/2^8\mathbb{Z}$ of integers modulo $2^8$ is a very different ring from the field $F_{2^8}$ with $2^8$ elements. In particular it is not a field and not even an integral domain.
In Sage these two rings are
Zmod(2^8)andGF(2^8).Pursing that line of thought, given a linear system involving integers between 0 and 255, one can consider it as a system to be solved over $\mathbb{R}$, over $\mathbb{R}$, over $\mathbb{Z}/2^8\mathbb{Z}$, or over the finite field with $2^8$ elements, considering the integers as encodings of field elements.
In this answer we address solving modulo $2^8$ and solving in the finite field.
Solve a linear system modulo 256
Let's see how to define the system, find one solution, find all solutions.
The system
Define the ring $\mathbb{Z}/2^8\mathbb{Z}$, the matrix, the target vector:
Check:
Find one solution
To find one solution to the system, i.e. a preimage of the target vector under the matrix, use the
solve_rightmethod:Check:
Find all solutions
Finding all solutions is now equivalent to finding the right kernel of $A$ i.e. all vectors $x$ such that $A x$ is zero.
Sadly, finding that kernel is not implemented in Sage:
The brute force solution is to try all vectors. There are $(2^8)^3$, i.e. $2^{24}$, i.e. 16,777,216 of them. Testing them all would take a while and waste resources.
Instead, we can observe that if $A x$ is zero in $\mathbb{Z}/2^8\mathbb{Z}$, then it is also zero modulo $\mathbb{Z}/2^k\mathbb{Z}$ for $k \in \{7, 6, 5, 4, 3, 2, 1, 0\}$.
For each of these $k$, call $A_k$ the matrix $A$ projected to $\mathbb{Z}/2^k\mathbb{Z}$.
The level $k = 0$ only has the zero matrix and the zero vector, so the kernel there is everything.
For the kernel of the projected matrix $A_1$, we can try all 8 vectors in $\mathbb{Z}/2^1\mathbb{Z}$, we find that 4 of them are part of the kernel.
For each $k$, each vector in the kernel of $A_{k-1}$ has $2^3$ lifts to $(\mathbb{Z}/2^k\mathbb{Z})^3$, which are the only candidates for forming the kernel of $A_k$.
For each starting vector in $\ker(A_1)$, we thus lift progressively, at each step there are eight vectors to try but only one of them ends up being in the kernel at the next level.
We end up computing 8 matrix times vector products in the first level (or computing a kernel basis by finding the row reduced echelon form over $\mathbb{Z}/2\mathbb{Z}$ which is a field, and then, for each of the 4 vectors of the level 1 kernel, 8 matrix times vector products in each of the next 7 levels.
In total, that means $8 + 4 \times 7 \times 8$, i.e. $225$ matrix times vector products. That's a lot more reasonable than the roughly 16.8 million matrix times vector products that brute force search would cost.
This function computes the kernel following that method:
Compute the kernel with that function:
Deduce the solutions to the original system:
Check:
Solve a linear system over the finite field with 256 elements
This is a different problem. As given, it amounts to solving a linear system in $F_{2^8}$, with the extra steps of converting field elements from integer representation to polynomial representation and back.
A related question and answer are at
Given the severeal steps, we write a function:
Example:
Here, finding the kernel would be easier, since the base ring of the relevant matrix is a field.