aboutsummaryrefslogtreecommitdiffstats
path: root/buch/papers/mceliece/example_code/mceliece_simple.py
blob: bac3b4249f0146016f7f5fc24f6bdba385829eb7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Apr 18 08:58:30 2021

@author: terminator
"""
import numpy as np
from numpy.polynomial import Polynomial as Poly


def gen_perm_M(n):
    '''
    generating random binary permutation matrix: https://en.wikipedia.org/wiki/Permutation_matrix 

    Parameters
    ----------
    n : int
        size of output matrix.

    Returns
    -------
    perm_M : np.ndarray
        binary permutation matrix.

    '''
    perm_M=np.zeros((n, n), dtype=int)
    perm_V=np.random.default_rng().permutation(n)
    perm_M[perm_V, np.r_[0:n]]=1
    return perm_M



def create_linear_code_matrix(n, k, g):
    '''
    create generator matrix for linear encoding
    use this matrix to create code_vector: matrix @ data_vector=code_vector

    Parameters
    ----------
    n : int
        len of code_vector (matrix @ data_vector).
    k : int
        len of data_vector.
    g : numpy.Polynominal
        polynomina wich defines the the constellations of .

    Returns
    -------
    np.ndarray
        Generator matrix

    '''
    if n != k+len(g)-1:
        raise Exception("n, k not compatible with degree of g")
    rows=[]
    for i in range(k): #create potences of p
        row=np.r_[np.zeros(i), g.coef, np.zeros(n-k-i)]
        rows.append(row)
    return np.array(rows, dtype=int).T


def gf2_inv(M, get_full=False):
    '''
    create inverse of matrix M in gf2

    Parameters
    ----------
    M : numpy.ndarray
        input Matrix.
    get_full : Bool, optional
        if Ture, return inverse of G with I on the left (if gaussian inversion successful) If True, vaidity is not proven. The default is False.

    Returns
    -------
    np.ndarray or None
        returns inverse if M was not singular in gf2, else None.

    '''
    size=len(M)
    G=np.hstack((M, np.eye(size)))
    G=np.array(G, dtype=int)
    for n in range(size): #forward reduction
        if G[n, n] == 0:    #swap line if necessary
            for i in range(n+1, size):
                if G[i, n]:
                    G[i, :], G[n, :] = G[n, :].copy(), G[i, :].copy()   #swap
            
        for i in range(n+1, size): #downward reduction
            if G[i, n]:
                G[i, :] = G[i, :] ^ G[n, :]
    #reached buttom_right with pivo 
    for n in range(size-1, -1, -1):  #backwards
        for i in range(n):
            if G[i, n]:
                G[i, :]= G[i, :] ^ G[n, :]
                

                    
    if get_full:
        return G
    else:
        valid=np.sum(np.abs(G[:, :size]-np.eye(size))) == 0  #reduction successfull when eye left on the left of G        
        if not valid:
            return None
        else:
            return G[:, size:]

def create_rand_bin_M(n, with_inverse=False):
    '''
    create random binary matrix

    Parameters
    ----------
    n : int
        size.
    with_inverse : bool, optional
        if False, return only random binary matrix.
        if True, return also inverse of random martix. for this purpose, random matrix will not be singular.
        The default is False.

    Returns
    -------
    M : TYPE
        if with_inverse is True: return tuple(random_matrix, inverse_of_random_matrix)
        else: return random_matrix

    '''
    inv=None
    while type(inv) == type(None):  #do it until inversion of m is successful wich means det(M)%2 != 0
        M=np.random.randint(0,2, (n,n))
        inv=gf2_inv(M)
    if with_inverse:
        return(M, inv)
    else:
        return M
    
def create_syndrome_table(n, g):
    '''
    create syndrome table for correcting errors in linear code

    Parameters
    ----------
    n : int
        len of linear-code code_vector.
    g : numpy.Polinominal
        generator polynominal used by linear-code-encoder.

    Returns
    -------
    list of error_vectors, one per syndrome. 
        get the corresponding error_vector by using the value represented by your syndrome as index of this list.

    '''
    zeros=np.zeros(n, dtype=int)
    syndrome_table=[0 for i in range(n+1)]
    syndrome_table[0]=zeros #when syndrome = 0, no bit-error to correct
    for i in range(n):
        faulty_vector=zeros.copy()
        faulty_vector[i]=1
        q, r=divmod(Poly(faulty_vector), g)
        r=np.r_[r.coef%2, np.zeros(len(g)-len(r))]
        index=np.sum([int(a*2**i) for i, a in enumerate(r)])
        syndrome_table[index]=faulty_vector
    return np.array(syndrome_table)
        
    
def decode_linear_code(c, g, syndrome_table):
    '''
    function to decode codeword encoded with linear_code

    Parameters
    ----------
    c : list or np.ndarray
        code_vector.
    g : numpy.Polynominal
        generator polynominal, used to create code_vector.
    syndrome_table : list of error_vectors
        if codeword contains an error, syndrome_table is used to correct wrong bit.

    Returns
    -------
    numpy.ndarray
        data_vector.

    '''
    q, r=divmod(Poly(c), g)
    q=np.r_[q.coef%2, np.zeros(len(c)-len(q)-len(g)+1)]
    r=np.r_[r.coef%2, np.zeros(len(g)-len(r))]
    syndrome_index=np.sum([int(a*2**i) for i, a in enumerate(r)])
    while syndrome_index > 0:
        c=c ^ syndrome_table[syndrome_index]
        q, r=divmod(Poly(c), g)
        q=np.r_[q.coef%2, np.zeros(len(c)-len(q)-len(g)+1)]
        r=np.r_[r.coef%2, np.zeros(len(g)-len(r))]
        syndrome_index=np.sum([int(a*2**i) for i, a in enumerate(r)])
    return np.array(q, dtype=int)
    
def encode_linear_code(d, G):
    '''
    uses generator matrix G to encode d

    Parameters
    ----------
    d : numpy.ndarray
        data_vector.
    G : numpy.ndarray
        generator matrix.

    Returns
    -------
    c : numpy.ndarray
        G @ d.

    '''
    c=(G @ d)%2
    return c

def encrypt(d, pub_key, t):
    '''
    encrypt data with public key, adding t bit-errors

    Parameters
    ----------
    d : numpy.ndarray or list of numpy.ndarray
        data_vector or list of data vectors to encrypt.
    pub_key : numpy.ndarray
        public key matrix used to encrypt data.
    t : int
        number of random errors to add to code_vector.

    Returns
    -------
    numpy.ndarray or list of numpy.ndarray (depending on d)
        encrypted data.

    '''
    if type(d) in (list,):
        encrypted_list=[encrypt(data, pub_key, t) for data in d]
        return encrypted_list
    else:
        c = np.array((pub_key @ d)%2, dtype=int)
        
        indexes_of_errors=np.random.default_rng().permutation(pub_key.shape[0])[:t] #add t random errors to codeword
        e=np.zeros(pub_key.shape[0], dtype=int)
        e[indexes_of_errors]=1
        c=c ^ e
        return np.array(c, dtype=int)

def decrypt(c, P_inv, linear_code_decoder, S_inv):
    '''
    decrypt encrypted message

    Parameters
    ----------
    c : numpy.ndarray or list of numpy.ndarray
        code_vector or list of code_vectors to decrypt.
    P_inv : numpy.ndarray
        inverted permutation matrix.
    linear_code_decoder : function(x)
        function to use to decode linear code.
    S_inv : numpy.ndarray
        inverted random binary matrix.

    Returns
    -------
    numpy.ndarray or list of numpy.ndarray (depending on d)
        decrypted data.

    '''
    if type(c) in (list,):
        decrypted_list=[decrypt(codew, P_inv, linear_code_decoder, S_inv) for codew in c]
        return decrypted_list
    else:
        c=np.array(P_inv @ c, dtype=int)%2
        d=linear_code_decoder(c)
        d=(S_inv @ d)%2
        return np.array(d, dtype=int)

def str_to_blocks4(string):
    blocks=[]
    for char in string:
        bits=[int(value) for value in np.binary_repr(ord(char), 8)[::-1]] #lsb @ index 0
        blocks.append(np.array(bits[:4], dtype=int)) #lower nibble first
        blocks.append(np.array(bits[4:], dtype=int))
    return blocks

def blocks4_to_str(blocks):
    string=''
    for i in range(0, len(blocks), 2):
        char=np.sum([b*2**i for i, b in enumerate(blocks[i])]) + \
            np.sum([b*2**(i+4) for i, b in enumerate(blocks[i+1])])
        string+=chr(char)
    return string

if __name__ == '__main__':

    #shared attributes:
    n=7
    k=4
    t=1
    
    #private key(s):
    g=Poly([1,1,0,1])  #generator polynom for 7/4 linear code (from table, 1.0 + 1.0·x¹ + 0.0·x² + 1.0·x³)
    P_M=gen_perm_M(n)    #create permutation matrix
    G_M=create_linear_code_matrix(n, k, g)  #linear code generator matrix
    S_M, S_inv=create_rand_bin_M(k, True) #random binary matrix and its inverse
    P_M_inv=P_M.T #inverse permutation matrix
    
    syndrome_table=create_syndrome_table(n, g) #part of linear-code decoder
    linear_code_decoder=lambda c:decode_linear_code(c, g, syndrome_table)
    
    #public key:
    pub_key=(P_M @ G_M @ S_M)%2
    
    
    msg_tx='Hello World?'
    
    blocks_tx=str_to_blocks4(msg_tx)
    encrypted=encrypt(blocks_tx, pub_key, t)
    
    blocks_rx=decrypt(encrypted, P_M_inv, linear_code_decoder, S_inv)
    msg_rx=blocks4_to_str(blocks_rx)
    
    print(f'msg_rx: {msg_rx}')