Linear codes and ciphers¶
Codes¶
A linear code of length
Sage can compute Hamming codes
sage: C = codes.HammingCode(GF(3), 3)
sage: C
[13, 10] Hamming Code over GF(3)
sage: C.minimum_distance()
3
sage: C.generator_matrix()
[1 0 0 0 0 0 0 0 0 0 1 2 0]
[0 1 0 0 0 0 0 0 0 0 0 1 2]
[0 0 1 0 0 0 0 0 0 0 1 0 2]
[0 0 0 1 0 0 0 0 0 0 1 1 1]
[0 0 0 0 1 0 0 0 0 0 1 1 2]
[0 0 0 0 0 1 0 0 0 0 2 0 2]
[0 0 0 0 0 0 1 0 0 0 1 2 1]
[0 0 0 0 0 0 0 1 0 0 2 1 1]
[0 0 0 0 0 0 0 0 1 0 2 2 0]
[0 0 0 0 0 0 0 0 0 1 0 1 1]
>>> from sage.all import *
>>> C = codes.HammingCode(GF(Integer(3)), Integer(3))
>>> C
[13, 10] Hamming Code over GF(3)
>>> C.minimum_distance()
3
>>> C.generator_matrix()
[1 0 0 0 0 0 0 0 0 0 1 2 0]
[0 1 0 0 0 0 0 0 0 0 0 1 2]
[0 0 1 0 0 0 0 0 0 0 1 0 2]
[0 0 0 1 0 0 0 0 0 0 1 1 1]
[0 0 0 0 1 0 0 0 0 0 1 1 2]
[0 0 0 0 0 1 0 0 0 0 2 0 2]
[0 0 0 0 0 0 1 0 0 0 1 2 1]
[0 0 0 0 0 0 0 1 0 0 2 1 1]
[0 0 0 0 0 0 0 0 1 0 2 2 0]
[0 0 0 0 0 0 0 0 0 1 0 1 1]
C = codes.HammingCode(GF(3), 3) C C.minimum_distance() C.generator_matrix()
the four Golay codes
sage: C = codes.GolayCode(GF(3))
sage: C
[12, 6, 6] Extended Golay code over GF(3)
sage: C.minimum_distance()
6
sage: C.generator_matrix()
[1 0 0 0 0 0 2 0 1 2 1 2]
[0 1 0 0 0 0 1 2 2 2 1 0]
[0 0 1 0 0 0 1 1 1 0 1 1]
[0 0 0 1 0 0 1 1 0 2 2 2]
[0 0 0 0 1 0 2 1 2 2 0 1]
[0 0 0 0 0 1 0 2 1 2 2 1]
>>> from sage.all import *
>>> C = codes.GolayCode(GF(Integer(3)))
>>> C
[12, 6, 6] Extended Golay code over GF(3)
>>> C.minimum_distance()
6
>>> C.generator_matrix()
[1 0 0 0 0 0 2 0 1 2 1 2]
[0 1 0 0 0 0 1 2 2 2 1 0]
[0 0 1 0 0 0 1 1 1 0 1 1]
[0 0 0 1 0 0 1 1 0 2 2 2]
[0 0 0 0 1 0 2 1 2 2 0 1]
[0 0 0 0 0 1 0 2 1 2 2 1]
C = codes.GolayCode(GF(3)) C C.minimum_distance() C.generator_matrix()
as well as binary Reed-Muller codes, quadratic residue codes, quasi-quadratic residue codes, “random” linear codes, and a code generated by a matrix of full rank (using, as usual, the rows as the basis).
For a given code,
sage: C = codes.HammingCode(GF(2), 3)
sage: Cperp = C.dual_code()
sage: C; Cperp
[7, 4] Hamming Code over GF(2)
[7, 3] linear code over GF(2)
sage: C.generator_matrix()
[1 0 0 0 0 1 1]
[0 1 0 0 1 0 1]
[0 0 1 0 1 1 0]
[0 0 0 1 1 1 1]
sage: C.parity_check_matrix()
[1 0 1 0 1 0 1]
[0 1 1 0 0 1 1]
[0 0 0 1 1 1 1]
sage: C.dual_code()
[7, 3] linear code over GF(2)
sage: C = codes.HammingCode(GF(4,'a'), 3)
sage: C.dual_code()
[21, 3] linear code over GF(4)
>>> from sage.all import *
>>> C = codes.HammingCode(GF(Integer(2)), Integer(3))
>>> Cperp = C.dual_code()
>>> C; Cperp
[7, 4] Hamming Code over GF(2)
[7, 3] linear code over GF(2)
>>> C.generator_matrix()
[1 0 0 0 0 1 1]
[0 1 0 0 1 0 1]
[0 0 1 0 1 1 0]
[0 0 0 1 1 1 1]
>>> C.parity_check_matrix()
[1 0 1 0 1 0 1]
[0 1 1 0 0 1 1]
[0 0 0 1 1 1 1]
>>> C.dual_code()
[7, 3] linear code over GF(2)
>>> C = codes.HammingCode(GF(Integer(4),'a'), Integer(3))
>>> C.dual_code()
[21, 3] linear code over GF(4)
C = codes.HammingCode(GF(2), 3) Cperp = C.dual_code() C; Cperp C.generator_matrix() C.parity_check_matrix() C.dual_code() C = codes.HammingCode(GF(4,'a'), 3) C.dual_code()
For
sage: C = codes.HammingCode(GF(2), 3)
sage: MS = MatrixSpace(GF(2),1,7)
sage: F = GF(2); a = F.gen()
sage: v = vector([a,a,F(0),a,a,F(0),a])
sage: c = C.decode_to_code(v, "Syndrome"); c
(1, 1, 0, 1, 0, 0, 1)
sage: c in C
True
>>> from sage.all import *
>>> C = codes.HammingCode(GF(Integer(2)), Integer(3))
>>> MS = MatrixSpace(GF(Integer(2)),Integer(1),Integer(7))
>>> F = GF(Integer(2)); a = F.gen()
>>> v = vector([a,a,F(Integer(0)),a,a,F(Integer(0)),a])
>>> c = C.decode_to_code(v, "Syndrome"); c
(1, 1, 0, 1, 0, 0, 1)
>>> c in C
True
C = codes.HammingCode(GF(2), 3) MS = MatrixSpace(GF(2),1,7) F = GF(2); a = F.gen() v = vector([a,a,F(0),a,a,F(0),a]) c = C.decode_to_code(v, "Syndrome"); c c in C
To plot the (histogram of) the weight distribution of a code, one can use the matplotlib package included with Sage:
sage: C = codes.HammingCode(GF(2), 4)
sage: C
[15, 11] Hamming Code over GF(2)
sage: w = C.weight_distribution(); w
[1, 0, 0, 35, 105, 168, 280, 435, 435, 280, 168, 105, 35, 0, 0, 1]
sage: J = range(len(w))
sage: W = IndexedSequence([ZZ(w[i]) for i in J],J)
sage: P = W.plot_histogram()
>>> from sage.all import *
>>> C = codes.HammingCode(GF(Integer(2)), Integer(4))
>>> C
[15, 11] Hamming Code over GF(2)
>>> w = C.weight_distribution(); w
[1, 0, 0, 35, 105, 168, 280, 435, 435, 280, 168, 105, 35, 0, 0, 1]
>>> J = range(len(w))
>>> W = IndexedSequence([ZZ(w[i]) for i in J],J)
>>> P = W.plot_histogram()
C = codes.HammingCode(GF(2), 4) C w = C.weight_distribution(); w J = range(len(w)) W = IndexedSequence([ZZ(w[i]) for i in J],J) P = W.plot_histogram()
Now type show(P)
to view this.
There are several coding theory functions we are skipping entirely.
Please see the reference manual or the file
coding/linear_codes.py
for examples.
Sage can also compute algebraic-geometric codes, called AG codes,
via the Singular interface § sec:agcodes. One may also use the AG
codes implemented in GUAVA via the Sage interface to GAP
gap_console()
. See the GUAVA manual for more details. {GUAVA}
Ciphers¶
LFSRs¶
A special type of stream cipher is implemented in Sage, namely, a linear feedback shift register (LFSR) sequence defined over a finite field. Stream ciphers have been used for a long time as a source of pseudo-random number generators. {linear feedback shift register}
S. Golomb {G} gives a list of three statistical properties a
sequence of numbers
In the case where
Assume
balance:
.low autocorrelation:
(For sequences satisfying these first two properties, it is known that
must hold.)proportional runs property: In each period, half the runs have length
, one-fourth have length , etc. Moveover, there are as many runs of ’s as there are of ’s.
A sequence satisfying these properties will be called pseudo-random. {pseudo-random}
A general feedback shift register is a map
where
for some given constants
is sometimes called the connection polynomial.
Example: Over
The LFSR sequence is then
The sequence of lfsr_connection_polynomial
(which produces the
reverse of berlekamp_massey
).
sage: F = GF(2)
sage: o = F(0)
sage: l = F(1)
sage: key = [l,o,o,l]; fill = [l,l,o,l]; n = 20
sage: s = lfsr_sequence(key,fill,n); s
[1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0]
sage: lfsr_autocorrelation(s,15,7)
4/15
sage: lfsr_autocorrelation(s,15,0)
8/15
sage: lfsr_connection_polynomial(s)
x^4 + x + 1
sage: from sage.matrix.berlekamp_massey import berlekamp_massey
sage: berlekamp_massey(s)
x^4 + x^3 + 1
>>> from sage.all import *
>>> F = GF(Integer(2))
>>> o = F(Integer(0))
>>> l = F(Integer(1))
>>> key = [l,o,o,l]; fill = [l,l,o,l]; n = Integer(20)
>>> s = lfsr_sequence(key,fill,n); s
[1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0]
>>> lfsr_autocorrelation(s,Integer(15),Integer(7))
4/15
>>> lfsr_autocorrelation(s,Integer(15),Integer(0))
8/15
>>> lfsr_connection_polynomial(s)
x^4 + x + 1
>>> from sage.matrix.berlekamp_massey import berlekamp_massey
>>> berlekamp_massey(s)
x^4 + x^3 + 1
F = GF(2) o = F(0) l = F(1) key = [l,o,o,l]; fill = [l,l,o,l]; n = 20 s = lfsr_sequence(key,fill,n); s lfsr_autocorrelation(s,15,7) lfsr_autocorrelation(s,15,0) lfsr_connection_polynomial(s) from sage.matrix.berlekamp_massey import berlekamp_massey berlekamp_massey(s)
Classical ciphers¶
has a type for cryptosystems (created by David Kohel, who also wrote the examples below), implementing classical cryptosystems. The general interface is as follows:
sage: S = AlphabeticStrings()
sage: S
Free alphabetic string monoid on A-Z
sage: E = SubstitutionCryptosystem(S)
sage: E
Substitution cryptosystem on Free alphabetic string monoid on A-Z
sage: K = S([ 25-i for i in range(26) ])
sage: e = E(K)
sage: m = S("THECATINTHEHAT")
sage: e(m)
GSVXZGRMGSVSZG
>>> from sage.all import *
>>> S = AlphabeticStrings()
>>> S
Free alphabetic string monoid on A-Z
>>> E = SubstitutionCryptosystem(S)
>>> E
Substitution cryptosystem on Free alphabetic string monoid on A-Z
>>> K = S([ Integer(25)-i for i in range(Integer(26)) ])
>>> e = E(K)
>>> m = S("THECATINTHEHAT")
>>> e(m)
GSVXZGRMGSVSZG
S = AlphabeticStrings() S E = SubstitutionCryptosystem(S) E K = S([ 25-i for i in range(26) ]) e = E(K) m = S("THECATINTHEHAT") e(m)
Here’s another example:
sage: S = AlphabeticStrings()
sage: E = TranspositionCryptosystem(S,15);
sage: m = S("THECATANDTHEHAT")
sage: G = E.key_space()
sage: G
Symmetric group of order 15! as a permutation group
sage: g = G([ 3, 2, 1, 6, 5, 4, 9, 8, 7, 12, 11, 10, 15, 14, 13 ])
sage: e = E(g)
sage: e(m)
EHTTACDNAEHTTAH
>>> from sage.all import *
>>> S = AlphabeticStrings()
>>> E = TranspositionCryptosystem(S,Integer(15));
>>> m = S("THECATANDTHEHAT")
>>> G = E.key_space()
>>> G
Symmetric group of order 15! as a permutation group
>>> g = G([ Integer(3), Integer(2), Integer(1), Integer(6), Integer(5), Integer(4), Integer(9), Integer(8), Integer(7), Integer(12), Integer(11), Integer(10), Integer(15), Integer(14), Integer(13) ])
>>> e = E(g)
>>> e(m)
EHTTACDNAEHTTAH
S = AlphabeticStrings() E = TranspositionCryptosystem(S,15); m = S("THECATANDTHEHAT") G = E.key_space() G g = G([ 3, 2, 1, 6, 5, 4, 9, 8, 7, 12, 11, 10, 15, 14, 13 ]) e = E(g) e(m)
The idea is that a cryptosystem is a map
e.key()
returns the
pre-image key.